From 20037e61122a688366060f9427506962048e77ed Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Jul 2016 21:03:21 +0800 Subject: Introduce Project#builds_for(build_name, ref = 'HEAD'): So that we could find the particular builds according to build_name and ref. It would be used to find the latest build artifacts from a particular branch or tag. --- app/models/commit_status.rb | 8 +++++++- app/models/project.rb | 8 ++++++++ spec/models/build_spec.rb | 12 ++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index e437e3417a8..6828705dbc8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -16,7 +16,13 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } + scope :latest, -> do + id = unscope(:select). + select("max(#{table_name}.id)"). + group(:name, :commit_id) + + where(id: id) + end scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } diff --git a/app/models/project.rb b/app/models/project.rb index 029026a4e56..293dbd52359 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -429,6 +429,14 @@ class Project < ActiveRecord::Base repository.commit(id) end + def builds_for(build_name, ref = 'HEAD') + sha = commit(ref).sha + + builds.joins(:pipeline). + merge(Ci::Pipeline.where(sha: sha)). + where(name: build_name) + end + def merge_base_commit(first_commit_id, second_commit_id) sha = repository.merge_base(first_commit_id, second_commit_id) repository.commit(sha) if sha diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index e8171788872..8e3c9672fd5 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -673,7 +673,7 @@ describe Ci::Build, models: true do context 'when build is running' do before { build.run! } - it 'should return false' do + it 'returns false' do expect(build.retryable?).to be false end end @@ -681,9 +681,17 @@ describe Ci::Build, models: true do context 'when build is finished' do before { build.success! } - it 'should return true' do + it 'returns true' do expect(build.retryable?).to be true end end end + + describe 'Project#builds_for' do + it 'returns builds from ref and build name' do + latest_build = project.builds_for(build.name, 'HEAD').latest.first + + expect(latest_build.id).to eq(build.id) + end + end end -- cgit v1.2.1 From 1e3ff09cf3cd78755e83288559cfb1cf0ff6539f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Jul 2016 21:18:10 +0800 Subject: Avoid ambiguous syntax --- spec/models/build_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 8e3c9672fd5..cb432a99cd2 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -674,7 +674,7 @@ describe Ci::Build, models: true do before { build.run! } it 'returns false' do - expect(build.retryable?).to be false + expect(build.retryable?).to be(false) end end @@ -682,7 +682,7 @@ describe Ci::Build, models: true do before { build.success! } it 'returns true' do - expect(build.retryable?).to be true + expect(build.retryable?).to be(true) end end end -- cgit v1.2.1 From 8f469c33cc8b90e1bcae8ddd5599ce2a2957a3af Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Jul 2016 21:18:54 +0800 Subject: Multiline for before block --- spec/models/build_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index cb432a99cd2..47ba4931460 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -671,7 +671,9 @@ describe Ci::Build, models: true do describe '#retryable?' do context 'when build is running' do - before { build.run! } + before do + build.run! + end it 'returns false' do expect(build.retryable?).to be(false) @@ -679,7 +681,9 @@ describe Ci::Build, models: true do end context 'when build is finished' do - before { build.success! } + before do + build.success! + end it 'returns true' do expect(build.retryable?).to be(true) -- cgit v1.2.1 From f601ec54fcfad7f365d3488c0a48575862c48958 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 11 Jul 2016 18:17:32 +0800 Subject: Introduce Projects::ArtifactsController#search: So we redirect from ref and build_name to the particular build, namely: * /u/r/artifacts/ref/build_name/* -> /u/r/builds/:build_id/artifacts/* For: * download * browse * file --- app/controllers/projects/artifacts_controller.rb | 24 ++++++++++++++++++++++-- config/routes.rb | 6 ++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index f11c8321464..c00295cd3b5 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -35,14 +35,34 @@ class Projects::ArtifactsController < Projects::ApplicationController redirect_to namespace_project_build_path(project.namespace, project, build) end + def search + url = namespace_project_build_url(project.namespace, project, build) + + if params[:path] + redirect_to "#{url}/artifacts/#{params[:path]}" + else + render_404 + end + end + private def validate_artifacts! - render_404 unless build.artifacts? + render_404 unless build && build.artifacts? end def build - @build ||= project.builds.find_by!(id: params[:build_id]) + @build ||= build_from_id || build_from_ref + end + + def build_from_id + project.builds.find_by(id: params[:build_id]) if params[:build_id] + end + + def build_from_ref + if params[:ref] + project.builds_for(params[:build_name], params[:ref]).latest.first + end end def artifacts_file diff --git a/config/routes.rb b/config/routes.rb index 1572656b8c5..0a4b8609252 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -733,6 +733,12 @@ Rails.application.routes.draw do resources :environments, only: [:index, :show, :new, :create, :destroy] + resources :artifacts, only: [] do + collection do + get :search, path: ':ref/:build_name(/*path)', format: false + end + end + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all -- cgit v1.2.1 From b14d40f0b0b46aba95d15b139345674c6a3dbd09 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 11 Jul 2016 18:40:45 +0800 Subject: Handle branches with / in the name --- config/routes.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 0a4b8609252..5c1460b0e75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -735,7 +735,8 @@ Rails.application.routes.draw do resources :artifacts, only: [] do collection do - get :search, path: ':ref/:build_name(/*path)', format: false + get :search, path: ':ref/:build_name/*path', format: false, + constraints: { ref: %r{.+(?=/)} } # ref could have / end end -- cgit v1.2.1 From ef833a220508f6f8a692b74e7fe593c68981d6f5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 11 Jul 2016 18:51:23 +0800 Subject: Give latest succeeded one, don't give pending/running ones --- app/controllers/projects/artifacts_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index c00295cd3b5..f71499be4f7 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -61,7 +61,9 @@ class Projects::ArtifactsController < Projects::ApplicationController def build_from_ref if params[:ref] - project.builds_for(params[:build_name], params[:ref]).latest.first + builds = project.builds_for(params[:build_name], params[:ref]) + + builds.latest.success.first end end -- cgit v1.2.1 From df5b78676e914ad8e14e96322b1dce383ae26875 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 11 Jul 2016 19:06:14 +0800 Subject: Using plain if/else is much easier to understand --- app/controllers/projects/artifacts_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index f71499be4f7..944fde11b09 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -5,11 +5,11 @@ class Projects::ArtifactsController < Projects::ApplicationController before_action :validate_artifacts! def download - unless artifacts_file.file_storage? - return redirect_to artifacts_file.url + if artifacts_file.file_storage? + send_file artifacts_file.path, disposition: 'attachment' + else + redirect_to artifacts_file.url end - - send_file artifacts_file.path, disposition: 'attachment' end def browse -- cgit v1.2.1 From 1f733a95c73ca767287fcad180e4fa367b4a2354 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 11 Jul 2016 19:06:40 +0800 Subject: Remove redundant return --- app/controllers/projects/artifacts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 944fde11b09..7d6ba80b965 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -16,7 +16,7 @@ class Projects::ArtifactsController < Projects::ApplicationController directory = params[:path] ? "#{params[:path]}/" : '' @entry = build.artifacts_metadata_entry(directory) - return render_404 unless @entry.exists? + render_404 unless @entry.exists? end def file -- cgit v1.2.1 From 2c646bb22593dc377c278622b35f79f1063725ad Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 11 Jul 2016 19:47:45 +0800 Subject: Move tests to respect to modules and extract artifacts tests --- spec/features/builds_spec.rb | 284 ------------------------------- spec/features/projects/artifacts_spec.rb | 31 ++++ spec/features/projects/builds_spec.rb | 267 +++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 284 deletions(-) delete mode 100644 spec/features/builds_spec.rb create mode 100644 spec/features/projects/artifacts_spec.rb create mode 100644 spec/features/projects/builds_spec.rb diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb deleted file mode 100644 index 16832c297ac..00000000000 --- a/spec/features/builds_spec.rb +++ /dev/null @@ -1,284 +0,0 @@ -require 'spec_helper' - -describe "Builds" do - let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } - - before do - login_as(:user) - @commit = FactoryGirl.create :ci_pipeline - @build = FactoryGirl.create :ci_build, pipeline: @commit - @build2 = FactoryGirl.create :ci_build - @project = @commit.project - @project.team << [@user, :developer] - end - - describe "GET /:project/builds" do - context "Running scope" do - before do - @build.run! - visit namespace_project_builds_path(@project.namespace, @project, scope: :running) - end - - it { expect(page).to have_selector('.nav-links li.active', text: 'Running') } - it { expect(page).to have_link 'Cancel running' } - it { expect(page).to have_content @build.short_sha } - it { expect(page).to have_content @build.ref } - it { expect(page).to have_content @build.name } - end - - context "Finished scope" do - before do - @build.run! - visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) - end - - it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') } - it { expect(page).to have_content 'No builds to show' } - it { expect(page).to have_link 'Cancel running' } - end - - context "All builds" do - before do - @project.builds.running_or_pending.each(&:success) - visit namespace_project_builds_path(@project.namespace, @project) - end - - it { expect(page).to have_selector('.nav-links li.active', text: 'All') } - it { expect(page).to have_content @build.short_sha } - it { expect(page).to have_content @build.ref } - it { expect(page).to have_content @build.name } - it { expect(page).not_to have_link 'Cancel running' } - end - end - - describe "POST /:project/builds/:id/cancel_all" do - before do - @build.run! - visit namespace_project_builds_path(@project.namespace, @project) - click_link "Cancel running" - end - - it { expect(page).to have_selector('.nav-links li.active', text: 'All') } - it { expect(page).to have_content 'canceled' } - it { expect(page).to have_content @build.short_sha } - it { expect(page).to have_content @build.ref } - it { expect(page).to have_content @build.name } - it { expect(page).not_to have_link 'Cancel running' } - end - - describe "GET /:project/builds/:id" do - context "Build from project" do - before do - visit namespace_project_build_path(@project.namespace, @project, @build) - end - - it { expect(page.status_code).to eq(200) } - it { expect(page).to have_content @commit.sha[0..7] } - it { expect(page).to have_content @commit.git_commit_message } - it { expect(page).to have_content @commit.git_author_name } - end - - context "Build from other project" do - before do - visit namespace_project_build_path(@project.namespace, @project, @build2) - end - - it { expect(page.status_code).to eq(404) } - end - - context "Download artifacts" do - before do - @build.update_attributes(artifacts_file: artifacts_file) - visit namespace_project_build_path(@project.namespace, @project, @build) - end - - it 'has button to download artifacts' do - expect(page).to have_content 'Download' - end - end - - context 'Artifacts expire date' do - before do - @build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at) - visit namespace_project_build_path(@project.namespace, @project, @build) - end - - context 'no expire date defined' do - let(:expire_at) { nil } - - it 'does not have the Keep button' do - expect(page).not_to have_content 'Keep' - end - end - - context 'when expire date is defined' do - let(:expire_at) { Time.now + 7.days } - - it 'keeps artifacts when Keep button is clicked' do - expect(page).to have_content 'The artifacts will be removed' - click_link 'Keep' - - expect(page).not_to have_link 'Keep' - expect(page).not_to have_content 'The artifacts will be removed' - end - end - - context 'when artifacts expired' do - let(:expire_at) { Time.now - 7.days } - - it 'does not have the Keep button' do - expect(page).to have_content 'The artifacts were removed' - expect(page).not_to have_link 'Keep' - end - end - end - - context 'Build raw trace' do - before do - @build.run! - @build.trace = 'BUILD TRACE' - visit namespace_project_build_path(@project.namespace, @project, @build) - end - - it do - expect(page).to have_link 'Raw' - end - end - end - - describe "POST /:project/builds/:id/cancel" do - context "Build from project" do - before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) - click_link "Cancel" - end - - it { expect(page.status_code).to eq(200) } - it { expect(page).to have_content 'canceled' } - it { expect(page).to have_content 'Retry' } - end - - context "Build from other project" do - before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) - page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2)) - end - - it { expect(page.status_code).to eq(404) } - end - end - - describe "POST /:project/builds/:id/retry" do - context "Build from project" do - before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) - click_link 'Cancel' - click_link 'Retry' - end - - it { expect(page.status_code).to eq(200) } - it { expect(page).to have_content 'pending' } - it { expect(page).to have_content 'Cancel' } - end - - context "Build from other project" do - before do - @build.run! - visit namespace_project_build_path(@project.namespace, @project, @build) - click_link 'Cancel' - page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2)) - end - - it { expect(page.status_code).to eq(404) } - end - end - - describe "GET /:project/builds/:id/download" do - before do - @build.update_attributes(artifacts_file: artifacts_file) - visit namespace_project_build_path(@project.namespace, @project, @build) - click_link 'Download' - end - - context "Build from other project" do - before do - @build2.update_attributes(artifacts_file: artifacts_file) - visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2) - end - - it { expect(page.status_code).to eq(404) } - end - end - - describe "GET /:project/builds/:id/raw" do - context "Build from project" do - before do - Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build.run! - @build.trace = 'BUILD TRACE' - visit namespace_project_build_path(@project.namespace, @project, @build) - page.within('.js-build-sidebar') { click_link 'Raw' } - end - - it 'sends the right headers' do - expect(page.status_code).to eq(200) - expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace) - end - end - - context "Build from other project" do - before do - Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build2.run! - @build2.trace = 'BUILD TRACE' - visit raw_namespace_project_build_path(@project.namespace, @project, @build2) - puts page.status_code - puts current_url - end - - it 'sends the right headers' do - expect(page.status_code).to eq(404) - end - end - end - - describe "GET /:project/builds/:id/trace.json" do - context "Build from project" do - before do - visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json) - end - - it { expect(page.status_code).to eq(200) } - end - - context "Build from other project" do - before do - visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json) - end - - it { expect(page.status_code).to eq(404) } - end - end - - describe "GET /:project/builds/:id/status" do - context "Build from project" do - before do - visit status_namespace_project_build_path(@project.namespace, @project, @build) - end - - it { expect(page.status_code).to eq(200) } - end - - context "Build from other project" do - before do - visit status_namespace_project_build_path(@project.namespace, @project, @build2) - end - - it { expect(page.status_code).to eq(404) } - end - end -end diff --git a/spec/features/projects/artifacts_spec.rb b/spec/features/projects/artifacts_spec.rb new file mode 100644 index 00000000000..fc6e2b07d40 --- /dev/null +++ b/spec/features/projects/artifacts_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe 'Artifacts' do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + + before do + login_as(:user) + @commit = FactoryGirl.create :ci_pipeline + @build = FactoryGirl.create :ci_build, pipeline: @commit + @build2 = FactoryGirl.create :ci_build + @project = @commit.project + @project.team << [@user, :developer] + end + + describe "GET /:project/builds/:id/artifacts/download" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link 'Download' + end + + context "Build from other project" do + before do + @build2.update_attributes(artifacts_file: artifacts_file) + visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2) + end + + it { expect(page.status_code).to eq(404) } + end + end +end diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb new file mode 100644 index 00000000000..25689f1c6e8 --- /dev/null +++ b/spec/features/projects/builds_spec.rb @@ -0,0 +1,267 @@ +require 'spec_helper' + +describe "Builds" do + let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + + before do + login_as(:user) + @commit = FactoryGirl.create :ci_pipeline + @build = FactoryGirl.create :ci_build, pipeline: @commit + @build2 = FactoryGirl.create :ci_build + @project = @commit.project + @project.team << [@user, :developer] + end + + describe "GET /:project/builds" do + context "Running scope" do + before do + @build.run! + visit namespace_project_builds_path(@project.namespace, @project, scope: :running) + end + + it { expect(page).to have_selector('.nav-links li.active', text: 'Running') } + it { expect(page).to have_link 'Cancel running' } + it { expect(page).to have_content @build.short_sha } + it { expect(page).to have_content @build.ref } + it { expect(page).to have_content @build.name } + end + + context "Finished scope" do + before do + @build.run! + visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) + end + + it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') } + it { expect(page).to have_content 'No builds to show' } + it { expect(page).to have_link 'Cancel running' } + end + + context "All builds" do + before do + @project.builds.running_or_pending.each(&:success) + visit namespace_project_builds_path(@project.namespace, @project) + end + + it { expect(page).to have_selector('.nav-links li.active', text: 'All') } + it { expect(page).to have_content @build.short_sha } + it { expect(page).to have_content @build.ref } + it { expect(page).to have_content @build.name } + it { expect(page).not_to have_link 'Cancel running' } + end + end + + describe "POST /:project/builds/:id/cancel_all" do + before do + @build.run! + visit namespace_project_builds_path(@project.namespace, @project) + click_link "Cancel running" + end + + it { expect(page).to have_selector('.nav-links li.active', text: 'All') } + it { expect(page).to have_content 'canceled' } + it { expect(page).to have_content @build.short_sha } + it { expect(page).to have_content @build.ref } + it { expect(page).to have_content @build.name } + it { expect(page).not_to have_link 'Cancel running' } + end + + describe "GET /:project/builds/:id" do + context "Build from project" do + before do + visit namespace_project_build_path(@project.namespace, @project, @build) + end + + it { expect(page.status_code).to eq(200) } + it { expect(page).to have_content @commit.sha[0..7] } + it { expect(page).to have_content @commit.git_commit_message } + it { expect(page).to have_content @commit.git_author_name } + end + + context "Build from other project" do + before do + visit namespace_project_build_path(@project.namespace, @project, @build2) + end + + it { expect(page.status_code).to eq(404) } + end + + context "Download artifacts" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@project.namespace, @project, @build) + end + + it 'has button to download artifacts' do + expect(page).to have_content 'Download' + end + end + + context 'Artifacts expire date' do + before do + @build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at) + visit namespace_project_build_path(@project.namespace, @project, @build) + end + + context 'no expire date defined' do + let(:expire_at) { nil } + + it 'does not have the Keep button' do + expect(page).not_to have_content 'Keep' + end + end + + context 'when expire date is defined' do + let(:expire_at) { Time.now + 7.days } + + it 'keeps artifacts when Keep button is clicked' do + expect(page).to have_content 'The artifacts will be removed' + click_link 'Keep' + + expect(page).not_to have_link 'Keep' + expect(page).not_to have_content 'The artifacts will be removed' + end + end + + context 'when artifacts expired' do + let(:expire_at) { Time.now - 7.days } + + it 'does not have the Keep button' do + expect(page).to have_content 'The artifacts were removed' + expect(page).not_to have_link 'Keep' + end + end + end + + context 'Build raw trace' do + before do + @build.run! + @build.trace = 'BUILD TRACE' + visit namespace_project_build_path(@project.namespace, @project, @build) + end + + it do + expect(page).to have_link 'Raw' + end + end + end + + describe "POST /:project/builds/:id/cancel" do + context "Build from project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link "Cancel" + end + + it { expect(page.status_code).to eq(200) } + it { expect(page).to have_content 'canceled' } + it { expect(page).to have_content 'Retry' } + end + + context "Build from other project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + page.driver.post(cancel_namespace_project_build_path(@project.namespace, @project, @build2)) + end + + it { expect(page.status_code).to eq(404) } + end + end + + describe "POST /:project/builds/:id/retry" do + context "Build from project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link 'Cancel' + click_link 'Retry' + end + + it { expect(page.status_code).to eq(200) } + it { expect(page).to have_content 'pending' } + it { expect(page).to have_content 'Cancel' } + end + + context "Build from other project" do + before do + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link 'Cancel' + page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2)) + end + + it { expect(page.status_code).to eq(404) } + end + end + + describe "GET /:project/builds/:id/raw" do + context "Build from project" do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build.run! + @build.trace = 'BUILD TRACE' + visit namespace_project_build_path(@project.namespace, @project, @build) + page.within('.js-build-sidebar') { click_link 'Raw' } + end + + it 'sends the right headers' do + expect(page.status_code).to eq(200) + expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace) + end + end + + context "Build from other project" do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build2.run! + @build2.trace = 'BUILD TRACE' + visit raw_namespace_project_build_path(@project.namespace, @project, @build2) + puts page.status_code + puts current_url + end + + it 'sends the right headers' do + expect(page.status_code).to eq(404) + end + end + end + + describe "GET /:project/builds/:id/trace.json" do + context "Build from project" do + before do + visit trace_namespace_project_build_path(@project.namespace, @project, @build, format: :json) + end + + it { expect(page.status_code).to eq(200) } + end + + context "Build from other project" do + before do + visit trace_namespace_project_build_path(@project.namespace, @project, @build2, format: :json) + end + + it { expect(page.status_code).to eq(404) } + end + end + + describe "GET /:project/builds/:id/status" do + context "Build from project" do + before do + visit status_namespace_project_build_path(@project.namespace, @project, @build) + end + + it { expect(page.status_code).to eq(200) } + end + + context "Build from other project" do + before do + visit status_namespace_project_build_path(@project.namespace, @project, @build2) + end + + it { expect(page.status_code).to eq(404) } + end + end +end -- cgit v1.2.1 From 2bee8e7db927d2bc2c5912b98dfe52d3c3c40fbd Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 11 Jul 2016 13:50:45 +0100 Subject: Hide build spinner on canceled builds Closes #19651 --- app/assets/javascripts/ci/build.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee index 74691b2c1b5..26b8686dee8 100644 --- a/app/assets/javascripts/ci/build.coffee +++ b/app/assets/javascripts/ci/build.coffee @@ -46,13 +46,15 @@ class @CiBuild , 4000 getInitialBuildTrace: -> + removeRefreshStatuses = ['success', 'failed', 'canceled'] + $.ajax url: @build_url dataType: 'json' success: (build_data) -> $('.js-build-output').html build_data.trace_html - if build_data.status is 'success' or build_data.status is 'failed' + if removeRefreshStatuses.indexOf(build_data.status) >= 0 $('.js-build-refresh').remove() getBuildTrace: -> -- cgit v1.2.1 From d0451a050d5c4a3d343077d0820451af5058636b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 18:56:41 +0800 Subject: Test for Project#builds_for and return empty array for nothing --- app/models/project.rb | 3 +++ spec/models/project_spec.rb | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index f3266a1b197..35ffb0a415d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -430,6 +430,9 @@ class Project < ActiveRecord::Base end def builds_for(build_name, ref = 'HEAD') + ct = commit(ref) + return [] unless ct + sha = commit(ref).sha builds.joins(:pipeline). diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5a27ccbab0a..06d99240708 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -359,11 +359,25 @@ describe Project, models: true do describe :repository do let(:project) { create(:project) } - it 'should return valid repo' do + it 'returns valid repo' do expect(project.repository).to be_kind_of(Repository) end end + describe '#builds_for' do + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit.sha) + end + let(:build) { create(:ci_build, pipeline: pipeline) } + + it 'returns builds for a particular ref' do + build_ids = project.builds_for(build.name, build.sha).map(&:id) + + expect(build_ids).to eq([build.id]) + end + end + describe :default_issues_tracker? do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } -- cgit v1.2.1 From 9604f12331ab0ce3a14b1780cc1245eb8df33038 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 19:14:44 +0800 Subject: Conforming the style --- spec/features/projects/artifacts_spec.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/spec/features/projects/artifacts_spec.rb b/spec/features/projects/artifacts_spec.rb index fc6e2b07d40..5e60c2f3074 100644 --- a/spec/features/projects/artifacts_spec.rb +++ b/spec/features/projects/artifacts_spec.rb @@ -2,27 +2,33 @@ require 'spec_helper' describe 'Artifacts' do let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + let(:pipeline) { create(:ci_pipeline) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:build2) { create(:ci_build) } + let(:project) { pipeline.project } before do login_as(:user) - @commit = FactoryGirl.create :ci_pipeline - @build = FactoryGirl.create :ci_build, pipeline: @commit - @build2 = FactoryGirl.create :ci_build - @project = @commit.project - @project.team << [@user, :developer] + project.team << [@user, :developer] end - describe "GET /:project/builds/:id/artifacts/download" do + describe 'GET /:project/builds/:id/artifacts/download' do before do - @build.update_attributes(artifacts_file: artifacts_file) - visit namespace_project_build_path(@project.namespace, @project, @build) + build.update_attributes(artifacts_file: artifacts_file) + + visit namespace_project_build_path(project.namespace, project, build) + click_link 'Download' end - context "Build from other project" do + context 'Build from other project' do before do - @build2.update_attributes(artifacts_file: artifacts_file) - visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2) + build2.update_attributes(artifacts_file: artifacts_file) + + visit download_namespace_project_build_artifacts_path( + project.namespace, + project, + build2) end it { expect(page.status_code).to eq(404) } -- cgit v1.2.1 From a1eac5e4de95a4d27b30432c527ab410e9d77787 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 19:20:31 +0800 Subject: Doh. I already wrote that test and I forgot. --- spec/models/build_spec.rb | 4 ++-- spec/models/project_spec.rb | 14 -------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 47ba4931460..c7c247189f5 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -693,9 +693,9 @@ describe Ci::Build, models: true do describe 'Project#builds_for' do it 'returns builds from ref and build name' do - latest_build = project.builds_for(build.name, 'HEAD').latest.first + build_ids = project.builds_for(build.name, 'HEAD').map(&:id) - expect(latest_build.id).to eq(build.id) + expect(build_ids).to eq([build.id]) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 06d99240708..143fd5167a4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -364,20 +364,6 @@ describe Project, models: true do end end - describe '#builds_for' do - let(:project) { create(:project) } - let(:pipeline) do - create(:ci_pipeline, project: project, sha: project.commit.sha) - end - let(:build) { create(:ci_build, pipeline: pipeline) } - - it 'returns builds for a particular ref' do - build_ids = project.builds_for(build.name, build.sha).map(&:id) - - expect(build_ids).to eq([build.id]) - end - end - describe :default_issues_tracker? do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } -- cgit v1.2.1 From 6597c213c341ae072216c125a97f94a174fc3dfa Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 19:28:21 +0800 Subject: Prefer empty relation rather than arrays --- app/models/project.rb | 2 +- spec/models/build_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 35ffb0a415d..bc15f8c4138 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -431,7 +431,7 @@ class Project < ActiveRecord::Base def builds_for(build_name, ref = 'HEAD') ct = commit(ref) - return [] unless ct + return builds.none unless ct sha = commit(ref).sha diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index c7c247189f5..b1354faa722 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -697,5 +697,11 @@ describe Ci::Build, models: true do expect(build_ids).to eq([build.id]) end + + it 'returns empty relation if the build cannot be found' do + builds = project.builds_for(build.name, 'TAIL').all + + expect(builds).to be_empty + end end end -- cgit v1.2.1 From c94cff3e13d3f5ab55816ba2e84f48a659462441 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 19:29:59 +0800 Subject: Prefer if over return --- app/models/project.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index bc15f8c4138..366817079bb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -431,13 +431,17 @@ class Project < ActiveRecord::Base def builds_for(build_name, ref = 'HEAD') ct = commit(ref) - return builds.none unless ct - sha = commit(ref).sha + if ct.nil? + builds.none - builds.joins(:pipeline). - merge(Ci::Pipeline.where(sha: sha)). - where(name: build_name) + else + sha = ct.sha + + builds.joins(:pipeline). + merge(Ci::Pipeline.where(sha: sha)). + where(name: build_name) + end end def merge_base_commit(first_commit_id, second_commit_id) -- cgit v1.2.1 From a96e9aa0d3f703e61a415d2b0532f3a96d90ba51 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 20:17:55 +0800 Subject: Make rubocop happy --- spec/features/projects/artifacts_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/projects/artifacts_spec.rb b/spec/features/projects/artifacts_spec.rb index 5e60c2f3074..f5356a8b701 100644 --- a/spec/features/projects/artifacts_spec.rb +++ b/spec/features/projects/artifacts_spec.rb @@ -26,9 +26,9 @@ describe 'Artifacts' do build2.update_attributes(artifacts_file: artifacts_file) visit download_namespace_project_build_artifacts_path( - project.namespace, - project, - build2) + project.namespace, + project, + build2) end it { expect(page.status_code).to eq(404) } -- cgit v1.2.1 From e383254070baf8a4701e3a10b7cc699f03cefff4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 23:15:08 +0800 Subject: Add all the tests and fix stuffs along the way: It turns out they are different: builds.success.latest.first and builds.latest.success.first If we put success first, that latest would also filter via success, and which is what we want here. --- app/controllers/projects/artifacts_controller.rb | 2 +- config/routes.rb | 2 +- .../requests/projects/artifacts_controller_spec.rb | 145 +++++++++++++++++++++ spec/spec_helper.rb | 6 +- 4 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 spec/requests/projects/artifacts_controller_spec.rb diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 7d6ba80b965..25a1c2ca7e1 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -63,7 +63,7 @@ class Projects::ArtifactsController < Projects::ApplicationController if params[:ref] builds = project.builds_for(params[:build_name], params[:ref]) - builds.latest.success.first + builds.success.latest.first end end diff --git a/config/routes.rb b/config/routes.rb index 10b497d05a0..32b00652bb3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -736,7 +736,7 @@ Rails.application.routes.draw do resources :artifacts, only: [] do collection do get :search, path: ':ref/:build_name/*path', format: false, - constraints: { ref: %r{.+(?=/)} } # ref could have / + constraints: { ref: /.+/ } # ref could have / end end diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb new file mode 100644 index 00000000000..b11eb1aedcc --- /dev/null +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -0,0 +1,145 @@ +require 'spec_helper' + +describe Projects::ArtifactsController do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit('fix').sha) + end + let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + + before do + login_as(user) + project.team << [user, :developer] + end + + describe 'GET /:project/artifacts/:ref/:build_name/browse' do + context '404' do + it 'has no such ref' do + path = search_namespace_project_artifacts_path( + project.namespace, + project, + 'TAIL', + build.name, + 'browse') + + get path + + expect(response.status).to eq(404) + end + + it 'has no such build' do + get search_namespace_project_artifacts_path( + project.namespace, + project, + pipeline.sha, + 'NOBUILD', + 'browse') + + expect(response.status).to eq(404) + end + + it 'has no path' do + get search_namespace_project_artifacts_path( + project.namespace, + project, + pipeline.sha, + build.name, + '') + + expect(response.status).to eq(404) + end + end + + context '302' do + def path_from_sha + search_namespace_project_artifacts_path( + project.namespace, + project, + pipeline.sha, + build.name, + 'browse') + end + + shared_examples 'redirect to the build' do + it 'redirects' do + path = browse_namespace_project_build_artifacts_path( + project.namespace, + project, + build) + + expect(response).to redirect_to(path) + end + end + + context 'with sha' do + before do + get path_from_sha + end + + it_behaves_like 'redirect to the build' + end + + context 'with regular branch' do + before do + pipeline.update(sha: project.commit('master').sha) + end + + before do + get search_namespace_project_artifacts_path( + project.namespace, + project, + 'master', + build.name, + 'browse') + end + + it_behaves_like 'redirect to the build' + end + + context 'with branch name containing slash' do + before do + pipeline.update(sha: project.commit('improve/awesome').sha) + end + + before do + get search_namespace_project_artifacts_path( + project.namespace, + project, + 'improve/awesome', + build.name, + 'browse') + end + + it_behaves_like 'redirect to the build' + end + + context 'with latest build' do + before do + 3.times do # creating some old builds + create(:ci_build, :success, :artifacts, pipeline: pipeline) + end + end + + before do + get path_from_sha + end + + it_behaves_like 'redirect to the build' + end + + context 'with success build' do + before do + build # make sure build was old, but still the latest success one + create(:ci_build, :pending, :artifacts, pipeline: pipeline) + end + + before do + get path_from_sha + end + + it_behaves_like 'redirect to the build' + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 606da1b7605..62097de2768 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,9 +28,9 @@ RSpec.configure do |config| config.verbose_retry = true config.display_try_failure_messages = true - config.include Devise::TestHelpers, type: :controller - config.include LoginHelpers, type: :feature - config.include LoginHelpers, type: :request + config.include Devise::TestHelpers, type: :controller + config.include Warden::Test::Helpers, type: :request + config.include LoginHelpers, type: :feature config.include StubConfiguration config.include EmailHelpers config.include RelativeUrl, type: feature -- cgit v1.2.1 From 57c72cb0dfc980106163b313f850c5b8a5e31a70 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 23:26:30 +0800 Subject: Could be faster when params[:path] is missing --- app/controllers/projects/artifacts_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 25a1c2ca7e1..bf9d72ae90c 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -36,9 +36,9 @@ class Projects::ArtifactsController < Projects::ApplicationController end def search - url = namespace_project_build_url(project.namespace, project, build) - if params[:path] + url = namespace_project_build_url(project.namespace, project, build) + redirect_to "#{url}/artifacts/#{params[:path]}" else render_404 -- cgit v1.2.1 From d0b9112fefcf0ee01d9df2dd9c2f1076738a53f1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 12 Jul 2016 23:28:41 +0800 Subject: save some lines and a local variable --- app/models/project.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 366817079bb..2a9d68d10d2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -436,10 +436,8 @@ class Project < ActiveRecord::Base builds.none else - sha = ct.sha - builds.joins(:pipeline). - merge(Ci::Pipeline.where(sha: sha)). + merge(Ci::Pipeline.where(sha: ct.sha)). where(name: build_name) end end -- cgit v1.2.1 From 3336828152767bf22f9c59d54c1c13f5e3d88132 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 13 Jul 2016 17:49:42 +0800 Subject: No need for a separate line now --- spec/requests/projects/artifacts_controller_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index b11eb1aedcc..2ab1c1ac428 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -16,15 +16,13 @@ describe Projects::ArtifactsController do describe 'GET /:project/artifacts/:ref/:build_name/browse' do context '404' do it 'has no such ref' do - path = search_namespace_project_artifacts_path( + get search_namespace_project_artifacts_path( project.namespace, project, 'TAIL', build.name, 'browse') - get path - expect(response.status).to eq(404) end -- cgit v1.2.1 From bb5f06718cd4fb344398a5fef19f3cf9b400de97 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 13 Jul 2016 18:19:29 +0800 Subject: Rename to ref_name so it's aligning with API --- app/controllers/projects/artifacts_controller.rb | 4 ++-- config/routes.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index bf9d72ae90c..f1370efd64e 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -60,8 +60,8 @@ class Projects::ArtifactsController < Projects::ApplicationController end def build_from_ref - if params[:ref] - builds = project.builds_for(params[:build_name], params[:ref]) + if params[:ref_name] + builds = project.builds_for(params[:build_name], params[:ref_name]) builds.success.latest.first end diff --git a/config/routes.rb b/config/routes.rb index 32b00652bb3..203f679226e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -735,8 +735,8 @@ Rails.application.routes.draw do resources :artifacts, only: [] do collection do - get :search, path: ':ref/:build_name/*path', format: false, - constraints: { ref: /.+/ } # ref could have / + get :search, path: ':ref_name/:build_name/*path', format: false, + constraints: { ref_name: /.+/ } # ref could have / end end -- cgit v1.2.1 From 6c80b597f58aaaca514e45a7e83b811db301e651 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 13 Jul 2016 21:44:27 +0800 Subject: Introduce path_from_ref and save some typing --- .../requests/projects/artifacts_controller_spec.rb | 26 +++++++--------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 2ab1c1ac428..4c4bacfcbda 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -50,12 +50,12 @@ describe Projects::ArtifactsController do end context '302' do - def path_from_sha + def path_from_ref(ref = pipeline.sha, build_name = build.name) search_namespace_project_artifacts_path( project.namespace, project, - pipeline.sha, - build.name, + ref, + build_name, 'browse') end @@ -72,7 +72,7 @@ describe Projects::ArtifactsController do context 'with sha' do before do - get path_from_sha + get path_from_ref end it_behaves_like 'redirect to the build' @@ -84,12 +84,7 @@ describe Projects::ArtifactsController do end before do - get search_namespace_project_artifacts_path( - project.namespace, - project, - 'master', - build.name, - 'browse') + get path_from_ref('master') end it_behaves_like 'redirect to the build' @@ -101,12 +96,7 @@ describe Projects::ArtifactsController do end before do - get search_namespace_project_artifacts_path( - project.namespace, - project, - 'improve/awesome', - build.name, - 'browse') + get path_from_ref('improve/awesome') end it_behaves_like 'redirect to the build' @@ -120,7 +110,7 @@ describe Projects::ArtifactsController do end before do - get path_from_sha + get path_from_ref end it_behaves_like 'redirect to the build' @@ -133,7 +123,7 @@ describe Projects::ArtifactsController do end before do - get path_from_sha + get path_from_ref end it_behaves_like 'redirect to the build' -- cgit v1.2.1 From 8735d95af16a6066e9f256a62f401d02c2c7e108 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 13 Jul 2016 23:05:57 +0800 Subject: Implement API for downloading artifacts from ref and build name: Basically: GET /api/projects/:id/artifacts/:ref_name/:build_name Also added tests for it. --- lib/api/api.rb | 1 + lib/api/artifacts.rb | 34 ++++++ spec/requests/api/artifacts_spec.rb | 52 +++++++++ .../requests/projects/artifacts_controller_spec.rb | 122 ++++----------------- spec/requests/shared/artifacts_context.rb | 78 +++++++++++++ 5 files changed, 188 insertions(+), 99 deletions(-) create mode 100644 lib/api/artifacts.rb create mode 100644 spec/requests/api/artifacts_spec.rb create mode 100644 spec/requests/shared/artifacts_context.rb diff --git a/lib/api/api.rb b/lib/api/api.rb index 3d7d67510a8..f18258bf5a3 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -26,6 +26,7 @@ module API # Ensure the namespace is right, otherwise we might load Grape::API::Helpers helpers ::API::Helpers + mount ::API::Artifacts mount ::API::AwardEmoji mount ::API::Branches mount ::API::Builds diff --git a/lib/api/artifacts.rb b/lib/api/artifacts.rb new file mode 100644 index 00000000000..6ce2bed8260 --- /dev/null +++ b/lib/api/artifacts.rb @@ -0,0 +1,34 @@ +module API + # Projects artifacts API + class Artifacts < Grape::API + before do + authenticate! + authorize!(:read_build, user_project) + end + + resource :projects do + # Download the artifacts file from ref_name and build_name + # + # Parameters: + # id (required) - The ID of a project + # ref_name (required) - The ref from repository + # build_name (required) - The name for the build + # Example Request: + # GET /projects/:id/artifacts/:ref_name/:build_name + get ':id/artifacts/:ref_name/:build_name', + requirements: { ref_name: /.+/ } do + builds = user_project.builds_for( + params[:build_name], params[:ref_name]) + + latest_build = builds.success.latest.first + + if latest_build + redirect( + "/projects/#{user_project.id}/builds/#{latest_build.id}/artifacts") + else + not_found! + end + end + end + end +end diff --git a/spec/requests/api/artifacts_spec.rb b/spec/requests/api/artifacts_spec.rb new file mode 100644 index 00000000000..2b84c1a2072 --- /dev/null +++ b/spec/requests/api/artifacts_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' +require_relative '../shared/artifacts_context' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit('fix').sha) + end + let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + + before do + project.team << [user, :developer] + end + + describe 'GET /projects/:id/artifacts/:ref_name/:build_name' do + def path_from_ref(ref = pipeline.sha, build_name = build.name, _ = '') + api("/projects/#{project.id}/artifacts/#{ref}/#{build_name}", user) + end + + context '401' do + let(:user) { nil } + + before do + get path_from_ref + end + + it 'gives 401 for unauthorized user' do + expect(response).to have_http_status(401) + end + end + + context '404' do + def verify + expect(response).to have_http_status(404) + end + + it_behaves_like 'artifacts from ref with 404' + end + + context '302' do + def verify + expect(response).to redirect_to( + "/projects/#{project.id}/builds/#{build.id}/artifacts") + end + + it_behaves_like 'artifacts from ref with 302' + end + end +end diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 4c4bacfcbda..9722a9a1d64 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require_relative '../shared/artifacts_context' describe Projects::ArtifactsController do let(:user) { create(:user) } @@ -14,120 +15,43 @@ describe Projects::ArtifactsController do end describe 'GET /:project/artifacts/:ref/:build_name/browse' do - context '404' do - it 'has no such ref' do - get search_namespace_project_artifacts_path( - project.namespace, - project, - 'TAIL', - build.name, - 'browse') + def path_from_ref(ref = pipeline.sha, build_name = build.name, + path = 'browse') + search_namespace_project_artifacts_path( + project.namespace, + project, + ref, + build_name, + path) + end + context '404' do + def verify expect(response.status).to eq(404) end - it 'has no such build' do - get search_namespace_project_artifacts_path( - project.namespace, - project, - pipeline.sha, - 'NOBUILD', - 'browse') - - expect(response.status).to eq(404) - end + it_behaves_like 'artifacts from ref with 404' - it 'has no path' do - get search_namespace_project_artifacts_path( - project.namespace, - project, - pipeline.sha, - build.name, - '') + context 'has no path' do + before do + get path_from_ref(pipeline.sha, build.name, '') + end - expect(response.status).to eq(404) + it('gives 404') { verify } end end context '302' do - def path_from_ref(ref = pipeline.sha, build_name = build.name) - search_namespace_project_artifacts_path( + def verify + path = browse_namespace_project_build_artifacts_path( project.namespace, project, - ref, - build_name, - 'browse') - end - - shared_examples 'redirect to the build' do - it 'redirects' do - path = browse_namespace_project_build_artifacts_path( - project.namespace, - project, - build) - - expect(response).to redirect_to(path) - end - end - - context 'with sha' do - before do - get path_from_ref - end - - it_behaves_like 'redirect to the build' - end - - context 'with regular branch' do - before do - pipeline.update(sha: project.commit('master').sha) - end - - before do - get path_from_ref('master') - end - - it_behaves_like 'redirect to the build' - end - - context 'with branch name containing slash' do - before do - pipeline.update(sha: project.commit('improve/awesome').sha) - end + build) - before do - get path_from_ref('improve/awesome') - end - - it_behaves_like 'redirect to the build' + expect(response).to redirect_to(path) end - context 'with latest build' do - before do - 3.times do # creating some old builds - create(:ci_build, :success, :artifacts, pipeline: pipeline) - end - end - - before do - get path_from_ref - end - - it_behaves_like 'redirect to the build' - end - - context 'with success build' do - before do - build # make sure build was old, but still the latest success one - create(:ci_build, :pending, :artifacts, pipeline: pipeline) - end - - before do - get path_from_ref - end - - it_behaves_like 'redirect to the build' - end + it_behaves_like 'artifacts from ref with 302' end end end diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb new file mode 100644 index 00000000000..4333be6e1cd --- /dev/null +++ b/spec/requests/shared/artifacts_context.rb @@ -0,0 +1,78 @@ +shared_context 'artifacts from ref with 404' do + context 'has no such ref' do + before do + get path_from_ref('TAIL', build.name) + end + + it('gives 404') { verify } + end + + context 'has no such build' do + before do + get path_from_ref(pipeline.sha, 'NOBUILD') + end + + it('gives 404') { verify } + end +end + +shared_context 'artifacts from ref with 302' do + context 'with sha' do + before do + get path_from_ref + end + + it('redirects') { verify } + end + + context 'with regular branch' do + before do + pipeline.update(sha: project.commit('master').sha) + end + + before do + get path_from_ref('master') + end + + it('redirects') { verify } + end + + context 'with branch name containing slash' do + before do + pipeline.update(sha: project.commit('improve/awesome').sha) + end + + before do + get path_from_ref('improve/awesome') + end + + it('redirects') { verify } + end + + context 'with latest build' do + before do + 3.times do # creating some old builds + create(:ci_build, :success, :artifacts, pipeline: pipeline) + end + end + + before do + get path_from_ref + end + + it('redirects') { verify } + end + + context 'with success build' do + before do + build # make sure build was old, but still the latest success one + create(:ci_build, :pending, :artifacts, pipeline: pipeline) + end + + before do + get path_from_ref + end + + it('redirects') { verify } + end +end -- cgit v1.2.1 From b043729d4959ab5cbfd4aff02ce8c8c4c8a9d26f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 13 Jul 2016 23:23:05 +0800 Subject: Share more stuffs --- spec/requests/api/artifacts_spec.rb | 13 ++----------- spec/requests/projects/artifacts_controller_spec.rb | 16 +++++----------- spec/requests/shared/artifacts_context.rb | 17 +++++++++++++++-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/spec/requests/api/artifacts_spec.rb b/spec/requests/api/artifacts_spec.rb index 2b84c1a2072..393b8f85402 100644 --- a/spec/requests/api/artifacts_spec.rb +++ b/spec/requests/api/artifacts_spec.rb @@ -4,18 +4,9 @@ require_relative '../shared/artifacts_context' describe API::API, api: true do include ApiHelpers - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:pipeline) do - create(:ci_pipeline, project: project, sha: project.commit('fix').sha) - end - let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } - - before do - project.team << [user, :developer] - end - describe 'GET /projects/:id/artifacts/:ref_name/:build_name' do + include_context 'artifacts from ref and build name' + def path_from_ref(ref = pipeline.sha, build_name = build.name, _ = '') api("/projects/#{project.id}/artifacts/#{ref}/#{build_name}", user) end diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 9722a9a1d64..26f6ee8d252 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -2,19 +2,13 @@ require 'spec_helper' require_relative '../shared/artifacts_context' describe Projects::ArtifactsController do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:pipeline) do - create(:ci_pipeline, project: project, sha: project.commit('fix').sha) - end - let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + describe 'GET /:project/artifacts/:ref/:build_name/browse' do + include_context 'artifacts from ref and build name' - before do - login_as(user) - project.team << [user, :developer] - end + before do + login_as(user) + end - describe 'GET /:project/artifacts/:ref/:build_name/browse' do def path_from_ref(ref = pipeline.sha, build_name = build.name, path = 'browse') search_namespace_project_artifacts_path( diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb index 4333be6e1cd..03f7f248773 100644 --- a/spec/requests/shared/artifacts_context.rb +++ b/spec/requests/shared/artifacts_context.rb @@ -1,4 +1,17 @@ -shared_context 'artifacts from ref with 404' do +shared_context 'artifacts from ref and build name' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit('fix').sha) + end + let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + + before do + project.team << [user, :developer] + end +end + +shared_examples 'artifacts from ref with 404' do context 'has no such ref' do before do get path_from_ref('TAIL', build.name) @@ -16,7 +29,7 @@ shared_context 'artifacts from ref with 404' do end end -shared_context 'artifacts from ref with 302' do +shared_examples 'artifacts from ref with 302' do context 'with sha' do before do get path_from_ref -- cgit v1.2.1 From 5b227f351f8b3568e948d705fb1e2bb571cfdcdd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 13 Jul 2016 23:39:03 +0800 Subject: rubocop likes this better --- spec/requests/projects/artifacts_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 26f6ee8d252..d44901289d8 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -9,8 +9,8 @@ describe Projects::ArtifactsController do login_as(user) end - def path_from_ref(ref = pipeline.sha, build_name = build.name, - path = 'browse') + def path_from_ref( + ref = pipeline.sha, build_name = build.name, path = 'browse') search_namespace_project_artifacts_path( project.namespace, project, -- cgit v1.2.1 From 66b91ce9ac70025093d52247ecfaa3f47536809f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 14:24:17 +0800 Subject: Avoid shadowing CommitStatus#id, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13047163 --- app/models/commit_status.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 6828705dbc8..baabbd785cc 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -17,11 +17,11 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user scope :latest, -> do - id = unscope(:select). + max_id = unscope(:select). select("max(#{table_name}.id)"). group(:name, :commit_id) - where(id: id) + where(id: max_id) end scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } -- cgit v1.2.1 From 5544852825d637dfe24b53e93b1e95d21767783c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 14:25:07 +0800 Subject: Remove blank line between if/else clause, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13047184 --- app/models/project.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 2a9d68d10d2..48bb9743439 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -434,7 +434,6 @@ class Project < ActiveRecord::Base if ct.nil? builds.none - else builds.joins(:pipeline). merge(Ci::Pipeline.where(sha: ct.sha)). -- cgit v1.2.1 From fab8bc5313a56c5f22e56903de2cb9c86df79fe4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 14:26:04 +0800 Subject: More descriptive local variable, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13047190 --- app/models/project.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 48bb9743439..793cf2d70fb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -430,13 +430,13 @@ class Project < ActiveRecord::Base end def builds_for(build_name, ref = 'HEAD') - ct = commit(ref) + commit_object = commit(ref) - if ct.nil? + if commit_object.nil? builds.none else builds.joins(:pipeline). - merge(Ci::Pipeline.where(sha: ct.sha)). + merge(Ci::Pipeline.where(sha: commit_object.sha)). where(name: build_name) end end -- cgit v1.2.1 From 1c7871e92f679f65e5b5d065d7478dd2e77f9b77 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 14:39:26 +0800 Subject: Introduce get_build! so we could omit one early return --- lib/api/builds.rb | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index d36047acd1f..f6e96ee7f3a 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -52,8 +52,7 @@ module API get ':id/builds/:build_id' do authorize_read_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) present build, with: Entities::Build, user_can_download_artifacts: can?(current_user, :read_build, user_project) @@ -69,8 +68,7 @@ module API get ':id/builds/:build_id/artifacts' do authorize_read_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) artifacts_file = build.artifacts_file @@ -97,8 +95,7 @@ module API get ':id/builds/:build_id/trace' do authorize_read_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) header 'Content-Disposition', "infile; filename=\"#{build.id}.log\"" content_type 'text/plain' @@ -118,8 +115,7 @@ module API post ':id/builds/:build_id/cancel' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) build.cancel @@ -137,8 +133,7 @@ module API post ':id/builds/:build_id/retry' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) return forbidden!('Build is not retryable') unless build.retryable? build = Ci::Build.retry(build, current_user) @@ -157,8 +152,7 @@ module API post ':id/builds/:build_id/erase' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build + build = get_build!(params[:build_id]) return forbidden!('Build is not erasable!') unless build.erasable? build.erase(erased_by: current_user) @@ -176,8 +170,8 @@ module API post ':id/builds/:build_id/artifacts/keep' do authorize_update_builds! - build = get_build(params[:build_id]) - return not_found!(build) unless build && build.artifacts? + build = get_build!(params[:build_id]) + return not_found!(build) unless build.artifacts? build.keep_artifacts! @@ -192,6 +186,10 @@ module API user_project.builds.find_by(id: id.to_i) end + def get_build!(id) + get_build(id) || not_found! + end + def filter_builds(builds, scope) return builds if scope.nil? || scope.empty? -- cgit v1.2.1 From e01c421b911a46774f8c5be92d383d8da14750c3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 16:36:09 +0800 Subject: Prefer if so it's more clear what's going on --- lib/api/builds.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index f6e96ee7f3a..b3b28541382 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -69,16 +69,17 @@ module API authorize_read_builds! build = get_build!(params[:build_id]) - artifacts_file = build.artifacts_file - unless artifacts_file.file_storage? - return redirect_to build.artifacts_file.url - end + if !artifacts_file.file_storage? + redirect_to(build.artifacts_file.url) - return not_found! unless artifacts_file.exists? + elsif artifacts_file.exists? + present_file!(artifacts_file.path, artifacts_file.filename) - present_file!(artifacts_file.path, artifacts_file.filename) + else + not_found! + end end # Get a trace of a specific build of a project -- cgit v1.2.1 From d7bbee7593ee54a9685c9eded00b121cca3913be Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 16:45:46 +0800 Subject: Update routes based on feedback from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13058785 And note that job/build_name could contain `/` --- app/controllers/projects/artifacts_controller.rb | 2 +- config/routes.rb | 15 ++++++++------- spec/requests/projects/artifacts_controller_spec.rb | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index f1370efd64e..3e487c24cbd 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -61,7 +61,7 @@ class Projects::ArtifactsController < Projects::ApplicationController def build_from_ref if params[:ref_name] - builds = project.builds_for(params[:build_name], params[:ref_name]) + builds = project.builds_for(params[:job], params[:ref_name]) builds.success.latest.first end diff --git a/config/routes.rb b/config/routes.rb index 203f679226e..ea6465038df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -733,16 +733,17 @@ Rails.application.routes.draw do resources :environments, only: [:index, :show, :new, :create, :destroy] - resources :artifacts, only: [] do - collection do - get :search, path: ':ref_name/:build_name/*path', format: false, - constraints: { ref_name: /.+/ } # ref could have / - end - end - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all + + resources :artifacts, only: [] do + collection do + get :search, path: ':ref_name/*path', + format: false, + constraints: { ref_name: /.+/ } # could have / + end + end end member do diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index d44901289d8..574b7028617 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require_relative '../shared/artifacts_context' describe Projects::ArtifactsController do - describe 'GET /:project/artifacts/:ref/:build_name/browse' do + describe 'GET /:project/builds/artifacts/:ref_name/browse?job=name' do include_context 'artifacts from ref and build name' before do @@ -10,13 +10,13 @@ describe Projects::ArtifactsController do end def path_from_ref( - ref = pipeline.sha, build_name = build.name, path = 'browse') + ref = pipeline.sha, job = build.name, path = 'browse') search_namespace_project_artifacts_path( project.namespace, project, ref, - build_name, - path) + path, + job: job) end context '404' do -- cgit v1.2.1 From a9a8ceebcbe25cbe27bebe9fc63ab364b1dd41ee Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 17:35:54 +0800 Subject: Merge features/projects/artifacts_spec.rb back It doesn't make too much sense having this standalone --- spec/features/projects/artifacts_spec.rb | 37 -------------------------------- spec/features/projects/builds_spec.rb | 17 +++++++++++++++ 2 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 spec/features/projects/artifacts_spec.rb diff --git a/spec/features/projects/artifacts_spec.rb b/spec/features/projects/artifacts_spec.rb deleted file mode 100644 index f5356a8b701..00000000000 --- a/spec/features/projects/artifacts_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'spec_helper' - -describe 'Artifacts' do - let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } - let(:pipeline) { create(:ci_pipeline) } - let(:build) { create(:ci_build, pipeline: pipeline) } - let(:build2) { create(:ci_build) } - let(:project) { pipeline.project } - - before do - login_as(:user) - project.team << [@user, :developer] - end - - describe 'GET /:project/builds/:id/artifacts/download' do - before do - build.update_attributes(artifacts_file: artifacts_file) - - visit namespace_project_build_path(project.namespace, project, build) - - click_link 'Download' - end - - context 'Build from other project' do - before do - build2.update_attributes(artifacts_file: artifacts_file) - - visit download_namespace_project_build_artifacts_path( - project.namespace, - project, - build2) - end - - it { expect(page.status_code).to eq(404) } - end - end -end diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index 25689f1c6e8..16832c297ac 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -196,6 +196,23 @@ describe "Builds" do end end + describe "GET /:project/builds/:id/download" do + before do + @build.update_attributes(artifacts_file: artifacts_file) + visit namespace_project_build_path(@project.namespace, @project, @build) + click_link 'Download' + end + + context "Build from other project" do + before do + @build2.update_attributes(artifacts_file: artifacts_file) + visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build2) + end + + it { expect(page.status_code).to eq(404) } + end + end + describe "GET /:project/builds/:id/raw" do context "Build from project" do before do -- cgit v1.2.1 From 70f508f5d48a3541a430539d7f8b41dfa99127a1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 18:50:34 +0800 Subject: Serve artifacts from Builds --- lib/api/api.rb | 1 - lib/api/artifacts.rb | 34 ---------------------------------- lib/api/builds.rb | 35 +++++++++++++++++++++++++++++++---- spec/requests/api/artifacts_spec.rb | 6 +++--- spec/requests/api/builds_spec.rb | 2 +- 5 files changed, 35 insertions(+), 43 deletions(-) delete mode 100644 lib/api/artifacts.rb diff --git a/lib/api/api.rb b/lib/api/api.rb index f18258bf5a3..3d7d67510a8 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -26,7 +26,6 @@ module API # Ensure the namespace is right, otherwise we might load Grape::API::Helpers helpers ::API::Helpers - mount ::API::Artifacts mount ::API::AwardEmoji mount ::API::Branches mount ::API::Builds diff --git a/lib/api/artifacts.rb b/lib/api/artifacts.rb deleted file mode 100644 index 6ce2bed8260..00000000000 --- a/lib/api/artifacts.rb +++ /dev/null @@ -1,34 +0,0 @@ -module API - # Projects artifacts API - class Artifacts < Grape::API - before do - authenticate! - authorize!(:read_build, user_project) - end - - resource :projects do - # Download the artifacts file from ref_name and build_name - # - # Parameters: - # id (required) - The ID of a project - # ref_name (required) - The ref from repository - # build_name (required) - The name for the build - # Example Request: - # GET /projects/:id/artifacts/:ref_name/:build_name - get ':id/artifacts/:ref_name/:build_name', - requirements: { ref_name: /.+/ } do - builds = user_project.builds_for( - params[:build_name], params[:ref_name]) - - latest_build = builds.success.latest.first - - if latest_build - redirect( - "/projects/#{user_project.id}/builds/#{latest_build.id}/artifacts") - else - not_found! - end - end - end - end -end diff --git a/lib/api/builds.rb b/lib/api/builds.rb index b3b28541382..505ba1a66fe 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -71,12 +71,27 @@ module API build = get_build!(params[:build_id]) artifacts_file = build.artifacts_file - if !artifacts_file.file_storage? - redirect_to(build.artifacts_file.url) + present_artifact!(artifacts_file) + end - elsif artifacts_file.exists? - present_file!(artifacts_file.path, artifacts_file.filename) + # Download the artifacts file from ref_name and build_name + # + # Parameters: + # id (required) - The ID of a project + # ref_name (required) - The ref from repository + # job (required) - The name for the build + # Example Request: + # GET /projects/:id/artifacts/:ref_name/:build_name + get ':id/builds/artifacts/:ref_name', + requirements: { ref_name: /.+/ } do + builds = user_project.builds_for( + params[:job], params[:ref_name]) + latest_build = builds.success.latest.first + + if latest_build + redirect( + "/projects/#{user_project.id}/builds/#{latest_build.id}/artifacts") else not_found! end @@ -191,6 +206,18 @@ module API get_build(id) || not_found! end + def present_artifact!(artifacts_file) + if !artifacts_file.file_storage? + redirect_to(build.artifacts_file.url) + + elsif artifacts_file.exists? + present_file!(artifacts_file.path, artifacts_file.filename) + + else + not_found! + end + end + def filter_builds(builds, scope) return builds if scope.nil? || scope.empty? diff --git a/spec/requests/api/artifacts_spec.rb b/spec/requests/api/artifacts_spec.rb index 393b8f85402..f1461f7bc53 100644 --- a/spec/requests/api/artifacts_spec.rb +++ b/spec/requests/api/artifacts_spec.rb @@ -1,14 +1,14 @@ require 'spec_helper' require_relative '../shared/artifacts_context' -describe API::API, api: true do +describe API::API, api: true do include ApiHelpers describe 'GET /projects/:id/artifacts/:ref_name/:build_name' do include_context 'artifacts from ref and build name' - def path_from_ref(ref = pipeline.sha, build_name = build.name, _ = '') - api("/projects/#{project.id}/artifacts/#{ref}/#{build_name}", user) + def path_from_ref(ref = pipeline.sha, job = build.name) + api("/projects/#{project.id}/builds/artifacts/#{ref}?job=#{job}", user) end context '401' do diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index f5b39c3d698..b661bf71545 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } -- cgit v1.2.1 From c4496de8bf90ded13245fedc6b760805f15f6942 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 14 Jul 2016 19:02:07 +0800 Subject: Provide the file directly rather than redirecting --- lib/api/builds.rb | 6 ++---- spec/requests/api/artifacts_spec.rb | 11 ++++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 505ba1a66fe..5c14444f91a 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -69,9 +69,8 @@ module API authorize_read_builds! build = get_build!(params[:build_id]) - artifacts_file = build.artifacts_file - present_artifact!(artifacts_file) + present_artifact!(build.artifacts_file) end # Download the artifacts file from ref_name and build_name @@ -90,8 +89,7 @@ module API latest_build = builds.success.latest.first if latest_build - redirect( - "/projects/#{user_project.id}/builds/#{latest_build.id}/artifacts") + present_artifact!(latest_build.artifacts_file) else not_found! end diff --git a/spec/requests/api/artifacts_spec.rb b/spec/requests/api/artifacts_spec.rb index f1461f7bc53..56d0965b0be 100644 --- a/spec/requests/api/artifacts_spec.rb +++ b/spec/requests/api/artifacts_spec.rb @@ -31,10 +31,15 @@ describe API::API, api: true do it_behaves_like 'artifacts from ref with 404' end - context '302' do + context '200' do def verify - expect(response).to redirect_to( - "/projects/#{project.id}/builds/#{build.id}/artifacts") + download_headers = + { 'Content-Transfer-Encoding' => 'binary', + 'Content-Disposition' => + "attachment; filename=#{build.artifacts_file.filename}" } + + expect(response).to have_http_status(200) + expect(response.headers).to include(download_headers) end it_behaves_like 'artifacts from ref with 302' -- cgit v1.2.1 From 640fc8264d6adce67e89236e23cd83656a84c505 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 15 Jul 2016 00:53:27 +0800 Subject: Avoid using let! --- spec/requests/api/builds_spec.rb | 62 ++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index b661bf71545..350d9cb5c7e 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -6,16 +6,21 @@ describe API::API, api: true do let(:user) { create(:user) } let(:api_user) { user } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } - let!(:developer) { create(:project_member, :developer, user: user, project: project) } - let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) } - let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) } - let!(:build) { create(:ci_build, pipeline: pipeline) } + let(:project) { create(:project, creator_id: user.id) } + let(:developer) { create(:project_member, :developer, user: user, project: project) } + let(:reporter) { create(:project_member, :reporter, user: user2, project: project) } + let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) } + let(:build) { create(:ci_build, pipeline: pipeline) } describe 'GET /projects/:id/builds ' do let(:query) { '' } - before { get api("/projects/#{project.id}/builds?#{query}", api_user) } + before do + developer + build + + get api("/projects/#{project.id}/builds?#{query}", api_user) + end context 'authorized user' do it 'should return project builds' do @@ -77,9 +82,9 @@ describe API::API, api: true do context 'when user is authorized' do context 'when pipeline has builds' do before do - create(:ci_pipeline, project: project, sha: project.commit.id) + developer + build create(:ci_build, pipeline: pipeline) - create(:ci_build) get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user) end @@ -93,6 +98,8 @@ describe API::API, api: true do context 'when pipeline has no builds' do before do + developer + branch_head = project.commit('feature').id get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user) end @@ -107,8 +114,7 @@ describe API::API, api: true do context 'when user is not authorized' do before do - create(:ci_pipeline, project: project, sha: project.commit.id) - create(:ci_build, pipeline: pipeline) + build get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil) end @@ -122,7 +128,11 @@ describe API::API, api: true do end describe 'GET /projects/:id/builds/:build_id' do - before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) } + before do + developer + + get api("/projects/#{project.id}/builds/#{build.id}", api_user) + end context 'authorized user' do it 'should return specific build data' do @@ -141,7 +151,11 @@ describe API::API, api: true do end describe 'GET /projects/:id/builds/:build_id/artifacts' do - before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) } + before do + developer + + get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) + end context 'build with artifacts' do let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } @@ -175,7 +189,11 @@ describe API::API, api: true do describe 'GET /projects/:id/builds/:build_id/trace' do let(:build) { create(:ci_build, :trace, pipeline: pipeline) } - before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) } + before do + developer + + get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) + end context 'authorized user' do it 'should return specific build trace' do @@ -194,7 +212,12 @@ describe API::API, api: true do end describe 'POST /projects/:id/builds/:build_id/cancel' do - before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) } + before do + developer + reporter + + post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) + end context 'authorized user' do context 'user with :update_build persmission' do @@ -225,7 +248,12 @@ describe API::API, api: true do describe 'POST /projects/:id/builds/:build_id/retry' do let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } - before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) } + before do + developer + reporter + + post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) + end context 'authorized user' do context 'user with :update_build permission' do @@ -256,6 +284,8 @@ describe API::API, api: true do describe 'POST /projects/:id/builds/:build_id/erase' do before do + developer + post api("/projects/#{project.id}/builds/#{build.id}/erase", user) end @@ -286,6 +316,8 @@ describe API::API, api: true do describe 'POST /projects/:id/builds/:build_id/artifacts/keep' do before do + developer + post api("/projects/#{project.id}/builds/#{build.id}/artifacts/keep", user) end -- cgit v1.2.1 From 398de9f1de8d2c0bf1bba57a4737a4518975dc85 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 15 Jul 2016 01:07:41 +0800 Subject: now we're able to merge the spec --- spec/requests/api/artifacts_spec.rb | 48 ------------------------------------- spec/requests/api/builds_spec.rb | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 48 deletions(-) delete mode 100644 spec/requests/api/artifacts_spec.rb diff --git a/spec/requests/api/artifacts_spec.rb b/spec/requests/api/artifacts_spec.rb deleted file mode 100644 index 56d0965b0be..00000000000 --- a/spec/requests/api/artifacts_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' -require_relative '../shared/artifacts_context' - -describe API::API, api: true do - include ApiHelpers - - describe 'GET /projects/:id/artifacts/:ref_name/:build_name' do - include_context 'artifacts from ref and build name' - - def path_from_ref(ref = pipeline.sha, job = build.name) - api("/projects/#{project.id}/builds/artifacts/#{ref}?job=#{job}", user) - end - - context '401' do - let(:user) { nil } - - before do - get path_from_ref - end - - it 'gives 401 for unauthorized user' do - expect(response).to have_http_status(401) - end - end - - context '404' do - def verify - expect(response).to have_http_status(404) - end - - it_behaves_like 'artifacts from ref with 404' - end - - context '200' do - def verify - download_headers = - { 'Content-Transfer-Encoding' => 'binary', - 'Content-Disposition' => - "attachment; filename=#{build.artifacts_file.filename}" } - - expect(response).to have_http_status(200) - expect(response.headers).to include(download_headers) - end - - it_behaves_like 'artifacts from ref with 302' - end - end -end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 350d9cb5c7e..5d2e2293236 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require_relative '../shared/artifacts_context' describe API::API, api: true do include ApiHelpers @@ -186,6 +187,48 @@ describe API::API, api: true do end end + describe 'GET /projects/:id/artifacts/:ref_name/:build_name' do + include_context 'artifacts from ref and build name' + + def path_from_ref(ref = pipeline.sha, job = build.name) + api("/projects/#{project.id}/builds/artifacts/#{ref}?job=#{job}", user) + end + + context '401' do + let(:user) { nil } + + before do + get path_from_ref + end + + it 'gives 401 for unauthorized user' do + expect(response).to have_http_status(401) + end + end + + context '404' do + def verify + expect(response).to have_http_status(404) + end + + it_behaves_like 'artifacts from ref with 404' + end + + context '200' do + def verify + download_headers = + { 'Content-Transfer-Encoding' => 'binary', + 'Content-Disposition' => + "attachment; filename=#{build.artifacts_file.filename}" } + + expect(response).to have_http_status(200) + expect(response.headers).to include(download_headers) + end + + it_behaves_like 'artifacts from ref with 302' + end + end + describe 'GET /projects/:id/builds/:build_id/trace' do let(:build) { create(:ci_build, :trace, pipeline: pipeline) } -- cgit v1.2.1 From e96401f097e3d3bffe3a34d6e053af356109370b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 15 Jul 2016 01:24:18 +0800 Subject: Add a download prefix so that we could add file prefix in the future --- lib/api/builds.rb | 7 +++---- spec/requests/api/builds_spec.rb | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 5c14444f91a..6792afb064e 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -80,11 +80,10 @@ module API # ref_name (required) - The ref from repository # job (required) - The name for the build # Example Request: - # GET /projects/:id/artifacts/:ref_name/:build_name - get ':id/builds/artifacts/:ref_name', + # GET /projects/:id/artifacts/download/:ref_name?job=name + get ':id/builds/artifacts/download/:ref_name', requirements: { ref_name: /.+/ } do - builds = user_project.builds_for( - params[:job], params[:ref_name]) + builds = user_project.builds_for(params[:job], params[:ref_name]) latest_build = builds.success.latest.first diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 5d2e2293236..246d273073a 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -191,7 +191,9 @@ describe API::API, api: true do include_context 'artifacts from ref and build name' def path_from_ref(ref = pipeline.sha, job = build.name) - api("/projects/#{project.id}/builds/artifacts/#{ref}?job=#{job}", user) + api( + "/projects/#{project.id}/builds/artifacts/download/#{ref}?job=#{job}", + user) end context '401' do -- cgit v1.2.1 From 2e90abf254096253ac0201de48210c9cabdb2db4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 15 Jul 2016 01:45:04 +0800 Subject: It could be redirecting or downloading in Rails or API --- spec/requests/api/builds_spec.rb | 2 +- spec/requests/projects/artifacts_controller_spec.rb | 2 +- spec/requests/shared/artifacts_context.rb | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 246d273073a..d226646e439 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -227,7 +227,7 @@ describe API::API, api: true do expect(response.headers).to include(download_headers) end - it_behaves_like 'artifacts from ref with 302' + it_behaves_like 'artifacts from ref successfully' end end diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 574b7028617..c8a21f7d0ec 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -45,7 +45,7 @@ describe Projects::ArtifactsController do expect(response).to redirect_to(path) end - it_behaves_like 'artifacts from ref with 302' + it_behaves_like 'artifacts from ref successfully' end end end diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb index 03f7f248773..0c9f33bfcb2 100644 --- a/spec/requests/shared/artifacts_context.rb +++ b/spec/requests/shared/artifacts_context.rb @@ -29,13 +29,13 @@ shared_examples 'artifacts from ref with 404' do end end -shared_examples 'artifacts from ref with 302' do +shared_examples 'artifacts from ref successfully' do context 'with sha' do before do get path_from_ref end - it('redirects') { verify } + it('gives the file') { verify } end context 'with regular branch' do @@ -47,7 +47,7 @@ shared_examples 'artifacts from ref with 302' do get path_from_ref('master') end - it('redirects') { verify } + it('gives the file') { verify } end context 'with branch name containing slash' do @@ -59,7 +59,7 @@ shared_examples 'artifacts from ref with 302' do get path_from_ref('improve/awesome') end - it('redirects') { verify } + it('gives the file') { verify } end context 'with latest build' do @@ -73,7 +73,7 @@ shared_examples 'artifacts from ref with 302' do get path_from_ref end - it('redirects') { verify } + it('gives the file') { verify } end context 'with success build' do @@ -86,6 +86,6 @@ shared_examples 'artifacts from ref with 302' do get path_from_ref end - it('redirects') { verify } + it('gives the file') { verify } end end -- cgit v1.2.1 From c23f2aa53099c6c906a8c6457183502450fa5703 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 15 Jul 2016 01:58:13 +0800 Subject: Fix outdated comment --- lib/api/builds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 6792afb064e..e65dfa88746 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -73,7 +73,7 @@ module API present_artifact!(build.artifacts_file) end - # Download the artifacts file from ref_name and build_name + # Download the artifacts file from ref_name and job # # Parameters: # id (required) - The ID of a project -- cgit v1.2.1 From 4bb3787eee282434263c37194a443edf6a93c1b7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 15 Jul 2016 02:01:10 +0800 Subject: Try to make the URL more consistent between Rails and API --- lib/api/builds.rb | 4 ++-- spec/requests/api/builds_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index e65dfa88746..237a88adcc7 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -80,8 +80,8 @@ module API # ref_name (required) - The ref from repository # job (required) - The name for the build # Example Request: - # GET /projects/:id/artifacts/download/:ref_name?job=name - get ':id/builds/artifacts/download/:ref_name', + # GET /projects/:id/artifacts/:ref_name/download?job=name + get ':id/builds/artifacts/:ref_name/download', requirements: { ref_name: /.+/ } do builds = user_project.builds_for(params[:job], params[:ref_name]) diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index d226646e439..59ea7992023 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -187,12 +187,12 @@ describe API::API, api: true do end end - describe 'GET /projects/:id/artifacts/:ref_name/:build_name' do + describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do include_context 'artifacts from ref and build name' def path_from_ref(ref = pipeline.sha, job = build.name) api( - "/projects/#{project.id}/builds/artifacts/download/#{ref}?job=#{job}", + "/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", user) end -- cgit v1.2.1 From 53a9dee6cb54b75fa2999b4a33a59928b3b73ec3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 15 Jul 2016 02:22:29 +0800 Subject: Introduce Project#latest_success_builds_for: So it's more accessible for views to access the names of jobs. Only filter Build#name from where we really need to download it. --- app/controllers/projects/artifacts_controller.rb | 4 ++-- app/models/project.rb | 9 ++++++--- lib/api/builds.rb | 5 ++--- spec/models/build_spec.rb | 12 ++++++++---- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 3e487c24cbd..1b3c4ec9bd8 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -61,9 +61,9 @@ class Projects::ArtifactsController < Projects::ApplicationController def build_from_ref if params[:ref_name] - builds = project.builds_for(params[:job], params[:ref_name]) + builds = project.latest_success_builds_for(params[:ref_name]) - builds.success.latest.first + builds.where(name: params[:job]).first end end diff --git a/app/models/project.rb b/app/models/project.rb index 793cf2d70fb..384841dbb9a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -429,15 +429,18 @@ class Project < ActiveRecord::Base repository.commit(ref) end - def builds_for(build_name, ref = 'HEAD') + def latest_success_builds_for(ref = 'HEAD') + builds_for(ref).success.latest + end + + def builds_for(ref = 'HEAD') commit_object = commit(ref) if commit_object.nil? builds.none else builds.joins(:pipeline). - merge(Ci::Pipeline.where(sha: commit_object.sha)). - where(name: build_name) + merge(Ci::Pipeline.where(sha: commit_object.sha)) end end diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 237a88adcc7..53774b5c10f 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -83,9 +83,8 @@ module API # GET /projects/:id/artifacts/:ref_name/download?job=name get ':id/builds/artifacts/:ref_name/download', requirements: { ref_name: /.+/ } do - builds = user_project.builds_for(params[:job], params[:ref_name]) - - latest_build = builds.success.latest.first + builds = user_project.latest_success_builds_for(params[:ref_name]) + latest_build = builds.where(name: params[:job]).first if latest_build present_artifact!(latest_build.artifacts_file) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index b1354faa722..7c95463a571 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -691,15 +691,19 @@ describe Ci::Build, models: true do end end - describe 'Project#builds_for' do - it 'returns builds from ref and build name' do - build_ids = project.builds_for(build.name, 'HEAD').map(&:id) + describe 'Project#latest_success_builds_for' do + before do + build.update(status: 'success') + end + + it 'returns builds from ref' do + build_ids = project.latest_success_builds_for('HEAD').map(&:id) expect(build_ids).to eq([build.id]) end it 'returns empty relation if the build cannot be found' do - builds = project.builds_for(build.name, 'TAIL').all + builds = project.latest_success_builds_for('TAIL').all expect(builds).to be_empty end -- cgit v1.2.1 From e313fd124225a0d89f3ad86a0cbb62b93c855898 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Sat, 16 Jul 2016 17:44:34 -0500 Subject: Add artifacts button --- app/views/projects/tags/_download.html.haml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 8a11dbfa9f4..3f335b84c52 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -1,14 +1,32 @@ -%span.btn-group - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do - %span Source code - %a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' } +.dropdown.inline + %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } + = icon('download') %span.caret %span.sr-only Select Archive Format %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li.dropdown-header Source code %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do %span Download zip %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz + - job_names = project.latest_success_builds_for('v8.9.6').pluck(:name) + %li.dropdown-header Artifacts + + + +-# +-# - artifacts = pipeline.builds.latest.select { |b| b.artifacts? } +-# - if artifacts.present? +-# .dropdown.inline.build-artifacts +-# %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} +-# = icon('download') +-# %b.caret +-# %ul.dropdown-menu.dropdown-menu-align-right +-# - artifacts.each do |build| +-# %li +-# = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do +-# = icon("download") +-# %span Download '#{build.name}' artifacts -- cgit v1.2.1 From faeaeda60e5601914338899f6b23b677d37a2ab5 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Sun, 17 Jul 2016 13:22:53 -0500 Subject: Add artifacts to tags --- app/views/projects/tags/_download.html.haml | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 3f335b84c52..1f4c6c9ec08 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -12,21 +12,10 @@ %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - job_names = project.latest_success_builds_for('v8.9.6').pluck(:name) - %li.dropdown-header Artifacts - - - --# --# - artifacts = pipeline.builds.latest.select { |b| b.artifacts? } --# - if artifacts.present? --# .dropdown.inline.build-artifacts --# %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} --# = icon('download') --# %b.caret --# %ul.dropdown-menu.dropdown-menu-align-right --# - artifacts.each do |build| --# %li --# = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do --# = icon("download") --# %span Download '#{build.name}' artifacts + - artifacts = project.builds_for(ref).latest.with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - artifacts.each do |job| + %li + = link_to download_namespace_project_build_artifacts_path(project.namespace, project, job), rel: 'nofollow' do + %span Download '#{job.name}' -- cgit v1.2.1 From 5c28f16a01dd2c5f3e5b4e97d70ee3a9b0cdad3f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Sun, 17 Jul 2016 19:04:45 -0500 Subject: Add artifacts download button on project page and branches page --- app/assets/stylesheets/framework/lists.scss | 4 ++++ app/views/projects/branches/_branch.html.haml | 15 +++++++++++++++ app/views/projects/buttons/_download.html.haml | 23 +++++++++++++++++++++-- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 2c40ec430ca..95a56ee0e95 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -157,6 +157,10 @@ ul.content-list { margin-right: 0; } } + + .artifacts-btn { + margin-right: 10px; + } } // When dragging a list item diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 4bd85061240..78b3de46f58 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,6 +27,21 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare + - artifacts = @project.builds_for(@repository.root_ref).latest.with_artifacts + - if artifacts.any? + .dropdown.inline.artifacts-btn + %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } + = icon('download') + %span.caret + %span.sr-only + Select Archive Format + %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li.dropdown-header Artifacts + - artifacts.each do |job| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do + %span Download '#{job.name}' + - if can_remove_branch?(@project, branch.name) = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do = icon("trash-o") diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 58f43ecb5d5..c971420b16c 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,4 +1,23 @@ - unless @project.empty_repo? - if can? current_user, :download_code, @project - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has-tooltip', data: {container: "body"}, rel: 'nofollow', title: "Download ZIP" do - = icon('download') + .dropdown.inline.btn-group + %button.btn{ 'data-toggle' => 'dropdown' } + = icon('download') + %span.caret + %span.sr-only + Select Archive Format + %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li.dropdown-header Source code + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), rel: 'nofollow' do + %span Download zip + %li + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'tar.gz'), rel: 'nofollow' do + %span Download tar.gz + - artifacts = @project.builds_for(@ref).latest.with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - artifacts.each do |job| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do + %span Download '#{job.name}' -- cgit v1.2.1 From 39c1cabf27da7a082f4e3da669da6b91393016d9 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Sun, 17 Jul 2016 19:11:00 -0500 Subject: Fix dropdown caret --- app/views/projects/buttons/_download.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index c971420b16c..f504d514963 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -3,7 +3,7 @@ .dropdown.inline.btn-group %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') - %span.caret + = icon('caret-down') %span.sr-only Select Archive Format %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } -- cgit v1.2.1 From 5151ebf4c68b3ac87d51474b49962f0e5ba6d3e7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 13:37:00 +0800 Subject: Use RSpec helpers, feedback from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125543 --- spec/models/build_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 7c95463a571..b2dcbd8da2e 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -628,7 +628,7 @@ describe Ci::Build, models: true do describe '#erasable?' do subject { build.erasable? } - it { is_expected.to eq true } + it { is_expected.to be_truthy } end describe '#erased?' do @@ -636,7 +636,7 @@ describe Ci::Build, models: true do subject { build.erased? } context 'build has not been erased' do - it { is_expected.to be false } + it { is_expected.to be_falsey } end context 'build has been erased' do @@ -644,12 +644,13 @@ describe Ci::Build, models: true do build.erase end - it { is_expected.to be true } + it { is_expected.to be_truthy } end end context 'metadata and build trace are not available' do let!(:build) { create(:ci_build, :success, :artifacts) } + before do build.remove_artifacts_metadata! end @@ -676,7 +677,7 @@ describe Ci::Build, models: true do end it 'returns false' do - expect(build.retryable?).to be(false) + expect(build).not_to be_retryable end end @@ -686,7 +687,7 @@ describe Ci::Build, models: true do end it 'returns true' do - expect(build.retryable?).to be(true) + expect(build).to be_retryable end end end -- cgit v1.2.1 From 78c37bdd773da9a41dc55e6915408ccae03186ff Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 13:39:53 +0800 Subject: Use find_by, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125486 --- app/controllers/projects/artifacts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 1b3c4ec9bd8..bfe0781d735 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -63,7 +63,7 @@ class Projects::ArtifactsController < Projects::ApplicationController if params[:ref_name] builds = project.latest_success_builds_for(params[:ref_name]) - builds.where(name: params[:job]).first + builds.find_by(name: params[:job]) end end -- cgit v1.2.1 From 6dcb75f98515d8f3a723edc1900e80cf9427c486 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 13:41:37 +0800 Subject: Remove blank lines between clauses, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125597 --- lib/api/builds.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 53774b5c10f..d988c669cb1 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -205,10 +205,8 @@ module API def present_artifact!(artifacts_file) if !artifacts_file.file_storage? redirect_to(build.artifacts_file.url) - elsif artifacts_file.exists? present_file!(artifacts_file.path, artifacts_file.filename) - else not_found! end -- cgit v1.2.1 From 6de48227badd879f7e12803592daa2fc73656f91 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 13:58:48 +0800 Subject: Use single line even if they're more than 80 chars, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125628 --- spec/requests/api/builds_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 59ea7992023..bc6242a0d71 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -191,9 +191,7 @@ describe API::API, api: true do include_context 'artifacts from ref and build name' def path_from_ref(ref = pipeline.sha, job = build.name) - api( - "/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", - user) + api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", user) end context '401' do -- cgit v1.2.1 From 6830e2821f16d832963320aae571612f50a8aaa0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 14:00:39 +0800 Subject: Match against records rather than id, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125605 https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125611 --- spec/models/build_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index b2dcbd8da2e..a57f0b6886c 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -698,9 +698,9 @@ describe Ci::Build, models: true do end it 'returns builds from ref' do - build_ids = project.latest_success_builds_for('HEAD').map(&:id) + builds = project.latest_success_builds_for('HEAD') - expect(build_ids).to eq([build.id]) + expect(builds).to contain_exactly(build) end it 'returns empty relation if the build cannot be found' do -- cgit v1.2.1 From cc91f09ac39a7c201d527734e835d01dc13e5059 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 14:34:03 +0800 Subject: Just use find_by! and we're rescuing ActiveRecord::RecordNotFound Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125645 --- lib/api/builds.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index d988c669cb1..a27397a82f7 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -84,13 +84,9 @@ module API get ':id/builds/artifacts/:ref_name/download', requirements: { ref_name: /.+/ } do builds = user_project.latest_success_builds_for(params[:ref_name]) - latest_build = builds.where(name: params[:job]).first + latest_build = builds.find_by!(name: params[:job]) - if latest_build - present_artifact!(latest_build.artifacts_file) - else - not_found! - end + present_artifact!(latest_build.artifacts_file) end # Get a trace of a specific build of a project -- cgit v1.2.1 From 5c1f75e983c88d4c884a15e9f84550fd256fb07f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 16:47:47 +0800 Subject: Use ci_commits.gl_project_id instead of ci_builds.gl_project_id: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13125513 --- app/models/project.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 384841dbb9a..d6e37e66a8b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -437,10 +437,11 @@ class Project < ActiveRecord::Base commit_object = commit(ref) if commit_object.nil? - builds.none + Ci::Build.none else - builds.joins(:pipeline). - merge(Ci::Pipeline.where(sha: commit_object.sha)) + Ci::Build.joins(:pipeline). + merge(Ci::Pipeline.where(sha: commit_object.sha, + project: self)) end end -- cgit v1.2.1 From 85409a5a10d22bebbc54a9c7b7c76e7c0e11b208 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 20:10:50 +0800 Subject: Use ci_commits.ref (Pipeline#ref) to find builds --- app/models/project.rb | 11 ++--------- spec/models/build_spec.rb | 5 +++-- spec/requests/api/builds_spec.rb | 2 +- spec/requests/projects/artifacts_controller_spec.rb | 2 +- spec/requests/shared/artifacts_context.rb | 19 ++++++++----------- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index d6e37e66a8b..770ec1c8a68 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -434,15 +434,8 @@ class Project < ActiveRecord::Base end def builds_for(ref = 'HEAD') - commit_object = commit(ref) - - if commit_object.nil? - Ci::Build.none - else - Ci::Build.joins(:pipeline). - merge(Ci::Pipeline.where(sha: commit_object.sha, - project: self)) - end + Ci::Build.joins(:pipeline). + merge(Ci::Pipeline.where(ref: ref, project: self)) end def merge_base_commit(first_commit_id, second_commit_id) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index a57f0b6886c..53064138a50 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -5,7 +5,8 @@ describe Ci::Build, models: true do let(:pipeline) do create(:ci_pipeline, project: project, - sha: project.commit.id) + sha: project.commit.id, + ref: 'fix') end let(:build) { create(:ci_build, pipeline: pipeline) } @@ -698,7 +699,7 @@ describe Ci::Build, models: true do end it 'returns builds from ref' do - builds = project.latest_success_builds_for('HEAD') + builds = project.latest_success_builds_for('fix') expect(builds).to contain_exactly(build) end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index bc6242a0d71..fb0f066498a 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -190,7 +190,7 @@ describe API::API, api: true do describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do include_context 'artifacts from ref and build name' - def path_from_ref(ref = pipeline.sha, job = build.name) + def path_from_ref(ref = pipeline.ref, job = build.name) api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", user) end diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index c8a21f7d0ec..1782d37008a 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -10,7 +10,7 @@ describe Projects::ArtifactsController do end def path_from_ref( - ref = pipeline.sha, job = build.name, path = 'browse') + ref = pipeline.ref, job = build.name, path = 'browse') search_namespace_project_artifacts_path( project.namespace, project, diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb index 0c9f33bfcb2..ff74b72a0b3 100644 --- a/spec/requests/shared/artifacts_context.rb +++ b/spec/requests/shared/artifacts_context.rb @@ -2,7 +2,10 @@ shared_context 'artifacts from ref and build name' do let(:user) { create(:user) } let(:project) { create(:project) } let(:pipeline) do - create(:ci_pipeline, project: project, sha: project.commit('fix').sha) + create(:ci_pipeline, + project: project, + sha: project.commit('fix').sha, + ref: 'fix') end let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } @@ -30,17 +33,10 @@ shared_examples 'artifacts from ref with 404' do end shared_examples 'artifacts from ref successfully' do - context 'with sha' do - before do - get path_from_ref - end - - it('gives the file') { verify } - end - context 'with regular branch' do before do - pipeline.update(sha: project.commit('master').sha) + pipeline.update(ref: 'master', + sha: project.commit('master').sha) end before do @@ -52,7 +48,8 @@ shared_examples 'artifacts from ref successfully' do context 'with branch name containing slash' do before do - pipeline.update(sha: project.commit('improve/awesome').sha) + pipeline.update(ref: 'improve/awesome', + sha: project.commit('improve/awesome').sha) end before do -- cgit v1.2.1 From af86b8c2c2b6fb08ea55eb89f1dd20aba81862ae Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 21:11:53 +0800 Subject: Latest success pipelines (rather than builds) --- app/models/ci/pipeline.rb | 8 +++++++ app/models/commit_status.rb | 4 ++-- app/models/project.rb | 7 ++----- spec/models/build_spec.rb | 35 ++++++++++++++++++++++++------- spec/requests/shared/artifacts_context.rb | 19 ++++++++++------- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index fa4071e2482..431a91004cd 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -18,6 +18,14 @@ module Ci after_touch :update_state after_save :keep_around_commits + scope :latest, -> do + max_id = unscope(:select). + select("max(#{table_name}.id)"). + group(:ref) + + where(id: max_id) + end + def self.truncate_sha(sha) sha[0...8] end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index baabbd785cc..3e97fe68d4b 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -18,8 +18,8 @@ class CommitStatus < ActiveRecord::Base scope :latest, -> do max_id = unscope(:select). - select("max(#{table_name}.id)"). - group(:name, :commit_id) + select("max(#{table_name}.id)"). + group(:name, :commit_id) where(id: max_id) end diff --git a/app/models/project.rb b/app/models/project.rb index 770ec1c8a68..1578fe67581 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -430,12 +430,9 @@ class Project < ActiveRecord::Base end def latest_success_builds_for(ref = 'HEAD') - builds_for(ref).success.latest - end - - def builds_for(ref = 'HEAD') Ci::Build.joins(:pipeline). - merge(Ci::Pipeline.where(ref: ref, project: self)) + merge(pipelines.where(ref: ref).success.latest). + with_artifacts end def merge_base_commit(first_commit_id, second_commit_id) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 53064138a50..8a8a4b46f08 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -6,7 +6,8 @@ describe Ci::Build, models: true do let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit.id, - ref: 'fix') + ref: 'fix', + status: 'success') end let(:build) { create(:ci_build, pipeline: pipeline) } @@ -694,20 +695,38 @@ describe Ci::Build, models: true do end describe 'Project#latest_success_builds_for' do + let(:build) do + create(:ci_build, :artifacts, :success, pipeline: pipeline) + end + before do - build.update(status: 'success') + build end - it 'returns builds from ref' do - builds = project.latest_success_builds_for('fix') + context 'with succeed pipeline' do + it 'returns builds from ref' do + builds = project.latest_success_builds_for('fix') + + expect(builds).to contain_exactly(build) + end + + it 'returns empty relation if the build cannot be found' do + builds = project.latest_success_builds_for('TAIL').all - expect(builds).to contain_exactly(build) + expect(builds).to be_empty + end end - it 'returns empty relation if the build cannot be found' do - builds = project.latest_success_builds_for('TAIL').all + context 'with pending pipeline' do + before do + pipeline.update(status: 'pending') + end - expect(builds).to be_empty + it 'returns empty relation' do + builds = project.latest_success_builds_for('fix').all + + expect(builds).to be_empty + end end end end diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb index ff74b72a0b3..635c5646f91 100644 --- a/spec/requests/shared/artifacts_context.rb +++ b/spec/requests/shared/artifacts_context.rb @@ -25,7 +25,7 @@ shared_examples 'artifacts from ref with 404' do context 'has no such build' do before do - get path_from_ref(pipeline.sha, 'NOBUILD') + get path_from_ref(pipeline.ref, 'NOBUILD') end it('gives 404') { verify } @@ -33,6 +33,11 @@ shared_examples 'artifacts from ref with 404' do end shared_examples 'artifacts from ref successfully' do + def create_new_pipeline(status) + new_pipeline = create(:ci_pipeline, status: 'success') + create(:ci_build, status, :artifacts, pipeline: new_pipeline) + end + context 'with regular branch' do before do pipeline.update(ref: 'master', @@ -59,10 +64,10 @@ shared_examples 'artifacts from ref successfully' do it('gives the file') { verify } end - context 'with latest build' do + context 'with latest pipeline' do before do - 3.times do # creating some old builds - create(:ci_build, :success, :artifacts, pipeline: pipeline) + 3.times do # creating some old pipelines + create_new_pipeline(:success) end end @@ -73,10 +78,10 @@ shared_examples 'artifacts from ref successfully' do it('gives the file') { verify } end - context 'with success build' do + context 'with success pipeline' do before do - build # make sure build was old, but still the latest success one - create(:ci_build, :pending, :artifacts, pipeline: pipeline) + build # make sure pipeline was old, but still the latest success one + create_new_pipeline(:pending) end before do -- cgit v1.2.1 From e51d4a05b7195a98b48548c4c7260886f956b6d2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 21:17:16 +0800 Subject: We should actually give latest success builds as well --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 1578fe67581..d26aa8073e8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -432,7 +432,7 @@ class Project < ActiveRecord::Base def latest_success_builds_for(ref = 'HEAD') Ci::Build.joins(:pipeline). merge(pipelines.where(ref: ref).success.latest). - with_artifacts + with_artifacts.success.latest end def merge_base_commit(first_commit_id, second_commit_id) -- cgit v1.2.1 From dcb436f58f16b20094d126bf0eb9e3403905c7cc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 22:07:26 +0800 Subject: Use Project#latest_success_builds_for --- app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 78b3de46f58..ced0380bb0b 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,7 +27,7 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare - - artifacts = @project.builds_for(@repository.root_ref).latest.with_artifacts + - artifacts = @project.latest_success_builds_for(@repository.root_ref) - if artifacts.any? .dropdown.inline.artifacts-btn %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index f504d514963..2eedc0d9a42 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -14,7 +14,7 @@ %li = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - artifacts = @project.builds_for(@ref).latest.with_artifacts + - artifacts = @project.latest_success_builds_for(@ref) - if artifacts.any? %li.dropdown-header Artifacts - artifacts.each do |job| diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 1f4c6c9ec08..4a8ef77ad8b 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -12,7 +12,7 @@ %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - artifacts = project.builds_for(ref).latest.with_artifacts + - artifacts = project.latest_success_builds_for(ref) - if artifacts.any? %li.dropdown-header Artifacts - artifacts.each do |job| -- cgit v1.2.1 From 72699d9719a269a82be6af0812d86d84733c77bd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 23:16:29 +0800 Subject: Should be branch.name, not root ref --- app/views/projects/branches/_branch.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index ced0380bb0b..c2d7237f142 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,7 +27,7 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare - - artifacts = @project.latest_success_builds_for(@repository.root_ref) + - artifacts = @project.latest_success_builds_for(branch.name) - if artifacts.any? .dropdown.inline.artifacts-btn %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } -- cgit v1.2.1 From 57a78c37c72b2d697bc863ebfb84d3ca61ba9d7b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 23:17:43 +0800 Subject: Show notice if builds are not from latest pipeline --- app/models/ci/build.rb | 3 +++ app/models/project.rb | 12 +++++++--- app/views/projects/branches/_branch.html.haml | 32 +++++++++++++++----------- app/views/projects/buttons/_download.html.haml | 18 +++++++++------ app/views/projects/tags/_download.html.haml | 18 +++++++++------ 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ffac3a22efc..9af04964b85 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -15,6 +15,9 @@ module Ci scope :with_artifacts, ->() { where.not(artifacts_file: nil) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } + scope :latest_success_with_artifacts, ->() do + with_artifacts.success.latest + end mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader diff --git a/app/models/project.rb b/app/models/project.rb index 12851c5d0ec..77431c3f538 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -429,10 +429,16 @@ class Project < ActiveRecord::Base repository.commit(ref) end - def latest_success_builds_for(ref = 'HEAD') + # ref can't be HEAD or SHA, can only be branch/tag name + def latest_success_pipeline_for(ref = 'master') + pipelines.where(ref: ref).success.latest + end + + # ref can't be HEAD or SHA, can only be branch/tag name + def latest_success_builds_for(ref = 'master') Ci::Build.joins(:pipeline). - merge(pipelines.where(ref: ref).success.latest). - with_artifacts.success.latest + merge(latest_success_pipeline_for(ref)). + latest_success_with_artifacts end def merge_base_commit(first_commit_id, second_commit_id) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index c2d7237f142..084a8474c4f 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,20 +27,24 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare - - artifacts = @project.latest_success_builds_for(branch.name) - - if artifacts.any? - .dropdown.inline.artifacts-btn - %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } - = icon('download') - %span.caret - %span.sr-only - Select Archive Format - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li.dropdown-header Artifacts - - artifacts.each do |job| - %li - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do - %span Download '#{job.name}' + - pipeline = @project.latest_success_pipeline_for(branch.name).first + - if pipeline + - artifacts = pipeline.builds.latest_success_with_artifacts + - if artifacts.any? + .dropdown.inline.artifacts-btn + %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } + = icon('download') + %span.caret + %span.sr-only + Select Archive Format + %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li.dropdown-header Artifacts + - unless pipeline.latest? + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(@project.namespace, @project, pipeline.sha))})" + - artifacts.each do |job| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do + %span Download '#{job.name}' - if can_remove_branch?(@project, branch.name) = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 2eedc0d9a42..24a11005e61 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -14,10 +14,14 @@ %li = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - artifacts = @project.latest_success_builds_for(@ref) - - if artifacts.any? - %li.dropdown-header Artifacts - - artifacts.each do |job| - %li - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do - %span Download '#{job.name}' + - pipeline = @project.latest_success_pipeline_for(@ref).first + - if pipeline + - artifacts = pipeline.builds.latest_success_with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(@project.namespace, @project, pipeline.sha))})" + - artifacts.each do |job| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do + %span Download '#{job.name}' diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 4a8ef77ad8b..d2650809a3e 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -12,10 +12,14 @@ %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - artifacts = project.latest_success_builds_for(ref) - - if artifacts.any? - %li.dropdown-header Artifacts - - artifacts.each do |job| - %li - = link_to download_namespace_project_build_artifacts_path(project.namespace, project, job), rel: 'nofollow' do - %span Download '#{job.name}' + - pipeline = project.latest_success_pipeline_for(ref).first + - if pipeline + - artifacts = pipeline.builds.latest_success_with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(project.namespace, project, pipeline.sha))})" + - artifacts.each do |job| + %li + = link_to download_namespace_project_build_artifacts_path(project.namespace, project, job), rel: 'nofollow' do + %span Download '#{job.name}' -- cgit v1.2.1 From 914336b2d9da688a7b6267425a408c0ce280f9b2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 23:26:30 +0800 Subject: Links to search_namespace_project_artifacts_path instead --- app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 084a8474c4f..bb1f2ec9604 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -43,7 +43,7 @@ = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(@project.namespace, @project, pipeline.sha))})" - artifacts.each do |job| %li - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do + = link_to search_namespace_project_artifacts_path(@project.namespace, @project, branch.name, 'download', job: job.name), rel: 'nofollow' do %span Download '#{job.name}' - if can_remove_branch?(@project, branch.name) diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 24a11005e61..558a9023ed2 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -23,5 +23,5 @@ = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(@project.namespace, @project, pipeline.sha))})" - artifacts.each do |job| %li - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do + = link_to search_namespace_project_artifacts_path(@project.namespace, @project, @ref, 'download', job: job.name), rel: 'nofollow' do %span Download '#{job.name}' diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index d2650809a3e..2a8891e917f 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -21,5 +21,5 @@ = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(project.namespace, project, pipeline.sha))})" - artifacts.each do |job| %li - = link_to download_namespace_project_build_artifacts_path(project.namespace, project, job), rel: 'nofollow' do + = link_to search_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do %span Download '#{job.name}' -- cgit v1.2.1 From e00fb385b247f90f12ddadd090e87cd59f2ebb36 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 18 Jul 2016 23:34:31 +0800 Subject: Actually should use tree path --- app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index bb1f2ec9604..cbd6ab74128 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -40,7 +40,7 @@ %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(@project.namespace, @project, pipeline.sha))})" + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(@project.namespace, @project, pipeline.sha))})" - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, branch.name, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 558a9023ed2..f96045e09f0 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -20,7 +20,7 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(@project.namespace, @project, pipeline.sha))})" + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(@project.namespace, @project, pipeline.sha))})" - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, @ref, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 2a8891e917f..6a431bcf7b2 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -18,7 +18,7 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_commits_path(project.namespace, project, pipeline.sha))})" + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(project.namespace, project, pipeline.sha))})" - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do -- cgit v1.2.1 From 5fa6af05eb42feed7e0ca69778019805f7780ea5 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 18 Jul 2016 21:02:18 -0700 Subject: Add artifacts to view branch page download dropdown --- .../repositories/_download_archive.html.haml | 29 ++++++++++------------ app/views/projects/tree/show.html.haml | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 24658319060..5b2ddce3e91 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -1,16 +1,14 @@ - ref = ref || nil - btn_class = btn_class || '' -- split_button = split_button || false -- if split_button == true - %span.btn-group{class: btn_class} - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do - %i.fa.fa-download - %span Download zip - %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } +%span.btn-group{class: btn_class} + .dropdown.inline + %button.btn{ 'data-toggle' => 'dropdown' } + = icon('download') %span.caret %span.sr-only Select Archive Format - %ul.col-xs-10.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li.dropdown-header Source code %li = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do %i.fa.fa-download @@ -27,11 +25,10 @@ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do %i.fa.fa-download %span Download tar -- else - %span.btn-group{class: btn_class} - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do - %i.fa.fa-download - %span zip - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do - %i.fa.fa-download - %span tar.gz + - artifacts = @project.latest_success_builds_for(@ref) + - if artifacts.any? + %li.dropdown-header Artifacts + - artifacts.each do |job| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do + %span Download '#{job.name}' diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index bf5360b4dee..c68f86f1378 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -11,7 +11,7 @@ .tree-controls = render 'projects/find_file_link' - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true + = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped' #tree-holder.tree-holder.clearfix .nav-block -- cgit v1.2.1 From 1a41cb90175ac8d6f780bf4fd35e4862f5312574 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 19 Jul 2016 16:50:53 +0800 Subject: Fix links and add not latest notice --- .../projects/repositories/_download_archive.html.haml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 5b2ddce3e91..396fb8598ae 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -25,10 +25,14 @@ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do %i.fa.fa-download %span Download tar - - artifacts = @project.latest_success_builds_for(@ref) - - if artifacts.any? - %li.dropdown-header Artifacts - - artifacts.each do |job| - %li - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, job), rel: 'nofollow' do - %span Download '#{job.name}' + - pipeline = @project.latest_success_pipeline_for(ref).first + - if pipeline + - artifacts = pipeline.builds.latest_success_with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(@project.namespace, @project, pipeline.sha))})" + - artifacts.each do |job| + %li + = link_to search_namespace_project_artifacts_path(@project.namespace, @project, ref, 'download', job: job.name), rel: 'nofollow' do + %span Download '#{job.name}' -- cgit v1.2.1 From fba2ec45b3bf493611f2d7e7e13a21c39bc654e0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 19 Jul 2016 17:08:21 +0800 Subject: Just use order(id: :desc) for latest stuffs: We don't need that subquery for group by ref and alike here. --- app/models/ci/build.rb | 2 +- app/models/ci/pipeline.rb | 10 +--------- app/models/project.rb | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 9af04964b85..c048eff0f80 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -16,7 +16,7 @@ module Ci scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :latest_success_with_artifacts, ->() do - with_artifacts.success.latest + with_artifacts.success.order(id: :desc) end mount_uploader :artifacts_file, ArtifactUploader diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index a8e6a23e1c4..148b056789a 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -20,14 +20,6 @@ module Ci after_touch :update_state after_save :keep_around_commits - scope :latest, -> do - max_id = unscope(:select). - select("max(#{table_name}.id)"). - group(:ref) - - where(id: max_id) - end - def self.truncate_sha(sha) sha[0...8] end @@ -226,7 +218,7 @@ module Ci def keep_around_commits return unless project - + project.repository.keep_around(self.sha) project.repository.keep_around(self.before_sha) end diff --git a/app/models/project.rb b/app/models/project.rb index 77431c3f538..30e8ade99ff 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -431,7 +431,7 @@ class Project < ActiveRecord::Base # ref can't be HEAD or SHA, can only be branch/tag name def latest_success_pipeline_for(ref = 'master') - pipelines.where(ref: ref).success.latest + pipelines.where(ref: ref).success.order(id: :desc) end # ref can't be HEAD or SHA, can only be branch/tag name -- cgit v1.2.1 From 0538e1e934484e76575164314fe8451374e4a4c8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 19 Jul 2016 17:09:32 +0800 Subject: Support SHA for downloading artifacts: So if we also query against SHA, we could actually support SHA. If there's a branch or tag also named like SHA this could be ambiguous, but since we could already do that in Git, I think it's probably fine, people would be aware they shouldn't use the same name anyway. --- app/models/project.rb | 9 ++++++--- spec/requests/shared/artifacts_context.rb | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 30e8ade99ff..c1cb1558132 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -429,12 +429,15 @@ class Project < ActiveRecord::Base repository.commit(ref) end - # ref can't be HEAD or SHA, can only be branch/tag name + # ref can't be HEAD, can only be branch/tag name or SHA def latest_success_pipeline_for(ref = 'master') - pipelines.where(ref: ref).success.order(id: :desc) + table = Ci::Pipeline.quoted_table_name + # TODO: Use `where(ref: ref).or(sha: ref)` in Rails 5 + pipelines.where("#{table}.ref = ? OR #{table}.sha = ?", ref, ref). + success.order(id: :desc) end - # ref can't be HEAD or SHA, can only be branch/tag name + # ref can't be HEAD, can only be branch/tag name or SHA def latest_success_builds_for(ref = 'master') Ci::Build.joins(:pipeline). merge(latest_success_pipeline_for(ref)). diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb index 635c5646f91..102ae392844 100644 --- a/spec/requests/shared/artifacts_context.rb +++ b/spec/requests/shared/artifacts_context.rb @@ -38,6 +38,14 @@ shared_examples 'artifacts from ref successfully' do create(:ci_build, status, :artifacts, pipeline: new_pipeline) end + context 'with sha' do + before do + get path_from_ref(pipeline.sha) + end + + it('gives the file') { verify } + end + context 'with regular branch' do before do pipeline.update(ref: 'master', -- cgit v1.2.1 From 85ceb8b72f5a67d21bc9530fe835fdece98f3d4e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 19 Jul 2016 17:51:45 +0800 Subject: Rename latest_success* to latest_successful: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13164642 --- app/controllers/projects/artifacts_controller.rb | 2 +- app/models/ci/build.rb | 2 +- app/models/project.rb | 8 ++++---- app/views/projects/branches/_branch.html.haml | 4 ++-- app/views/projects/buttons/_download.html.haml | 4 ++-- app/views/projects/repositories/_download_archive.html.haml | 4 ++-- app/views/projects/tags/_download.html.haml | 4 ++-- lib/api/builds.rb | 2 +- spec/models/build_spec.rb | 8 ++++---- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index f33cf238d88..05112571225 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -60,7 +60,7 @@ class Projects::ArtifactsController < Projects::ApplicationController def build_from_ref if params[:ref_name] - builds = project.latest_success_builds_for(params[:ref_name]) + builds = project.latest_successful_builds_for(params[:ref_name]) builds.find_by(name: params[:job]) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index c048eff0f80..65dfe4f0190 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -15,7 +15,7 @@ module Ci scope :with_artifacts, ->() { where.not(artifacts_file: nil) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } - scope :latest_success_with_artifacts, ->() do + scope :latest_successful_with_artifacts, ->() do with_artifacts.success.order(id: :desc) end diff --git a/app/models/project.rb b/app/models/project.rb index c1cb1558132..60928bf9922 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -430,7 +430,7 @@ class Project < ActiveRecord::Base end # ref can't be HEAD, can only be branch/tag name or SHA - def latest_success_pipeline_for(ref = 'master') + def latest_successful_pipeline_for(ref = 'master') table = Ci::Pipeline.quoted_table_name # TODO: Use `where(ref: ref).or(sha: ref)` in Rails 5 pipelines.where("#{table}.ref = ? OR #{table}.sha = ?", ref, ref). @@ -438,10 +438,10 @@ class Project < ActiveRecord::Base end # ref can't be HEAD, can only be branch/tag name or SHA - def latest_success_builds_for(ref = 'master') + def latest_successful_builds_for(ref = 'master') Ci::Build.joins(:pipeline). - merge(latest_success_pipeline_for(ref)). - latest_success_with_artifacts + merge(latest_successful_pipeline_for(ref)). + latest_successful_with_artifacts end def merge_base_commit(first_commit_id, second_commit_id) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index cbd6ab74128..8f6ddfd9044 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,9 +27,9 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare - - pipeline = @project.latest_success_pipeline_for(branch.name).first + - pipeline = @project.latest_successful_pipeline_for(branch.name).first - if pipeline - - artifacts = pipeline.builds.latest_success_with_artifacts + - artifacts = pipeline.builds.latest_successful_with_artifacts - if artifacts.any? .dropdown.inline.artifacts-btn %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index f96045e09f0..047931a7fa5 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -14,9 +14,9 @@ %li = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - pipeline = @project.latest_success_pipeline_for(@ref).first + - pipeline = @project.latest_successful_pipeline_for(@ref).first - if pipeline - - artifacts = pipeline.builds.latest_success_with_artifacts + - artifacts = pipeline.builds.latest_successful_with_artifacts - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 396fb8598ae..1c03aa0a332 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -25,9 +25,9 @@ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do %i.fa.fa-download %span Download tar - - pipeline = @project.latest_success_pipeline_for(ref).first + - pipeline = @project.latest_successful_pipeline_for(ref).first - if pipeline - - artifacts = pipeline.builds.latest_success_with_artifacts + - artifacts = pipeline.builds.latest_successful_with_artifacts - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 6a431bcf7b2..8e5e5cb559b 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -12,9 +12,9 @@ %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - pipeline = project.latest_success_pipeline_for(ref).first + - pipeline = project.latest_successful_pipeline_for(ref).first - if pipeline - - artifacts = pipeline.builds.latest_success_with_artifacts + - artifacts = pipeline.builds.latest_successful_with_artifacts - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? diff --git a/lib/api/builds.rb b/lib/api/builds.rb index a27397a82f7..bb9e8f1ae6e 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -83,7 +83,7 @@ module API # GET /projects/:id/artifacts/:ref_name/download?job=name get ':id/builds/artifacts/:ref_name/download', requirements: { ref_name: /.+/ } do - builds = user_project.latest_success_builds_for(params[:ref_name]) + builds = user_project.latest_successful_builds_for(params[:ref_name]) latest_build = builds.find_by!(name: params[:job]) present_artifact!(latest_build.artifacts_file) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index d435cd745b3..355cb8fdfff 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -678,7 +678,7 @@ describe Ci::Build, models: true do end end - describe 'Project#latest_success_builds_for' do + describe 'Project#latest_successful_builds_for' do let(:build) do create(:ci_build, :artifacts, :success, pipeline: pipeline) end @@ -689,13 +689,13 @@ describe Ci::Build, models: true do context 'with succeed pipeline' do it 'returns builds from ref' do - builds = project.latest_success_builds_for('fix') + builds = project.latest_successful_builds_for('fix') expect(builds).to contain_exactly(build) end it 'returns empty relation if the build cannot be found' do - builds = project.latest_success_builds_for('TAIL').all + builds = project.latest_successful_builds_for('TAIL').all expect(builds).to be_empty end @@ -707,7 +707,7 @@ describe Ci::Build, models: true do end it 'returns empty relation' do - builds = project.latest_success_builds_for('fix').all + builds = project.latest_successful_builds_for('fix').all expect(builds).to be_empty end -- cgit v1.2.1 From e3ce02300bf90451b98479720d1093afe8b7eea8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 19 Jul 2016 18:03:21 +0800 Subject: Link to pipeline instead of source tree, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13164795 --- app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/repositories/_download_archive.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 8f6ddfd9044..e48b78f9eef 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -40,7 +40,7 @@ %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(@project.namespace, @project, pipeline.sha))})" + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(@project.namespace, @project, pipeline))})" - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, branch.name, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 047931a7fa5..32f911f6b31 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -20,7 +20,7 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(@project.namespace, @project, pipeline.sha))})" + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(@project.namespace, @project, pipeline))})" - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, @ref, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 1c03aa0a332..e29d99371c8 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -31,7 +31,7 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(@project.namespace, @project, pipeline.sha))})" + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(@project.namespace, @project, pipeline))})" - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, ref, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 8e5e5cb559b..8acf145049a 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -18,7 +18,7 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_tree_path(project.namespace, project, pipeline.sha))})" + = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(project.namespace, project, pipeline))})" - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do -- cgit v1.2.1 From 69d0671342f361f48001c1c9bb4571cd881fdca8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 21 Jul 2016 18:47:05 +0800 Subject: Restore to what it used to be --- spec/requests/api/builds_spec.rb | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 56eb9cd8f8d..0d9820df18f 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require_relative '../shared/artifacts_context' describe API::API, api: true do include ApiHelpers @@ -17,9 +16,6 @@ describe API::API, api: true do let(:query) { '' } before do - developer - build - get api("/projects/#{project.id}/builds?#{query}", api_user) end @@ -83,9 +79,9 @@ describe API::API, api: true do context 'when user is authorized' do context 'when pipeline has builds' do before do - developer - build + create(:ci_pipeline, project: project, sha: project.commit.id) create(:ci_build, pipeline: pipeline) + create(:ci_build) get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user) end @@ -99,8 +95,6 @@ describe API::API, api: true do context 'when pipeline has no builds' do before do - developer - branch_head = project.commit('feature').id get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user) end @@ -115,7 +109,8 @@ describe API::API, api: true do context 'when user is not authorized' do before do - build + create(:ci_pipeline, project: project, sha: project.commit.id) + create(:ci_build, pipeline: pipeline) get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil) end @@ -130,8 +125,6 @@ describe API::API, api: true do describe 'GET /projects/:id/builds/:build_id' do before do - developer - get api("/projects/#{project.id}/builds/#{build.id}", api_user) end @@ -153,8 +146,6 @@ describe API::API, api: true do describe 'GET /projects/:id/builds/:build_id/artifacts' do before do - developer - get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) end @@ -304,9 +295,6 @@ describe API::API, api: true do describe 'POST /projects/:id/builds/:build_id/cancel' do before do - developer - reporter - post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) end @@ -340,9 +328,6 @@ describe API::API, api: true do let(:build) { create(:ci_build, :canceled, pipeline: pipeline) } before do - developer - reporter - post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) end @@ -375,8 +360,6 @@ describe API::API, api: true do describe 'POST /projects/:id/builds/:build_id/erase' do before do - developer - post api("/projects/#{project.id}/builds/#{build.id}/erase", user) end @@ -407,8 +390,6 @@ describe API::API, api: true do describe 'POST /projects/:id/builds/:build_id/artifacts/keep' do before do - developer - post api("/projects/#{project.id}/builds/#{build.id}/artifacts/keep", user) end -- cgit v1.2.1 From fa02d8fcc588b3720640131e7cc9ef7b06470932 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 21 Jul 2016 19:09:46 +0800 Subject: Merge shared context into controller test and update accordingly --- .../requests/projects/artifacts_controller_spec.rb | 77 +++++++++++++--- spec/requests/shared/artifacts_context.rb | 101 --------------------- 2 files changed, 62 insertions(+), 116 deletions(-) delete mode 100644 spec/requests/shared/artifacts_context.rb diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 1782d37008a..b823676d9e1 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -1,9 +1,20 @@ require 'spec_helper' -require_relative '../shared/artifacts_context' describe Projects::ArtifactsController do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_pipeline, + project: project, + sha: project.commit.sha, + ref: project.default_branch) + end + let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } + describe 'GET /:project/builds/artifacts/:ref_name/browse?job=name' do - include_context 'artifacts from ref and build name' + before do + project.team << [user, :developer] + end before do login_as(user) @@ -19,33 +30,69 @@ describe Projects::ArtifactsController do job: job) end - context '404' do - def verify - expect(response.status).to eq(404) + context 'cannot find the build' do + shared_examples 'not found' do + it { expect(response).to have_http_status(:not_found) } end - it_behaves_like 'artifacts from ref with 404' + context 'has no such ref' do + before do + get path_from_ref('TAIL', build.name) + end + + it_behaves_like 'not found' + end + + context 'has no such build' do + before do + get path_from_ref(pipeline.ref, 'NOBUILD') + end + + it_behaves_like 'not found' + end context 'has no path' do before do get path_from_ref(pipeline.sha, build.name, '') end - it('gives 404') { verify } + it_behaves_like 'not found' end end - context '302' do - def verify - path = browse_namespace_project_build_artifacts_path( - project.namespace, - project, - build) + context 'found the build and redirect' do + shared_examples 'redirect to the build' do + it 'redirects' do + path = browse_namespace_project_build_artifacts_path( + project.namespace, + project, + build) - expect(response).to redirect_to(path) + expect(response).to redirect_to(path) + end + end + + context 'with regular branch' do + before do + pipeline.update(ref: 'master', + sha: project.commit('master').sha) + + get path_from_ref('master') + end + + it_behaves_like 'redirect to the build' end - it_behaves_like 'artifacts from ref successfully' + context 'with branch name containing slash' do + before do + pipeline.update(ref: 'improve/awesome', + sha: project.commit('improve/awesome').sha) + + get path_from_ref('improve/awesome') + end + + it_behaves_like 'redirect to the build' + end end end end diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb deleted file mode 100644 index 102ae392844..00000000000 --- a/spec/requests/shared/artifacts_context.rb +++ /dev/null @@ -1,101 +0,0 @@ -shared_context 'artifacts from ref and build name' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:pipeline) do - create(:ci_pipeline, - project: project, - sha: project.commit('fix').sha, - ref: 'fix') - end - let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } - - before do - project.team << [user, :developer] - end -end - -shared_examples 'artifacts from ref with 404' do - context 'has no such ref' do - before do - get path_from_ref('TAIL', build.name) - end - - it('gives 404') { verify } - end - - context 'has no such build' do - before do - get path_from_ref(pipeline.ref, 'NOBUILD') - end - - it('gives 404') { verify } - end -end - -shared_examples 'artifacts from ref successfully' do - def create_new_pipeline(status) - new_pipeline = create(:ci_pipeline, status: 'success') - create(:ci_build, status, :artifacts, pipeline: new_pipeline) - end - - context 'with sha' do - before do - get path_from_ref(pipeline.sha) - end - - it('gives the file') { verify } - end - - context 'with regular branch' do - before do - pipeline.update(ref: 'master', - sha: project.commit('master').sha) - end - - before do - get path_from_ref('master') - end - - it('gives the file') { verify } - end - - context 'with branch name containing slash' do - before do - pipeline.update(ref: 'improve/awesome', - sha: project.commit('improve/awesome').sha) - end - - before do - get path_from_ref('improve/awesome') - end - - it('gives the file') { verify } - end - - context 'with latest pipeline' do - before do - 3.times do # creating some old pipelines - create_new_pipeline(:success) - end - end - - before do - get path_from_ref - end - - it('gives the file') { verify } - end - - context 'with success pipeline' do - before do - build # make sure pipeline was old, but still the latest success one - create_new_pipeline(:pending) - end - - before do - get path_from_ref - end - - it('gives the file') { verify } - end -end -- cgit v1.2.1 From 2d9e7468de7edfe1868b8d9dc6dcdaff116f0da8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 21 Jul 2016 20:00:33 +0800 Subject: They were moved to project_spec.rb --- spec/models/build_spec.rb | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 950580fdee3..13ef60b732a 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -915,40 +915,4 @@ describe Ci::Build, models: true do end end end - - describe 'Project#latest_successful_builds_for' do - let(:build) do - create(:ci_build, :artifacts, :success, pipeline: pipeline) - end - - before do - build - end - - context 'with succeed pipeline' do - it 'returns builds from ref' do - builds = project.latest_successful_builds_for('fix') - - expect(builds).to contain_exactly(build) - end - - it 'returns empty relation if the build cannot be found' do - builds = project.latest_successful_builds_for('TAIL').all - - expect(builds).to be_empty - end - end - - context 'with pending pipeline' do - before do - pipeline.update(status: 'pending') - end - - it 'returns empty relation' do - builds = project.latest_successful_builds_for('fix').all - - expect(builds).to be_empty - end - end - end end -- cgit v1.2.1 From 80c22e4c09d7808d2a971c78a6232d4843c8c4f7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 25 Jul 2016 20:52:18 +0800 Subject: Add four features tests for download buttons in different places --- .../projects/branches/download_buttons_spec.rb | 37 +++++++++++++++++++++ .../projects/files/download_buttons_spec.rb | 38 ++++++++++++++++++++++ .../projects/main/download_buttons_spec.rb | 37 +++++++++++++++++++++ .../projects/tags/download_buttons_spec.rb | 38 ++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 spec/features/projects/branches/download_buttons_spec.rb create mode 100644 spec/features/projects/files/download_buttons_spec.rb create mode 100644 spec/features/projects/main/download_buttons_spec.rb create mode 100644 spec/features/projects/tags/download_buttons_spec.rb diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb new file mode 100644 index 00000000000..d3f53b65699 --- /dev/null +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +feature 'Download buttons in branches page', feature: true do + given(:user) { create(:user) } + given(:role) { :developer } + given(:status) { 'success' } + given(:project) { create(:project) } + given(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.sha, + ref: project.default_branch, + status: status) + end + given!(:build) do + create(:ci_build, :success, :artifacts, + pipeline: pipeline, + status: pipeline.status, + name: 'build') + end + + background do + login_as(user) + project.team << [user, role] + end + + describe 'when checking branches' do + context 'with artifacts' do + before do + visit namespace_project_branches_path(project.namespace, project) + end + + scenario 'shows download artifacts button' do + expect(page).to have_link "Download '#{build.name}'" + end + end + end +end diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb new file mode 100644 index 00000000000..60c2ffedce0 --- /dev/null +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +feature 'Download buttons in files tree', feature: true do + given(:user) { create(:user) } + given(:role) { :developer } + given(:status) { 'success' } + given(:project) { create(:project) } + given(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.sha, + ref: project.default_branch, + status: status) + end + given!(:build) do + create(:ci_build, :success, :artifacts, + pipeline: pipeline, + status: pipeline.status, + name: 'build') + end + + background do + login_as(user) + project.team << [user, role] + end + + describe 'when files tree' do + context 'with artifacts' do + before do + visit namespace_project_tree_path( + project.namespace, project, project.default_branch) + end + + scenario 'shows download artifacts button' do + expect(page).to have_link "Download '#{build.name}'" + end + end + end +end diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb new file mode 100644 index 00000000000..62e56808558 --- /dev/null +++ b/spec/features/projects/main/download_buttons_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +feature 'Download buttons in project main page', feature: true do + given(:user) { create(:user) } + given(:role) { :developer } + given(:status) { 'success' } + given(:project) { create(:project) } + given(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.sha, + ref: project.default_branch, + status: status) + end + given!(:build) do + create(:ci_build, :success, :artifacts, + pipeline: pipeline, + status: pipeline.status, + name: 'build') + end + + background do + login_as(user) + project.team << [user, role] + end + + describe 'when checking project main page' do + context 'with artifacts' do + before do + visit namespace_project_path(project.namespace, project) + end + + scenario 'shows download artifacts button' do + expect(page).to have_link "Download '#{build.name}'" + end + end + end +end diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb new file mode 100644 index 00000000000..d4c4cfe9c99 --- /dev/null +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +feature 'Download buttons in tags page', feature: true do + given(:user) { create(:user) } + given(:role) { :developer } + given(:status) { 'success' } + given(:tag) { 'v1.0.0' } + given(:project) { create(:project) } + given(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.sha, + ref: tag, + status: status) + end + given!(:build) do + create(:ci_build, :success, :artifacts, + pipeline: pipeline, + status: pipeline.status, + name: 'build') + end + + background do + login_as(user) + project.team << [user, role] + end + + describe 'when checking tags' do + context 'with artifacts' do + before do + visit namespace_project_tags_path(project.namespace, project) + end + + scenario 'shows download artifacts button' do + expect(page).to have_link "Download '#{build.name}'" + end + end + end +end -- cgit v1.2.1 From 2acb8a3085ad64ce56625a9d63f01f9398e757a3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 25 Jul 2016 14:09:23 +0300 Subject: Change merge request diff index to be not uniq Signed-off-by: Dmitriy Zaporozhets --- ...20160725104020_merge_request_diff_remove_uniq.rb | 21 +++++++++++++++++++++ .../20160725104452_merge_request_diff_add_index.rb | 17 +++++++++++++++++ db/schema.rb | 14 +++++++------- 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20160725104020_merge_request_diff_remove_uniq.rb create mode 100644 db/migrate/20160725104452_merge_request_diff_add_index.rb diff --git a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb new file mode 100644 index 00000000000..c8cbd2718ff --- /dev/null +++ b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb @@ -0,0 +1,21 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MergeRequestDiffRemoveUniq < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + if index_exists?(:merge_request_diffs, :merge_request_id) + remove_index :merge_request_diffs, :merge_request_id + end + end + + def down + unless index_exists?(:merge_request_diffs, :merge_request_id) + add_concurrent_index :merge_request_diffs, :merge_request_id, unique: true + end + end +end diff --git a/db/migrate/20160725104452_merge_request_diff_add_index.rb b/db/migrate/20160725104452_merge_request_diff_add_index.rb new file mode 100644 index 00000000000..6d04242dd25 --- /dev/null +++ b/db/migrate/20160725104452_merge_request_diff_add_index.rb @@ -0,0 +1,17 @@ +class MergeRequestDiffAddIndex < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + add_concurrent_index :merge_request_diffs, :merge_request_id + end + + def down + if index_exists?(:merge_request_diffs, :merge_request_id) + remove_index :merge_request_diffs, :merge_request_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 5b35a528e71..e3eecfebfd4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160726093600) do +ActiveRecord::Schema.define(version: 20160725104452) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -84,8 +84,8 @@ ActiveRecord::Schema.define(version: 20160726093600) do t.string "health_check_access_token" t.boolean "send_user_confirmation_email", default: false t.integer "container_registry_token_expire_delay", default: 5 - t.boolean "user_default_external", default: false, null: false t.text "after_sign_up_text" + t.boolean "user_default_external", default: false, null: false t.string "repository_storage", default: "default" t.string "enabled_git_access_protocol" t.boolean "domain_blacklist_enabled", default: false @@ -167,8 +167,8 @@ ActiveRecord::Schema.define(version: 20160726093600) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.string "environment" t.datetime "artifacts_expire_at" + t.string "environment" t.integer "artifacts_size" t.string "when" t.text "yaml_variables" @@ -605,7 +605,7 @@ ActiveRecord::Schema.define(version: 20160726093600) do t.string "start_commit_sha" end - add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree + add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree create_table "merge_requests", force: :cascade do |t| t.string "target_branch", null: false @@ -782,10 +782,10 @@ ActiveRecord::Schema.define(version: 20160726093600) do t.integer "user_id", null: false t.string "token", null: false t.string "name", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.boolean "revoked", default: false t.datetime "expires_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree @@ -847,8 +847,8 @@ ActiveRecord::Schema.define(version: 20160726093600) do t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" t.string "repository_storage", default: "default", null: false - t.boolean "has_external_wiki" t.boolean "request_access_enabled", default: true, null: false + t.boolean "has_external_wiki" end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree -- cgit v1.2.1 From 2afcc9e910bde32507e14be452c9f7681f5497e4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 25 Jul 2016 14:10:21 +0300 Subject: Create new version of merge request diff on push instead of updating existing one Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f1b9c1d6feb..ef0b7da048e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -10,8 +10,7 @@ class MergeRequest < ActiveRecord::Base belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" belongs_to :merge_user, class_name: "User" - has_one :merge_request_diff, dependent: :destroy - + has_many :merge_request_diffs, dependent: :destroy has_many :events, as: :target, dependent: :destroy serialize :merge_params, Hash @@ -294,7 +293,9 @@ class MergeRequest < ActiveRecord::Base end def reload_diff - return unless merge_request_diff && open? + return unless open? + + merge_request_diff = merge_request_diffs.create old_diff_refs = self.diff_refs @@ -691,4 +692,8 @@ class MergeRequest < ActiveRecord::Base def keep_around_commit project.repository.keep_around(self.merge_commit_sha) end + + def merge_request_diff + merge_request_diffs.first + end end -- cgit v1.2.1 From ceff8106433187613eb97d13952fc96c4806b847 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 25 Jul 2016 17:04:55 +0300 Subject: Create merge request diff on merge request creation Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ef0b7da048e..88c5987d485 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -286,6 +286,10 @@ class MergeRequest < ActiveRecord::Base end end + def create_merge_request_diff + merge_request_diffs.create + end + def update_merge_request_diff if source_branch_changed? || target_branch_changed? reload_diff -- cgit v1.2.1 From 94ca25c9b8c62f9995fbd571c33954754950e1da Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 25 Jul 2016 18:42:57 +0300 Subject: Improve merge request diff creation and update tests Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 6 +----- spec/models/merge_request_spec.rb | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 88c5987d485..cc1d0c18437 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -299,12 +299,8 @@ class MergeRequest < ActiveRecord::Base def reload_diff return unless open? - merge_request_diff = merge_request_diffs.create - old_diff_refs = self.diff_refs - - merge_request_diff.reload_content - + create_merge_request_diff new_diff_refs = self.diff_refs update_diff_notes_positions( diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index a0e3c26e542..03e4ae574ef 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -9,7 +9,7 @@ describe MergeRequest, models: true do it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } it { is_expected.to belong_to(:merge_user).class_name("User") } - it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } + it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) } end describe 'modules' do @@ -134,7 +134,7 @@ describe MergeRequest, models: true do context 'when there are MR diffs' do it 'delegates to the MR diffs' do - merge_request.merge_request_diff = MergeRequestDiff.new + merge_request.merge_request_diffs.build expect(merge_request.merge_request_diff).to receive(:diffs).with(options) @@ -654,22 +654,26 @@ describe MergeRequest, models: true do let(:commit) { subject.project.commit(sample_commit.id) } - it "reloads the diff content" do - expect(subject.merge_request_diff).to receive(:reload_content) - + it "does not change existing merge request diff" do + expect(subject.merge_request_diff).not_to receive(:reload_content) subject.reload_diff end + it "creates new merge request diff" do + expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1) + end + it "updates diff note positions" do old_diff_refs = subject.diff_refs - merge_request_diff = subject.merge_request_diff - # Update merge_request_diff so that #diff_refs will return commit.diff_refs - allow(merge_request_diff).to receive(:reload_content) do - merge_request_diff.base_commit_sha = commit.parent_id - merge_request_diff.start_commit_sha = commit.parent_id - merge_request_diff.head_commit_sha = commit.sha + allow(subject).to receive(:create_merge_request_diff) do + subject.merge_request_diffs.create( + importing: true, + base_commit_sha: commit.parent_id, + start_commit_sha: commit.parent_id, + head_commit_sha: commit.sha + ) end expect(Notes::DiffPositionUpdateService).to receive(:new).with( @@ -679,8 +683,8 @@ describe MergeRequest, models: true do new_diff_refs: commit.diff_refs, paths: note.position.paths ).and_call_original - expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note) + expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note) expect_any_instance_of(DiffNote).to receive(:save).once subject.reload_diff -- cgit v1.2.1 From b8fef7eb5948344f4d442a52637cad168f4c5bf1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 26 Jul 2016 18:24:25 +0300 Subject: Add ability to render different merge request versions Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/merge_requests_controller.rb | 8 +++++++- app/models/merge_request_diff.rb | 10 ++++++++++ .../projects/merge_requests/show/_diffs.html.haml | 5 +++-- .../projects/merge_requests/show/_versions.html.haml | 18 ++++++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 app/views/projects/merge_requests/show/_versions.html.haml diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 03166294ddd..a886af713d5 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -81,7 +81,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController def diffs apply_diff_view_cookie! - @merge_request_diff = @merge_request.merge_request_diff + @merge_request_diff = + if params[:diff_id] + @merge_request.merge_request_diffs.find(params[:diff_id]) + else + @merge_request.merge_request_diff + end + respond_to do |format| format.html { define_discussion_vars } diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 3f520c8f3ff..a92f597225a 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -96,6 +96,16 @@ class MergeRequestDiff < ActiveRecord::Base end end + def diff_refs + return unless start_commit || base_commit + + Gitlab::Diff::DiffRefs.new( + base_sha: base_commit_sha, + start_sha: start_commit_sha, + head_sha: head_commit_sha + ) + end + private # Collect array of Git::Commit objects diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 1b0bae86ad4..e26499cc144 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,6 +1,7 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diffs: @merge_request.diffs(diff_options), - project: @merge_request.project, diff_refs: @merge_request.diff_refs + = render 'projects/merge_requests/show/versions' + = render "projects/diffs/diffs", diffs: @merge_request_diff.diffs(diff_options), + project: @merge_request.project, diff_refs: @merge_request_diff.diff_refs - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml new file mode 100644 index 00000000000..2764786d9bf --- /dev/null +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -0,0 +1,18 @@ +- if @merge_request.merge_request_diffs.size > 1 + - latest_diff = @merge_request.merge_request_diff + .light-well + Merge Request version + + %span.dropdown.inline + %a.dropdown-toggle{ data: {toggle: :dropdown} } + %strong.monospace #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} + %span.caret + %ul.dropdown-menu + - @merge_request.merge_request_diffs.each do |merge_request_diff| + %li{ class: ('active' if merge_request_diff == latest_diff) } + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id) do + %strong.monospace + #{merge_request_diff.base_commit.short_id}..#{merge_request_diff.head_commit.short_id} + %br + %small + = time_ago_with_tooltip(merge_request_diff.created_at) -- cgit v1.2.1 From dbd58dc4bb638be33f98336410ae6f4ede15f737 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 26 Jul 2016 20:03:45 +0300 Subject: Improve merge request version switcher Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/dropdowns.scss | 6 ++++++ app/assets/stylesheets/pages/merge_requests.scss | 6 ++++++ .../merge_requests/show/_versions.html.haml | 24 ++++++++++++++-------- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index c54eb0d6479..28346423541 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -17,6 +17,12 @@ .dropdown { position: relative; + + .btn-link { + &:hover { + cursor: pointer; + } + } } .open { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 0a661e529f0..14ded0eec72 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -371,3 +371,9 @@ } } } + +.mr-version-switch { + background: $background-color; + padding: $gl-btn-padding; + border-bottom: 1px solid $border-color; +} diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 2764786d9bf..5c0098bcf98 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,18 +1,24 @@ -- if @merge_request.merge_request_diffs.size > 1 - - latest_diff = @merge_request.merge_request_diff - .light-well - Merge Request version +- diffs_count = @merge_request.merge_request_diffs.count +- if diffs_count > 1 + - latest_diff = @merge_request.merge_request_diff + .mr-version-switch + Version:  %span.dropdown.inline - %a.dropdown-toggle{ data: {toggle: :dropdown} } - %strong.monospace #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} + %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } + %strong.monospace< + - if latest_diff == @merge_request_diff + #{"latest"} + - else + #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} %span.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-selectable - @merge_request.merge_request_diffs.each do |merge_request_diff| - %li{ class: ('active' if merge_request_diff == latest_diff) } - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id) do + %li + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do %strong.monospace #{merge_request_diff.base_commit.short_id}..#{merge_request_diff.head_commit.short_id} %br %small + = time_ago_with_tooltip(merge_request_diff.created_at) -- cgit v1.2.1 From 6190216541adb04fd0ce4b21862e6b253b868671 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 27 Jul 2016 11:27:06 +0300 Subject: Add small visual improvements to merge request version switch Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 6 +++++- app/views/projects/merge_requests/show/_versions.html.haml | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 14ded0eec72..7d8f58ca5af 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -375,5 +375,9 @@ .mr-version-switch { background: $background-color; padding: $gl-btn-padding; - border-bottom: 1px solid $border-color; + color: $gl-placeholder-color; + + a.btn-link { + color: $gl-dark-link-color; + } } diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 5c0098bcf98..8b7d866941c 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -10,15 +10,18 @@ - if latest_diff == @merge_request_diff #{"latest"} - else - #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} + #{@merge_request_diff.head_commit.short_id} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - @merge_request.merge_request_diffs.each do |merge_request_diff| %li = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do %strong.monospace - #{merge_request_diff.base_commit.short_id}..#{merge_request_diff.head_commit.short_id} + #{merge_request_diff.head_commit.short_id} %br %small - = time_ago_with_tooltip(merge_request_diff.created_at) + + .pull-right + %span.monospace + git diff #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} -- cgit v1.2.1 From 09fa0139281d7a76d6b40991f7672187fea40e67 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 27 Jul 2016 14:41:19 +0300 Subject: Refactor merge request diff Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 89 +++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index a92f597225a..07bceda049c 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -8,7 +8,8 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request - delegate :source_branch_sha, :target_branch_sha, :target_branch, :source_branch, to: :merge_request, prefix: nil + delegate :source_branch_sha, :target_branch_sha, + :target_branch, :source_branch, to: :merge_request, prefix: nil state_machine :state, initial: :empty do state :collected @@ -24,9 +25,16 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_commits serialize :st_diffs + after_initialize :set_diff_range after_create :reload_content, unless: :importing? after_save :keep_around_commits, unless: :importing? + def set_diff_range + self.start_commit_sha ||= target_branch_sha + self.head_commit_sha ||= source_branch_sha + self.base_commit_sha ||= branch_base_sha + end + def reload_content reload_commits reload_diffs @@ -41,8 +49,8 @@ class MergeRequestDiff < ActiveRecord::Base @diffs_no_whitespace ||= begin compare = Gitlab::Git::Compare.new( repository.raw_repository, - self.start_commit_sha || self.target_branch_sha, - self.head_commit_sha || self.source_branch_sha, + start_commit_sha, + head_commit_sha ) compare.diffs(options) end @@ -65,35 +73,21 @@ class MergeRequestDiff < ActiveRecord::Base end def base_commit - return unless self.base_commit_sha + return unless base_commit_sha - project.commit(self.base_commit_sha) + project.commit(base_commit_sha) end def start_commit - return unless self.start_commit_sha + return unless start_commit_sha - project.commit(self.start_commit_sha) + project.commit(start_commit_sha) end def head_commit - return last_commit unless self.head_commit_sha + return last_commit unless head_commit_sha - project.commit(self.head_commit_sha) - end - - def compare - @compare ||= - begin - # Update ref for merge request - merge_request.fetch_ref - - Gitlab::Git::Compare.new( - repository.raw_repository, - self.target_branch_sha, - self.source_branch_sha - ) - end + project.commit(head_commit_sha) end def diff_refs @@ -108,16 +102,18 @@ class MergeRequestDiff < ActiveRecord::Base private - # Collect array of Git::Commit objects - # between target and source branches - def unmerged_commits - commits = compare.commits - - if commits.present? - commits = Commit.decorate(commits, merge_request.source_project).reverse - end + def compare + @compare ||= + begin + # Update ref for merge request + merge_request.fetch_ref - commits + Gitlab::Git::Compare.new( + repository.raw_repository, + start_commit_sha, + head_commit_sha + ) + end end def dump_commits(commits) @@ -133,21 +129,16 @@ class MergeRequestDiff < ActiveRecord::Base def reload_commits new_attributes = {} - commit_objects = unmerged_commits + commits = compare.commits - if commit_objects.present? - new_attributes[:st_commits] = dump_commits(commit_objects) + if commits.present? + commits = Commit.decorate(commits, merge_request.source_project).reverse + new_attributes[:st_commits] = dump_commits(commits) end update_columns_serialized(new_attributes) end - # Collect array of Git::Diff objects - # between target and source branches - def unmerged_diffs - compare.diffs(Commit.max_diff_options) - end - def dump_diffs(diffs) if diffs.respond_to?(:map) diffs.map(&:to_hash) @@ -177,7 +168,7 @@ class MergeRequestDiff < ActiveRecord::Base if commits.size.zero? new_attributes[:state] = :empty else - diff_collection = unmerged_diffs + diff_collection = compare.diffs(Commit.max_diff_options) if diff_collection.overflow? # Set our state to 'overflow' to make the #empty? and #collected? @@ -195,10 +186,6 @@ class MergeRequestDiff < ActiveRecord::Base new_attributes[:st_diffs] = new_diffs - new_attributes[:start_commit_sha] = self.target_branch_sha - new_attributes[:head_commit_sha] = self.source_branch_sha - new_attributes[:base_commit_sha] = branch_base_sha - update_columns_serialized(new_attributes) keep_around_commits @@ -213,9 +200,9 @@ class MergeRequestDiff < ActiveRecord::Base end def branch_base_commit - return unless self.source_branch_sha && self.target_branch_sha + return unless source_branch_sha && target_branch_sha - project.merge_base_commit(self.source_branch_sha, self.target_branch_sha) + project.merge_base_commit(source_branch_sha, target_branch_sha) end def branch_base_sha @@ -254,8 +241,8 @@ class MergeRequestDiff < ActiveRecord::Base end def keep_around_commits - repository.keep_around(target_branch_sha) - repository.keep_around(source_branch_sha) - repository.keep_around(branch_base_sha) + repository.keep_around(start_commit_sha) + repository.keep_around(head_commit_sha) + repository.keep_around(base_commit_sha) end end -- cgit v1.2.1 From 5d5f2cf3ef7bc238f75d9a73ce94efceec6f690e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 27 Jul 2016 17:55:12 +0300 Subject: Add feature test for merge request version Signed-off-by: Dmitriy Zaporozhets --- .../merge_requests/merge_request_versions_spec.rb | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 spec/features/merge_requests/merge_request_versions_spec.rb diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb new file mode 100644 index 00000000000..862ead4e72e --- /dev/null +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +feature 'Merge Request versions', js: true, feature: true do + before do + login_as :admin + merge_request = create(:merge_request, importing: true) + merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') + project = merge_request.source_project + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'show the latest version of the diff' do + page.within '.mr-version-switch' do + expect(page).to have_content 'Version: latest' + end + + expect(page).to have_content '8 changed files' + end + + describe 'switch between versions' do + before do + page.within '.mr-version-switch' do + find('.btn-link').click + #find('a', text: '6f6d7e7e').click + click_link '6f6d7e7e' + end + end + + it 'should show older version' do + page.within '.mr-version-switch' do + expect(page).to have_content 'Version: 6f6d7e7e' + end + + expect(page).to have_content '5 changed files' + end + end +end -- cgit v1.2.1 From 988836bc807a6e7ba5362fd8bddd330d1f3bf19b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Jul 2016 11:36:30 +0300 Subject: Refactor MergeRequestDiff model Since MergeRequestDiff is not about branches and current state of merge request diff anymore I removed most of branch related method and added validation for head/start/base commit sha. From this point MergeRequestDiff is about saving diff between branches only once at moment of creation. Once created MergeRequestDiff should not be changes. Because of that we should not rely on changes in source/target branches when read from MergeRequestDiff Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 07bceda049c..1e529c84706 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -8,9 +8,6 @@ class MergeRequestDiff < ActiveRecord::Base belongs_to :merge_request - delegate :source_branch_sha, :target_branch_sha, - :target_branch, :source_branch, to: :merge_request, prefix: nil - state_machine :state, initial: :empty do state :collected state :overflow @@ -25,14 +22,23 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_commits serialize :st_diffs - after_initialize :set_diff_range - after_create :reload_content, unless: :importing? + validates :head_commit_sha, presence: true + validates :start_commit_sha, presence: true + validates :base_commit_sha, presence: true + + after_initialize :ensure_head_commit_sha, if: :persisted? + before_create :set_diff_range, unless: :importing? + after_create :reload_content, unless: :importing? after_save :keep_around_commits, unless: :importing? + def ensure_head_commit_sha + self.head_commit_sha ||= last_commit.sha + end + def set_diff_range - self.start_commit_sha ||= target_branch_sha - self.head_commit_sha ||= source_branch_sha - self.base_commit_sha ||= branch_base_sha + self.start_commit_sha ||= merge_request.target_branch_sha + self.head_commit_sha ||= merge_request.source_branch_sha + self.base_commit_sha ||= find_base_sha end def reload_content @@ -199,14 +205,10 @@ class MergeRequestDiff < ActiveRecord::Base project.repository end - def branch_base_commit - return unless source_branch_sha && target_branch_sha - - project.merge_base_commit(source_branch_sha, target_branch_sha) - end + def find_base_sha + return unless head_commit_sha && start_commit_sha - def branch_base_sha - branch_base_commit.try(:sha) + project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha) end def utf8_st_diffs -- cgit v1.2.1 From 964742f600f5fc5e07272982c4d3847ccc76ce00 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Jul 2016 13:46:27 +0300 Subject: Ensure merge request is created with valid diff object * Add merge_request_diff validation to merge_request model * Improve initialize of merge_request_diff object * Rename some merge_request_diff methods for clarity Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 7 ++++++- app/models/merge_request_diff.rb | 41 +++++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index cc1d0c18437..618829891a0 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -15,7 +15,7 @@ class MergeRequest < ActiveRecord::Base serialize :merge_params, Hash - after_create :create_merge_request_diff, unless: :importing? + before_validation :ensure_merge_request_diff, on: :create, unless: :importing? after_update :update_merge_request_diff delegate :commits, :real_size, to: :merge_request_diff, prefix: nil @@ -95,6 +95,7 @@ class MergeRequest < ActiveRecord::Base validates :merge_user, presence: true, if: :merge_when_build_succeeds? validate :validate_branches, unless: [:allow_broken, :importing?] validate :validate_fork + validates_associated :merge_request_diff, on: :create, unless: [:allow_broken, :importing?] scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } @@ -286,6 +287,10 @@ class MergeRequest < ActiveRecord::Base end end + def ensure_merge_request_diff + merge_request_diff || merge_request_diffs.build + end + def create_merge_request_diff merge_request_diffs.create end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 1e529c84706..da1e317027d 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -22,28 +22,31 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_commits serialize :st_diffs - validates :head_commit_sha, presence: true validates :start_commit_sha, presence: true + validates :head_commit_sha, presence: true validates :base_commit_sha, presence: true - after_initialize :ensure_head_commit_sha, if: :persisted? - before_create :set_diff_range, unless: :importing? - after_create :reload_content, unless: :importing? - after_save :keep_around_commits, unless: :importing? - - def ensure_head_commit_sha - self.head_commit_sha ||= last_commit.sha - end + after_initialize :set_diff_range, unless: :importing? + after_create :save_git_content, unless: :importing? + after_save :keep_around_commits, unless: :importing? def set_diff_range - self.start_commit_sha ||= merge_request.target_branch_sha - self.head_commit_sha ||= merge_request.source_branch_sha - self.base_commit_sha ||= find_base_sha + if persisted? + # Workaround for old MergeRequestDiff object + # that does not have head_commit_sha in the database + self.head_commit_sha ||= last_commit.sha + else + self.start_commit_sha ||= merge_request.target_branch_sha + self.head_commit_sha ||= merge_request.source_branch_sha + self.base_commit_sha ||= find_base_sha + end end - def reload_content - reload_commits - reload_diffs + # Collect information about commits and diff from repository + # and save it to the database as serialized data + def save_git_content + save_commits + save_diffs end def size @@ -130,9 +133,9 @@ class MergeRequestDiff < ActiveRecord::Base array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } end - # Reload all commits related to current merge request from repo + # Load all commits related to current merge request diff from repo # and save it as array of hashes in st_commits db field - def reload_commits + def save_commits new_attributes = {} commits = compare.commits @@ -165,9 +168,9 @@ class MergeRequestDiff < ActiveRecord::Base end end - # Reload diffs between branches related to current merge request from repo + # Load diffs between branches related to current merge request diff from repo # and save it as array of hashes in st_diffs db field - def reload_diffs + def save_diffs new_attributes = {} new_diffs = [] -- cgit v1.2.1 From 5cad2d290219d29aa2be6e991b42b73ac9d87754 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Jul 2016 17:26:54 +0300 Subject: Add improvements to merge request versions * show commits count in the merge request version dropdown * initialize base/start commit sha for old merge request diffs from repo Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 4 +++- app/views/projects/merge_requests/show/_versions.html.haml | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index da1e317027d..2bc0b2636f4 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -34,7 +34,9 @@ class MergeRequestDiff < ActiveRecord::Base if persisted? # Workaround for old MergeRequestDiff object # that does not have head_commit_sha in the database - self.head_commit_sha ||= last_commit.sha + self.start_commit_sha ||= merge_request.target_branch_sha + self.head_commit_sha ||= last_commit.sha + self.base_commit_sha ||= find_base_sha else self.start_commit_sha ||= merge_request.target_branch_sha self.head_commit_sha ||= merge_request.source_branch_sha diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 8b7d866941c..93a06154022 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -20,8 +20,9 @@ #{merge_request_diff.head_commit.short_id} %br %small + #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, = time_ago_with_tooltip(merge_request_diff.created_at) .pull-right %span.monospace - git diff #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} + #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} -- cgit v1.2.1 From 8e031ce3b29b186858ddda2a28a6ea8f98b2f749 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Jul 2016 17:41:35 +0300 Subject: Remove requirement for base_commit_sha to allow creation of merge requests for orphaned branches Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 2bc0b2636f4..da0c15ddeea 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -22,9 +22,8 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_commits serialize :st_diffs - validates :start_commit_sha, presence: true - validates :head_commit_sha, presence: true - validates :base_commit_sha, presence: true + validates :start_commit_sha, presence: true, unless: :importing? + validates :head_commit_sha, presence: true, unless: :importing? after_initialize :set_diff_range, unless: :importing? after_create :save_git_content, unless: :importing? -- cgit v1.2.1 From b0a023842d4e9fc29db7fa6c7168528d9e4ac09d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Jul 2016 18:41:57 +0300 Subject: Reload merge request association reload when update code Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 9 ++------- spec/models/merge_request_spec.rb | 5 ++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 618829891a0..08523e45c65 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -246,13 +246,7 @@ class MergeRequest < ActiveRecord::Base end def diff_refs - return unless diff_start_commit || diff_base_commit - - Gitlab::Diff::DiffRefs.new( - base_sha: diff_base_sha, - start_sha: diff_start_sha, - head_sha: diff_head_sha - ) + merge_request_diff.diff_refs end def validate_branches @@ -306,6 +300,7 @@ class MergeRequest < ActiveRecord::Base old_diff_refs = self.diff_refs create_merge_request_diff + merge_request_diffs.reload new_diff_refs = self.diff_refs update_diff_notes_positions( diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 03e4ae574ef..f095e23f7ac 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -291,7 +291,7 @@ describe MergeRequest, models: true do end it "can be removed if the last commit is the head of the source branch" do - allow(subject.source_project).to receive(:commit).and_return(subject.diff_head_commit) + allow(subject).to receive(:source_branch_head).and_return(subject.diff_head_commit) expect(subject.can_remove_source_branch?(user)).to be_truthy end @@ -655,7 +655,7 @@ describe MergeRequest, models: true do let(:commit) { subject.project.commit(sample_commit.id) } it "does not change existing merge request diff" do - expect(subject.merge_request_diff).not_to receive(:reload_content) + expect(subject.merge_request_diff).not_to receive(:save_git_content) subject.reload_diff end @@ -669,7 +669,6 @@ describe MergeRequest, models: true do # Update merge_request_diff so that #diff_refs will return commit.diff_refs allow(subject).to receive(:create_merge_request_diff) do subject.merge_request_diffs.create( - importing: true, base_commit_sha: commit.parent_id, start_commit_sha: commit.parent_id, head_commit_sha: commit.sha -- cgit v1.2.1 From 0e974b52d8f5806c24fdc80ef5b62b261f5be2ba Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Jul 2016 22:36:18 +0300 Subject: Refactor MergeRequestDiff initialize method Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 28 ++++++++++------------ .../merge_requests/merge_request_versions_spec.rb | 1 - 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index da0c15ddeea..940a1016302 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -25,22 +25,18 @@ class MergeRequestDiff < ActiveRecord::Base validates :start_commit_sha, presence: true, unless: :importing? validates :head_commit_sha, presence: true, unless: :importing? - after_initialize :set_diff_range, unless: :importing? - after_create :save_git_content, unless: :importing? - after_save :keep_around_commits, unless: :importing? - - def set_diff_range - if persisted? - # Workaround for old MergeRequestDiff object - # that does not have head_commit_sha in the database - self.start_commit_sha ||= merge_request.target_branch_sha - self.head_commit_sha ||= last_commit.sha - self.base_commit_sha ||= find_base_sha - else - self.start_commit_sha ||= merge_request.target_branch_sha - self.head_commit_sha ||= merge_request.source_branch_sha - self.base_commit_sha ||= find_base_sha - end + after_initialize :initialize_commits_sha, unless: :importing? + after_create :save_git_content, unless: :importing? + after_save :keep_around_commits, unless: :importing? + + # Those variables are used for collecting commits and diff from git repository. + # After object is created those sha are stored in the database. + # However some old MergeRequestDiff records don't + # have those variables in the database so we try to initialize it + def initialize_commits_sha + self.start_commit_sha ||= merge_request.target_branch_sha + self.head_commit_sha ||= last_commit.try(:sha) || merge_request.source_branch_sha + self.base_commit_sha ||= find_base_sha end # Collect information about commits and diff from repository diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index 862ead4e72e..577c910f11b 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -22,7 +22,6 @@ feature 'Merge Request versions', js: true, feature: true do before do page.within '.mr-version-switch' do find('.btn-link').click - #find('a', text: '6f6d7e7e').click click_link '6f6d7e7e' end end -- cgit v1.2.1 From 3c1dca0301366c63d1800aa11e73a82e68e120d0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 Jul 2016 23:40:13 +0300 Subject: Add more tests to merge_request_diff and improve initialize Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 4 +++- app/models/merge_request_diff.rb | 4 +--- spec/models/merge_request_diff_spec.rb | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 08523e45c65..6d36a5ba288 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -246,7 +246,9 @@ class MergeRequest < ActiveRecord::Base end def diff_refs - merge_request_diff.diff_refs + if merge_request_diff + merge_request_diff.diff_refs + end end def validate_branches diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 940a1016302..b668b62cdac 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -35,7 +35,7 @@ class MergeRequestDiff < ActiveRecord::Base # have those variables in the database so we try to initialize it def initialize_commits_sha self.start_commit_sha ||= merge_request.target_branch_sha - self.head_commit_sha ||= last_commit.try(:sha) || merge_request.source_branch_sha + self.head_commit_sha ||= persisted? ? last_commit.sha : merge_request.source_branch_sha self.base_commit_sha ||= find_base_sha end @@ -191,9 +191,7 @@ class MergeRequestDiff < ActiveRecord::Base end new_attributes[:st_diffs] = new_diffs - update_columns_serialized(new_attributes) - keep_around_commits end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 9a637c94fbe..16bba82181b 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -1,6 +1,23 @@ require 'spec_helper' describe MergeRequestDiff, models: true do + describe 'initialize new object' do + subject { build(:merge_request).merge_request_diffs.build } + + it { expect(subject).to be_valid } + it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } + it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } + end + + describe 'create new record' do + subject { create(:merge_request) } + + it { expect(subject).to be_valid } + it { expect(subject.commits.count).to eq(5) } + it { expect(subject.diffs.count).to eq(8) } + end + describe '#diffs' do let(:mr) { create(:merge_request, :with_diffs) } let(:mr_diff) { mr.merge_request_diff } -- cgit v1.2.1 From f8aeb8cdac98108bca5d1be2a382c32df6a500e5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Aug 2016 18:41:21 +0300 Subject: Change merge request diff creation from callback to part of the service Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/merge_requests_controller.rb | 1 - app/models/merge_request.rb | 5 ----- app/services/merge_requests/create_service.rb | 1 + spec/factories/merge_requests.rb | 6 ++++++ spec/models/merge_request_diff_spec.rb | 3 ++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a886af713d5..33188e88385 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -88,7 +88,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.merge_request_diff end - respond_to do |format| format.html { define_discussion_vars } format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6d36a5ba288..85048f928dc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -15,7 +15,6 @@ class MergeRequest < ActiveRecord::Base serialize :merge_params, Hash - before_validation :ensure_merge_request_diff, on: :create, unless: :importing? after_update :update_merge_request_diff delegate :commits, :real_size, to: :merge_request_diff, prefix: nil @@ -283,10 +282,6 @@ class MergeRequest < ActiveRecord::Base end end - def ensure_merge_request_diff - merge_request_diff || merge_request_diffs.build - end - def create_merge_request_diff merge_request_diffs.create end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 96a25330af1..553d7443c86 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -16,6 +16,7 @@ module MergeRequests merge_request.target_project ||= source_project merge_request.author = current_user merge_request.merge_params['force_remove_source_branch'] = force_remove_source_branch + merge_request.merge_request_diffs.build if merge_request.save merge_request.update_attributes(label_ids: label_params) diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index c6a08d78b78..05851c49497 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -68,5 +68,11 @@ FactoryGirl.define do factory :closed_merge_request, traits: [:closed] factory :reopened_merge_request, traits: [:reopened] factory :merge_request_with_diffs, traits: [:with_diffs] + + after :create do |merge_request| + unless merge_request.merge_request_diff + merge_request.create_merge_request_diff + end + end end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 16bba82181b..0a55515e8c6 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -11,9 +11,10 @@ describe MergeRequestDiff, models: true do end describe 'create new record' do - subject { create(:merge_request) } + subject { create(:merge_request).merge_request_diff } it { expect(subject).to be_valid } + it { expect(subject).to be_persisted } it { expect(subject.commits.count).to eq(5) } it { expect(subject.diffs.count).to eq(8) } end -- cgit v1.2.1 From 6515ec09bbfa25027d1b8a1240e09bc1c6edbcfb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Aug 2016 15:38:03 +0300 Subject: Chnage the way how merge request diff is initialized and saved Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 15 +++----- app/models/merge_request_diff.rb | 54 +++++++++++++++------------ app/services/merge_requests/create_service.rb | 1 - features/project/merge_requests.feature | 2 +- features/steps/project/merge_requests.rb | 6 +-- spec/factories/merge_requests.rb | 6 --- spec/models/merge_request_diff_spec.rb | 12 ++---- 7 files changed, 44 insertions(+), 52 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 85048f928dc..6f2216ab5db 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -11,11 +11,13 @@ class MergeRequest < ActiveRecord::Base belongs_to :merge_user, class_name: "User" has_many :merge_request_diffs, dependent: :destroy + has_one :merge_request_diff has_many :events, as: :target, dependent: :destroy serialize :merge_params, Hash - after_update :update_merge_request_diff + after_create :ensure_merge_request_diff, unless: :importing? + after_update :reload_diff_if_branch_changed delegate :commits, :real_size, to: :merge_request_diff, prefix: nil @@ -94,7 +96,6 @@ class MergeRequest < ActiveRecord::Base validates :merge_user, presence: true, if: :merge_when_build_succeeds? validate :validate_branches, unless: [:allow_broken, :importing?] validate :validate_fork - validates_associated :merge_request_diff, on: :create, unless: [:allow_broken, :importing?] scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } @@ -282,11 +283,11 @@ class MergeRequest < ActiveRecord::Base end end - def create_merge_request_diff - merge_request_diffs.create + def ensure_merge_request_diff + merge_request_diff || create_merge_request_diff end - def update_merge_request_diff + def reload_diff_if_branch_changed if source_branch_changed? || target_branch_changed? reload_diff end @@ -689,8 +690,4 @@ class MergeRequest < ActiveRecord::Base def keep_around_commit project.repository.keep_around(self.merge_commit_sha) end - - def merge_request_diff - merge_request_diffs.first - end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index b668b62cdac..074d8f5d40a 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -22,28 +22,28 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_commits serialize :st_diffs - validates :start_commit_sha, presence: true, unless: :importing? - validates :head_commit_sha, presence: true, unless: :importing? + # For compatibility with old MergeRequestDiff which + # does not store those variables in database + after_initialize :ensure_commits_sha, if: :persisted? - after_initialize :initialize_commits_sha, unless: :importing? + # All diff information is collected from repository after object is created. + # It allows you to override variables like head_commit_sha before getting diff. after_create :save_git_content, unless: :importing? - after_save :keep_around_commits, unless: :importing? - - # Those variables are used for collecting commits and diff from git repository. - # After object is created those sha are stored in the database. - # However some old MergeRequestDiff records don't - # have those variables in the database so we try to initialize it - def initialize_commits_sha - self.start_commit_sha ||= merge_request.target_branch_sha - self.head_commit_sha ||= persisted? ? last_commit.sha : merge_request.source_branch_sha - self.base_commit_sha ||= find_base_sha - end # Collect information about commits and diff from repository # and save it to the database as serialized data def save_git_content + ensure_commits_sha save_commits + reload_commits save_diffs + keep_around_commits + end + + def ensure_commits_sha + self.start_commit_sha ||= merge_request.target_branch_sha + self.head_commit_sha ||= last_commit.try(:sha) || merge_request.source_branch_sha + self.base_commit_sha ||= find_base_sha end def size @@ -52,14 +52,15 @@ class MergeRequestDiff < ActiveRecord::Base def diffs(options={}) if options[:ignore_whitespace_change] - @diffs_no_whitespace ||= begin - compare = Gitlab::Git::Compare.new( - repository.raw_repository, - start_commit_sha, - head_commit_sha - ) - compare.diffs(options) - end + @diffs_no_whitespace ||= + begin + compare = Gitlab::Git::Compare.new( + repository.raw_repository, + start_commit_sha, + head_commit_sha + ) + compare.diffs(options) + end else @diffs ||= {} @diffs[options] ||= load_diffs(st_diffs, options) @@ -70,6 +71,11 @@ class MergeRequestDiff < ActiveRecord::Base @commits ||= load_commits(st_commits || []) end + def reload_commits + @commits = nil + commits + end + def last_commit commits.first end @@ -192,7 +198,6 @@ class MergeRequestDiff < ActiveRecord::Base new_attributes[:st_diffs] = new_diffs update_columns_serialized(new_attributes) - keep_around_commits end def project @@ -207,6 +212,9 @@ class MergeRequestDiff < ActiveRecord::Base return unless head_commit_sha && start_commit_sha project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha) + rescue Rugged::OdbError + # In case head or start commit does not exist in the repository any more + nil end def utf8_st_diffs diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 553d7443c86..96a25330af1 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -16,7 +16,6 @@ module MergeRequests merge_request.target_project ||= source_project merge_request.author = current_user merge_request.merge_params['force_remove_source_branch'] = force_remove_source_branch - merge_request.merge_request_diffs.build if merge_request.save merge_request.update_attributes(label_ids: label_params) diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 21768c15c17..7673b4284ef 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -24,7 +24,7 @@ Feature: Project Merge Requests Scenario: I should see target branch when it is different from default Given project "Shop" have "Bug NS-06" open merge request When I visit project "Shop" merge requests page - Then I should see "other_branch" branch + Then I should see "feature_conflict" branch Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target Given project "Shop" have "Bug NS-07" open merge request with rebased branch diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index da848afd48e..74e162aadee 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -56,8 +56,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps expect(find('.merge-request-info')).not_to have_content "master" end - step 'I should see "other_branch" branch' do - expect(page).to have_content "other_branch" + step 'I should see "feature_conflict" branch' do + expect(page).to have_content "feature_conflict" end step 'I should see "Bug NS-04" in merge requests' do @@ -122,7 +122,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps source_project: project, target_project: project, source_branch: 'fix', - target_branch: 'other_branch', + target_branch: 'feature_conflict', author: project.users.first, description: "# Description header" ) diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 05851c49497..c6a08d78b78 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -68,11 +68,5 @@ FactoryGirl.define do factory :closed_merge_request, traits: [:closed] factory :reopened_merge_request, traits: [:reopened] factory :merge_request_with_diffs, traits: [:with_diffs] - - after :create do |merge_request| - unless merge_request.merge_request_diff - merge_request.create_merge_request_diff - end - end end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 0a55515e8c6..f2954a39b08 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -1,15 +1,6 @@ require 'spec_helper' describe MergeRequestDiff, models: true do - describe 'initialize new object' do - subject { build(:merge_request).merge_request_diffs.build } - - it { expect(subject).to be_valid } - it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } - it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } - it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } - end - describe 'create new record' do subject { create(:merge_request).merge_request_diff } @@ -17,6 +8,9 @@ describe MergeRequestDiff, models: true do it { expect(subject).to be_persisted } it { expect(subject.commits.count).to eq(5) } it { expect(subject.diffs.count).to eq(8) } + it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } + it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } end describe '#diffs' do -- cgit v1.2.1 From 7cb8ceb5b9b7da6f9bd5f80a50eeca6727ac5902 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Aug 2016 16:28:37 +0300 Subject: Fix creation and reload of the merge request diff Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6f2216ab5db..8b0b413343c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -11,7 +11,9 @@ class MergeRequest < ActiveRecord::Base belongs_to :merge_user, class_name: "User" has_many :merge_request_diffs, dependent: :destroy - has_one :merge_request_diff + has_one :merge_request_diff, + -> { order('merge_request_diffs.id DESC') } + has_many :events, as: :target, dependent: :destroy serialize :merge_params, Hash @@ -287,6 +289,15 @@ class MergeRequest < ActiveRecord::Base merge_request_diff || create_merge_request_diff end + def create_merge_request_diff + merge_request_diffs.create + reload_merge_request_diff + end + + def reload_merge_request_diff + merge_request_diff(true) + end + def reload_diff_if_branch_changed if source_branch_changed? || target_branch_changed? reload_diff @@ -298,7 +309,6 @@ class MergeRequest < ActiveRecord::Base old_diff_refs = self.diff_refs create_merge_request_diff - merge_request_diffs.reload new_diff_refs = self.diff_refs update_diff_notes_positions( -- cgit v1.2.1 From fc8d2fbc946d144d2a9d075d67a9f1fe1035da0e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Aug 2016 17:52:20 +0300 Subject: Build diff_refs for merge request if merge request diff does not exist Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 8b0b413343c..56d5157298f 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -250,6 +250,12 @@ class MergeRequest < ActiveRecord::Base def diff_refs if merge_request_diff merge_request_diff.diff_refs + elsif diff_start_commit || diff_base_commit + Gitlab::Diff::DiffRefs.new( + base_sha: diff_base_sha, + start_sha: diff_start_sha, + head_sha: diff_head_sha + ) end end -- cgit v1.2.1 From 159998a460be6ca1ea5efd135a5c3aefeab2d9b6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Aug 2016 18:07:50 +0300 Subject: Fix merge request spec for update diff note Signed-off-by: Dmitriy Zaporozhets --- spec/models/merge_request_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index f095e23f7ac..eb382f9ef84 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -673,6 +673,8 @@ describe MergeRequest, models: true do start_commit_sha: commit.parent_id, head_commit_sha: commit.sha ) + + subject.merge_request_diff(true) end expect(Notes::DiffPositionUpdateService).to receive(:new).with( -- cgit v1.2.1 From 9a5f40878c6d71e7fd4858f7fa1d948af6c371ae Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Aug 2016 11:32:29 +0300 Subject: Add API to list merge request diff versions Signed-off-by: Dmitriy Zaporozhets --- doc/api/merge_requests.md | 90 +++++++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/entities.rb | 11 ++++ lib/api/merge_request_diffs.rb | 25 ++++++++ spec/requests/api/merge_request_diffs_spec.rb | 32 ++++++++++ 5 files changed, 159 insertions(+) create mode 100644 lib/api/merge_request_diffs.rb create mode 100644 spec/requests/api/merge_request_diffs_spec.rb diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index e00882e6d5d..cf262dc4a80 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -894,3 +894,93 @@ Example response: "created_at": "2016-07-01T11:14:15.530Z" } ``` + +## Get MR diff versions + +Get a list of merge request diff versions. + +``` +GET /projects/:id/merge_requests/:merge_request_id/versions +``` + +Parameters: + +- `id` (required) - The ID of a project +- `merge_request_id` (required) - The ID of MR + + +```json +[{ + "id": 110, + "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-26T14:44:48.926Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1", + "commits": [{ + "id": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "short_id": "33e2ee85", + "title": "Change year to 2018", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-26T17:44:29.000+03:00", + "message": "Change year to 2018" + }, { + "id": "aa24655de48b36335556ac8a3cd8bb521f977cbd", + "short_id": "aa24655d", + "title": "Update LICENSE", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-25T17:21:53.000+03:00", + "message": "Update LICENSE" + }, { + "id": "3eed087b29835c48015768f839d76e5ea8f07a24", + "short_id": "3eed087b", + "title": "Add license", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-25T17:21:20.000+03:00", + "message": "Add license" + }], + "diffs": [{ + "old_path": "LICENSE", + "new_path": "LICENSE", + "a_mode": "0", + "b_mode": "100644", + "diff": "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2018 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n", + "new_file": true, + "renamed_file": false, + "deleted_file": false + }] +}, { + "id": 108, + "head_commit_sha": "3eed087b29835c48015768f839d76e5ea8f07a24", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-25T14:21:33.028Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1", + "commits": [{ + "id": "3eed087b29835c48015768f839d76e5ea8f07a24", + "short_id": "3eed087b", + "title": "Add license", + "author_name": "Administrator", + "author_email": "admin@example.com", + "created_at": "2016-07-25T17:21:20.000+03:00", + "message": "Add license" + }], + "diffs": [{ + "old_path": "LICENSE", + "new_path": "LICENSE", + "a_mode": "0", + "b_mode": "100644", + "diff": "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2016 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n", + "new_file": true, + "renamed_file": false, + "deleted_file": false + }] +}] +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index bd16806892b..9d8f297191f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -67,5 +67,6 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + mount ::API::MergeRequestDiffs end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3e21b7a0b8a..af069063f0c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -228,6 +228,17 @@ module API end end + class MergeRequestDiff < Grape::Entity + expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha, + :created_at, :merge_request_id, :state, :real_size + + expose :commits, using: Entities::RepoCommit + + expose :diffs, using: Entities::RepoDiff do |compare, _| + compare.diffs(all_diffs: true).to_a + end + end + class SSHKey < Grape::Entity expose :id, :title, :key, :created_at end diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb new file mode 100644 index 00000000000..4bd149d1603 --- /dev/null +++ b/lib/api/merge_request_diffs.rb @@ -0,0 +1,25 @@ +module API + # MergeRequestDiff API + class MergeRequestDiffs < Grape::API + before { authenticate! } + + resource :projects do + # List merge requests diff versions + # + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - The ID of MR + # + # Example: + # GET /projects/:id/merge_requests/:merge_request_id/versions + # + get ":id/merge_requests/:merge_request_id/versions" do + merge_request = user_project.merge_requests. + find(params[:merge_request_id]) + + authorize! :read_merge_request, merge_request + present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff + end + end + end +end diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb new file mode 100644 index 00000000000..db076b4b9a5 --- /dev/null +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -0,0 +1,32 @@ +require "spec_helper" + +describe API::API, 'MergeRequestDiffs', api: true do + include ApiHelpers + + let!(:user) { create(:user) } + let!(:merge_request) { create(:merge_request, importing: true) } + let!(:project) { merge_request.target_project } + + before do + merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') + project.team << [user, :master] + end + + describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do + context 'valid merge request' do + before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user) } + let(:merge_request_diff) { merge_request.merge_request_diffs.first } + + it { expect(response.status).to eq 200 } + it { expect(json_response.size).to eq(merge_request.merge_request_diffs.size) } + it { expect(json_response.first['id']).to eq(merge_request_diff.id) } + it { expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) } + end + + it 'returns a 404 when merge_request_id not found' do + get api("/projects/#{project.id}/merge_requests/999/versions", user) + expect(response).to have_http_status(404) + end + end +end -- cgit v1.2.1 From 69096ad9da77ce51d544a8f23f4e8873df273f4e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Aug 2016 11:58:09 +0300 Subject: Update merge request versions API to match styleguide Signed-off-by: Dmitriy Zaporozhets --- doc/api/merge_requests.md | 11 ++++++++--- lib/api/merge_request_diffs.rb | 19 ++++++++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index cf262dc4a80..68b5172ed0d 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -903,11 +903,16 @@ Get a list of merge request diff versions. GET /projects/:id/merge_requests/:merge_request_id/versions ``` -Parameters: +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer | yes | The ID of the project | +| `merge_request_id` | integer | yes | The ID of the merge request | -- `id` (required) - The ID of a project -- `merge_request_id` (required) - The ID of MR +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions +``` +Example response: ```json [{ diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index 4bd149d1603..be954ff96ac 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -4,15 +4,16 @@ module API before { authenticate! } resource :projects do - # List merge requests diff versions - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of MR - # - # Example: - # GET /projects/:id/merge_requests/:merge_request_id/versions - # + desc 'Get a list of merge request diff versions' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::MergeRequestDiff + end + + params do + requires :id, type: Integer, desc: 'The ID of a project' + requests :merge_request_id, type: Integer, desc: 'The ID of a merge request' + end + get ":id/merge_requests/:merge_request_id/versions" do merge_request = user_project.merge_requests. find(params[:merge_request_id]) -- cgit v1.2.1 From 08f5f897889dc7e7f00a65f56e988449350e60f2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Aug 2016 12:35:20 +0300 Subject: Fix project id param for merge request version API Signed-off-by: Dmitriy Zaporozhets --- doc/api/merge_requests.md | 2 +- lib/api/merge_request_diffs.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 68b5172ed0d..0c06cfcd876 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -905,7 +905,7 @@ GET /projects/:id/merge_requests/:merge_request_id/versions | Attribute | Type | Required | Description | | --------- | ------- | -------- | --------------------- | -| `id` | integer | yes | The ID of the project | +| `id` | String | yes | The ID of the project | | `merge_request_id` | integer | yes | The ID of the merge request | ```bash diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index be954ff96ac..a2f92f81107 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -10,8 +10,8 @@ module API end params do - requires :id, type: Integer, desc: 'The ID of a project' - requests :merge_request_id, type: Integer, desc: 'The ID of a merge request' + requires :id, type: String, desc: 'The ID of a project' + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' end get ":id/merge_requests/:merge_request_id/versions" do -- cgit v1.2.1 From d99d5198c2df6f931664b8096bcbfc28e8221145 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 8 Aug 2016 23:00:11 +0300 Subject: Add MergeRequest#branch_merge_base_commit method Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 35 +++++++++++++++++++++++++++-------- spec/models/merge_request_spec.rb | 17 +++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 56d5157298f..7e3444882ea 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -177,8 +177,8 @@ class MergeRequest < ActiveRecord::Base def diff_base_commit if persisted? merge_request_diff.base_commit - elsif diff_start_commit && diff_head_commit - self.target_project.merge_base_commit(diff_start_sha, diff_head_sha) + else + branch_merge_base_commit end end @@ -239,6 +239,15 @@ class MergeRequest < ActiveRecord::Base target_project.repository.commit(target_branch) if target_branch_ref end + def branch_merge_base_commit + start_sha = target_branch_sha + head_sha = source_branch_sha + + if start_sha && head_sha + target_project.merge_base_commit(start_sha, head_sha) + end + end + def target_branch_sha @target_branch_sha || target_branch_head.try(:sha) end @@ -247,15 +256,25 @@ class MergeRequest < ActiveRecord::Base @source_branch_sha || source_branch_head.try(:sha) end + def branch_merge_base_sha + branch_merge_base_commit.try(:sha) + end + def diff_refs if merge_request_diff merge_request_diff.diff_refs - elsif diff_start_commit || diff_base_commit - Gitlab::Diff::DiffRefs.new( - base_sha: diff_base_sha, - start_sha: diff_start_sha, - head_sha: diff_head_sha - ) + else + start_sha = target_branch_sha + head_sha = source_branch_sha + base_sha = branch_merge_base_sha + + if start_sha || base_sha + Gitlab::Diff::DiffRefs.new( + base_sha: base_sha, + start_sha: start_sha, + head_sha: head_sha + ) + end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index eb382f9ef84..d32bec53a28 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -691,4 +691,21 @@ describe MergeRequest, models: true do subject.reload_diff end end + + describe '#branch_merge_base_commit' do + context 'source and target branch exist' do + it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } + it { expect(subject.branch_merge_base_commit).to be_a(Commit) } + end + + context 'when the target branch does not exist' do + before do + subject.project.repository.raw_repository.delete_branch(subject.target_branch) + end + + it 'returns nil' do + expect(subject.branch_merge_base_commit).to be_nil + end + end + end end -- cgit v1.2.1 From 28e33df46bdffac3dc9388b56035db38dcdab5e3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 9 Aug 2016 15:16:50 +0300 Subject: Load merge request versions without loading whole diff from database Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 4 ++++ app/views/projects/merge_requests/show/_versions.html.haml | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 074d8f5d40a..24e09c4d57c 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -30,6 +30,10 @@ class MergeRequestDiff < ActiveRecord::Base # It allows you to override variables like head_commit_sha before getting diff. after_create :save_git_content, unless: :importing? + def self.select_without_diff + select(column_names - ['st_diffs']) + end + # Collect information about commits and diff from repository # and save it to the database as serialized data def save_git_content diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 93a06154022..84c2b83e330 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,7 +1,7 @@ -- diffs_count = @merge_request.merge_request_diffs.count +- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff +- latest_diff = merge_request_diffs.first -- if diffs_count > 1 - - latest_diff = @merge_request.merge_request_diff +- if merge_request_diffs.size > 1 .mr-version-switch Version:  %span.dropdown.inline @@ -13,7 +13,7 @@ #{@merge_request_diff.head_commit.short_id} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - - @merge_request.merge_request_diffs.each do |merge_request_diff| + - merge_request_diffs.each do |merge_request_diff| %li = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do %strong.monospace -- cgit v1.2.1 From 3eae0641ef0708f9b223abbe0070e332ea0b20ac Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 10 Aug 2016 18:40:10 +0800 Subject: Introduce Pipeline#latest and Pipeline.latest_for: So that we could easily access it for the view --- app/models/ci/pipeline.rb | 12 +++++++++-- spec/models/ci/pipeline_spec.rb | 48 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index bce6a992af6..bc1190537da 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -21,8 +21,12 @@ module Ci after_save :keep_around_commits # ref can't be HEAD or SHA, can only be branch/tag name - scope :latest_successful_for, ->(ref = default_branch) do - where(ref: ref).success.order(id: :desc).limit(1) + scope :latest_successful_for, ->(ref) do + latest(ref).success + end + + scope :latest_for, ->(ref) do + where(ref: ref).order(id: :desc).limit(1) end def self.truncate_sha(sha) @@ -98,6 +102,10 @@ module Ci end end + def latest + project.pipelines.latest_for(ref).first + end + def latest? return false unless ref commit = project.commit(ref) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ccee591cf7a..556a6e1b59a 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Ci::Pipeline, models: true do - let(:project) { FactoryGirl.create :empty_project } - let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_pipeline, project: project) } it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:user) } @@ -481,6 +481,50 @@ describe Ci::Pipeline, models: true do end end + context 'with non-empty project' do + let(:project) { create(:project) } + let(:pipeline) { create_pipeline } + + describe '#latest?' do + context 'with latest sha' do + it 'returns true' do + expect(pipeline).to be_latest + end + end + + context 'with not latest sha' do + before do + pipeline.update( + sha: project.commit("#{project.default_branch}~1").sha) + end + + it 'returns false' do + expect(pipeline).not_to be_latest + end + end + end + + describe '#latest' do + let(:previous_pipeline) { create_pipeline } + + before do + previous_pipeline + pipeline + end + + it 'gives the latest pipeline' do + expect(previous_pipeline.latest).to eq(pipeline) + end + end + + def create_pipeline + create(:ci_pipeline, + project: project, + ref: project.default_branch, + sha: project.commit.sha) + end + end + describe '#manual_actions' do subject { pipeline.manual_actions } -- cgit v1.2.1 From cc3dbf83f4c0cf21fee56398f27851981f0e98f6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 10 Aug 2016 18:57:51 +0800 Subject: Empty lines around blocks --- spec/features/projects/branches/download_buttons_spec.rb | 11 +++++++---- spec/features/projects/files/download_buttons_spec.rb | 11 +++++++---- spec/features/projects/main/download_buttons_spec.rb | 11 +++++++---- spec/features/projects/tags/download_buttons_spec.rb | 11 +++++++---- spec/requests/projects/artifacts_controller_spec.rb | 2 ++ 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index d3f53b65699..a223786777b 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -5,12 +5,15 @@ feature 'Download buttons in branches page', feature: true do given(:role) { :developer } given(:status) { 'success' } given(:project) { create(:project) } + given(:pipeline) do - create(:ci_pipeline, project: project, - sha: project.commit.sha, - ref: project.default_branch, - status: status) + create(:ci_pipeline, + project: project, + sha: project.commit.sha, + ref: project.default_branch, + status: status) end + given!(:build) do create(:ci_build, :success, :artifacts, pipeline: pipeline, diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb index 60c2ffedce0..be5cebcd7c9 100644 --- a/spec/features/projects/files/download_buttons_spec.rb +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -5,12 +5,15 @@ feature 'Download buttons in files tree', feature: true do given(:role) { :developer } given(:status) { 'success' } given(:project) { create(:project) } + given(:pipeline) do - create(:ci_pipeline, project: project, - sha: project.commit.sha, - ref: project.default_branch, - status: status) + create(:ci_pipeline, + project: project, + sha: project.commit.sha, + ref: project.default_branch, + status: status) end + given!(:build) do create(:ci_build, :success, :artifacts, pipeline: pipeline, diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb index 62e56808558..b26c0ea7a14 100644 --- a/spec/features/projects/main/download_buttons_spec.rb +++ b/spec/features/projects/main/download_buttons_spec.rb @@ -5,12 +5,15 @@ feature 'Download buttons in project main page', feature: true do given(:role) { :developer } given(:status) { 'success' } given(:project) { create(:project) } + given(:pipeline) do - create(:ci_pipeline, project: project, - sha: project.commit.sha, - ref: project.default_branch, - status: status) + create(:ci_pipeline, + project: project, + sha: project.commit.sha, + ref: project.default_branch, + status: status) end + given!(:build) do create(:ci_build, :success, :artifacts, pipeline: pipeline, diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index d4c4cfe9c99..ebc5204cf1d 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -6,12 +6,15 @@ feature 'Download buttons in tags page', feature: true do given(:status) { 'success' } given(:tag) { 'v1.0.0' } given(:project) { create(:project) } + given(:pipeline) do - create(:ci_pipeline, project: project, - sha: project.commit.sha, - ref: tag, - status: status) + create(:ci_pipeline, + project: project, + sha: project.commit.sha, + ref: tag, + status: status) end + given!(:build) do create(:ci_build, :success, :artifacts, pipeline: pipeline, diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index b823676d9e1..952b9fb99b7 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -3,12 +3,14 @@ require 'spec_helper' describe Projects::ArtifactsController do let(:user) { create(:user) } let(:project) { create(:project) } + let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit.sha, ref: project.default_branch) end + let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } describe 'GET /:project/builds/artifacts/:ref_name/browse?job=name' do -- cgit v1.2.1 From 729ad5d53f59b18f14f9c0c7ed306b34ae70c4b5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 10 Aug 2016 18:59:06 +0800 Subject: This might be fixed on master already, but well --- spec/lib/gitlab/redis_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb index 879ed30841c..e54f5ffb312 100644 --- a/spec/lib/gitlab/redis_spec.rb +++ b/spec/lib/gitlab/redis_spec.rb @@ -67,7 +67,6 @@ describe Gitlab::Redis do expect(subject).to receive(:fetch_config) { 'redis://myredis:6379' } expect(subject.send(:raw_config_hash)).to eq(url: 'redis://myredis:6379') end - end describe '#fetch_config' do -- cgit v1.2.1 From 517249858e41694f51b67461b313d5a34c2a466c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 10 Aug 2016 21:07:26 +0800 Subject: It's latest_for, not just latest --- app/models/ci/pipeline.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index bc1190537da..50f9ee7fc66 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -22,7 +22,7 @@ module Ci # ref can't be HEAD or SHA, can only be branch/tag name scope :latest_successful_for, ->(ref) do - latest(ref).success + latest_for(ref).success end scope :latest_for, ->(ref) do -- cgit v1.2.1 From 4b559c9afb34b80b910efec514653c6ea65adba8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 11 Aug 2016 17:26:04 +0800 Subject: Reverse ref and sha in args and rename pipeline to pipeline_for --- app/models/merge_request.rb | 3 ++- app/models/project.rb | 7 ++++--- app/views/projects/issues/_related_branches.html.haml | 2 +- db/fixtures/development/14_builds.rb | 2 +- lib/api/commit_statuses.rb | 2 +- spec/models/project_spec.rb | 2 +- spec/requests/api/commits_spec.rb | 2 +- spec/services/ci/image_for_build_service_spec.rb | 2 +- 8 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b1fb3ce5d69..7c8e938df75 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -666,7 +666,8 @@ class MergeRequest < ActiveRecord::Base end def pipeline - @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project + return unless diff_head_sha && source_project + @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end def merge_commit diff --git a/app/models/project.rb b/app/models/project.rb index d306f86f783..dc9b4b38a10 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1086,12 +1086,13 @@ class Project < ActiveRecord::Base !namespace.share_with_group_lock end - def pipeline(sha, ref) + def pipeline_for(ref, sha) pipelines.order(id: :desc).find_by(sha: sha, ref: ref) end - def ensure_pipeline(sha, ref, current_user = nil) - pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user) + def ensure_pipeline(ref, sha, current_user = nil) + pipeline_for(ref, sha) || + pipelines.create(sha: sha, ref: ref, user: current_user) end def enable_ci diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index 6ea9f612d13..a8eeab3e55e 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -5,7 +5,7 @@ - @related_branches.each do |branch| %li - target = @project.repository.find_branch(branch).target - - pipeline = @project.pipeline(target.sha, branch) if target + - pipeline = @project.pipeline_for(branch, target.sha) if target - if pipeline %span.related-branch-ci-status = render_pipeline_status(pipeline) diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb index e65abe4ef77..6cc18bf51ed 100644 --- a/db/fixtures/development/14_builds.rb +++ b/db/fixtures/development/14_builds.rb @@ -40,7 +40,7 @@ class Gitlab::Seeder::Builds commits = @project.repository.commits('master', limit: 5) commits_sha = commits.map { |commit| commit.raw.id } commits_sha.map do |sha| - @project.ensure_pipeline(sha, 'master') + @project.ensure_pipeline('master', sha) end rescue [] diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 4df6ca8333e..5e3c9563703 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -64,7 +64,7 @@ module API ref = branches.first end - pipeline = @project.ensure_pipeline(commit.sha, ref, current_user) + pipeline = @project.ensure_pipeline(ref, commit.sha, current_user) name = params[:name] || params[:context] status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9c3b4712cab..60819fe02be 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -688,7 +688,7 @@ describe Project, models: true do let(:project) { create :project } let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' } - subject { project.pipeline(pipeline.sha, 'master') } + subject { project.pipeline_for('master', pipeline.sha) } it { is_expected.to eq(pipeline) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 4379fcb3c1e..60c2f14bd3c 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -94,7 +94,7 @@ describe API::API, api: true do end it "returns status for CI" do - pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master') + pipeline = project.ensure_pipeline('master', project.repository.commit.sha) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response).to have_http_status(200) expect(json_response['status']).to eq(pipeline.status) diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb index 3a3e3efe709..21e00b11a12 100644 --- a/spec/services/ci/image_for_build_service_spec.rb +++ b/spec/services/ci/image_for_build_service_spec.rb @@ -5,7 +5,7 @@ module Ci let(:service) { ImageForBuildService.new } let(:project) { FactoryGirl.create(:empty_project) } let(:commit_sha) { '01234567890123456789' } - let(:commit) { project.ensure_pipeline(commit_sha, 'master') } + let(:commit) { project.ensure_pipeline('master', commit_sha) } let(:build) { FactoryGirl.create(:ci_build, pipeline: commit) } describe '#execute' do -- cgit v1.2.1 From 0a9d9f7d5096aa742564e704a96fa7c40eeaf007 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 11 Aug 2016 17:55:23 +0800 Subject: Fetch the current SHA if SHA was not passed --- app/models/project.rb | 3 ++- spec/models/project_spec.rb | 38 ++++++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index dc9b4b38a10..2969bec0bf7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1086,7 +1086,8 @@ class Project < ActiveRecord::Base !namespace.share_with_group_lock end - def pipeline_for(ref, sha) + def pipeline_for(ref, sha = commit(ref).try(:sha)) + return unless sha pipelines.order(id: :desc).find_by(sha: sha, ref: ref) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 60819fe02be..1ce306c4f39 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -684,23 +684,37 @@ describe Project, models: true do end end - describe '#pipeline' do - let(:project) { create :project } - let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' } - - subject { project.pipeline_for('master', pipeline.sha) } + describe '#pipeline_for' do + let(:project) { create(:project) } + let!(:pipeline) { create_pipeline } - it { is_expected.to eq(pipeline) } + shared_examples 'giving the correct pipeline' do + it { is_expected.to eq(pipeline) } - context 'return latest' do - let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' } + context 'return latest' do + let!(:pipeline2) { create_pipeline } - before do - pipeline - pipeline2 + it { is_expected.to eq(pipeline2) } end + end + + context 'with explicit sha' do + subject { project.pipeline_for('master', pipeline.sha) } + + it_behaves_like 'giving the correct pipeline' + end + + context 'with implicit sha' do + subject { project.pipeline_for('master') } + + it_behaves_like 'giving the correct pipeline' + end - it { is_expected.to eq(pipeline2) } + def create_pipeline + create(:ci_pipeline, + project: project, + ref: 'master', + sha: project.commit('master').sha) end end -- cgit v1.2.1 From 71046cc62919846b52d3724f7277ca14bb3a3a81 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 11 Aug 2016 17:56:55 +0800 Subject: Fix mock --- spec/models/merge_request_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 3270b877c1a..11cfc7cace1 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -463,8 +463,8 @@ describe MergeRequest, models: true do allow(subject).to receive(:diff_head_sha).and_return('123abc') - expect(subject.source_project).to receive(:pipeline). - with('123abc', 'master'). + expect(subject.source_project).to receive(:pipeline_for). + with('master', '123abc'). and_return(pipeline) expect(subject.pipeline).to eq(pipeline) -- cgit v1.2.1 From 2a435e1d116065496fcb82f9b2182f7037d4c8b3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 11 Aug 2016 18:04:52 +0800 Subject: Remove Pipeline#latest in favour of Project#pipeline_for(ref) --- app/models/ci/pipeline.rb | 4 ---- spec/models/ci/pipeline_spec.rb | 28 +++++++--------------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 50f9ee7fc66..9621bddf8dc 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -102,10 +102,6 @@ module Ci end end - def latest - project.pipelines.latest_for(ref).first - end - def latest? return false unless ref commit = project.commit(ref) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 556a6e1b59a..2dbc3d985b0 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -483,7 +483,13 @@ describe Ci::Pipeline, models: true do context 'with non-empty project' do let(:project) { create(:project) } - let(:pipeline) { create_pipeline } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + ref: project.default_branch, + sha: project.commit.sha) + end describe '#latest?' do context 'with latest sha' do @@ -503,26 +509,6 @@ describe Ci::Pipeline, models: true do end end end - - describe '#latest' do - let(:previous_pipeline) { create_pipeline } - - before do - previous_pipeline - pipeline - end - - it 'gives the latest pipeline' do - expect(previous_pipeline.latest).to eq(pipeline) - end - end - - def create_pipeline - create(:ci_pipeline, - project: project, - ref: project.default_branch, - sha: project.commit.sha) - end end describe '#manual_actions' do -- cgit v1.2.1 From d84aa560331a646016880e4d2c5c0a3b3d4b32a6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 11 Aug 2016 18:09:26 +0800 Subject: Make Pipeline.latest_successful_for return the record --- app/models/ci/pipeline.rb | 8 ++------ app/models/project.rb | 2 +- app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/repositories/_download_archive.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 9621bddf8dc..0289a51eedd 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -21,12 +21,8 @@ module Ci after_save :keep_around_commits # ref can't be HEAD or SHA, can only be branch/tag name - scope :latest_successful_for, ->(ref) do - latest_for(ref).success - end - - scope :latest_for, ->(ref) do - where(ref: ref).order(id: :desc).limit(1) + def self.latest_successful_for(ref) + where(ref: ref).order(id: :desc).success.first end def self.truncate_sha(sha) diff --git a/app/models/project.rb b/app/models/project.rb index 2969bec0bf7..7aa34cdeec8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -432,7 +432,7 @@ class Project < ActiveRecord::Base # ref can't be HEAD, can only be branch/tag name or SHA def latest_successful_builds_for(ref = default_branch) - latest_pipeline = pipelines.latest_successful_for(ref).first + latest_pipeline = pipelines.latest_successful_for(ref) if latest_pipeline latest_pipeline.builds.latest.with_artifacts diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index b096516c627..2029758f30d 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,7 +27,7 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare - - pipeline = @project.pipelines.latest_successful_for(branch.name).first + - pipeline = @project.pipelines.latest_successful_for(branch.name) - if pipeline - artifacts = pipeline.builds.latest.with_artifacts - if artifacts.any? diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index d135b448dd7..e7ef0cbaa91 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -14,7 +14,7 @@ %li = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - pipeline = @project.pipelines.latest_successful_for(@ref).first + - pipeline = @project.pipelines.latest_successful_for(@ref) - if pipeline - artifacts = pipeline.builds.latest.with_artifacts - if artifacts.any? diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 183daebfc3a..0ef9ad5f789 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -25,7 +25,7 @@ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do %i.fa.fa-download %span Download tar - - pipeline = @project.pipelines.latest_successful_for(ref).first + - pipeline = @project.pipelines.latest_successful_for(ref) - if pipeline - artifacts = pipeline.builds.latest.with_artifacts - if artifacts.any? diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index bb2e5224306..ba3fd4627af 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -12,7 +12,7 @@ %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - pipeline = project.pipelines.latest_successful_for(ref).first + - pipeline = project.pipelines.latest_successful_for(ref) - if pipeline - artifacts = pipeline.builds.latest.with_artifacts - if artifacts.any? -- cgit v1.2.1 From 62d991c80f0b5716945912ebf2031f56da75a12b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 11 Aug 2016 19:04:45 +0800 Subject: Show latest pipeline status if what provided artifacts aren't latest --- app/views/projects/branches/_branch.html.haml | 5 ++++- app/views/projects/buttons/_download.html.haml | 5 ++++- app/views/projects/repositories/_download_archive.html.haml | 5 ++++- app/views/projects/tags/_download.html.haml | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 2029758f30d..8a33cd1502c 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -40,7 +40,10 @@ %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(@project.namespace, @project, pipeline))})" + - latest_pipeline = @project.pipeline_for(branch.name) + %li + %span= latest_pipeline.status.humanize + %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, branch.name, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index e7ef0cbaa91..84135a6d049 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -20,7 +20,10 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(@project.namespace, @project, pipeline))})" + - latest_pipeline = @project.pipeline_for(@ref) + %li + %span= latest_pipeline.status.humanize + %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, @ref, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 0ef9ad5f789..c9a0b74e47b 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -31,7 +31,10 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(@project.namespace, @project, pipeline))})" + - latest_pipeline = @project.pipeline_for(ref) + %li + %span= latest_pipeline.status.humanize + %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(@project.namespace, @project, ref, 'download', job: job.name), rel: 'nofollow' do diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index ba3fd4627af..10e8f77ac0c 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -18,7 +18,10 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - = " (not latest, but #{link_to(pipeline.short_sha, namespace_project_pipeline_path(project.namespace, project, pipeline))})" + - latest_pipeline = @project.pipeline_for(ref) + %li + %span= latest_pipeline.status.humanize + %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li = link_to search_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do -- cgit v1.2.1 From 44b29b5b3b908ee5acd1d35fdf8e75333e7e50c1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 11 Aug 2016 19:15:22 +0800 Subject: Add a CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 42d32e53685..407d183c17d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.11.0 (unreleased) - Fix CI status icon link underline (ClemMakesApps) - The Repository class is now instrumented - Cache the commit author in RequestStore to avoid extra lookups in PostReceive + - Add a button to download latest successful artifacts for branches and tags - Expand commit message width in repo view (ClemMakesApps) - Cache highlighted diff lines for merge requests - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' -- cgit v1.2.1 From 445edcfacc583ba613a4b865b104d7d7e1f7ec0f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 12 Aug 2016 13:08:16 +0300 Subject: Remove unnecessary   in merge request versions dropdown Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/merge_requests/show/_versions.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 84c2b83e330..15d94b16ced 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -3,7 +3,7 @@ - if merge_request_diffs.size > 1 .mr-version-switch - Version:  + Version: %span.dropdown.inline %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } %strong.monospace< -- cgit v1.2.1 From ac072132b8f0e36badf297208a5964109dbed126 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 12 Aug 2016 14:44:49 +0300 Subject: Add single merge request diff API endpoint Signed-off-by: Dmitriy Zaporozhets --- doc/api/merge_requests.md | 72 ++++++++++++++++----------- lib/api/entities.rb | 2 + lib/api/merge_request_diffs.rb | 19 +++++++ spec/requests/api/merge_request_diffs_spec.rb | 17 +++++++ 4 files changed, 81 insertions(+), 29 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 0c06cfcd876..130488bf7c1 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -916,6 +916,48 @@ Example response: ```json [{ + "id": 110, + "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-26T14:44:48.926Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1" +}, { + "id": 108, + "head_commit_sha": "3eed087b29835c48015768f839d76e5ea8f07a24", + "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", + "created_at": "2016-07-25T14:21:33.028Z", + "merge_request_id": 105, + "state": "collected", + "real_size": "1" +}] +``` + +## Get a single MR diff version + +Get a single merge request diff version. + +``` +GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | String | yes | The ID of the project | +| `merge_request_id` | integer | yes | The ID of the merge request | +| `version_id` | integer | yes | The ID of the merge request diff version | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions/1 +``` + +Example response: + +```json +{ "id": 110, "head_commit_sha": "33e2ee8579fda5bc36accc9c6fbd0b4fefda9e30", "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", @@ -959,33 +1001,5 @@ Example response: "renamed_file": false, "deleted_file": false }] -}, { - "id": 108, - "head_commit_sha": "3eed087b29835c48015768f839d76e5ea8f07a24", - "base_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", - "start_commit_sha": "eeb57dffe83deb686a60a71c16c32f71046868fd", - "created_at": "2016-07-25T14:21:33.028Z", - "merge_request_id": 105, - "state": "collected", - "real_size": "1", - "commits": [{ - "id": "3eed087b29835c48015768f839d76e5ea8f07a24", - "short_id": "3eed087b", - "title": "Add license", - "author_name": "Administrator", - "author_email": "admin@example.com", - "created_at": "2016-07-25T17:21:20.000+03:00", - "message": "Add license" - }], - "diffs": [{ - "old_path": "LICENSE", - "new_path": "LICENSE", - "a_mode": "0", - "b_mode": "100644", - "diff": "--- /dev/null\n+++ b/LICENSE\n@@ -0,0 +1,21 @@\n+The MIT License (MIT)\n+\n+Copyright (c) 2016 Administrator\n+\n+Permission is hereby granted, free of charge, to any person obtaining a copy\n+of this software and associated documentation files (the \"Software\"), to deal\n+in the Software without restriction, including without limitation the rights\n+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+copies of the Software, and to permit persons to whom the Software is\n+furnished to do so, subject to the following conditions:\n+\n+The above copyright notice and this permission notice shall be included in all\n+copies or substantial portions of the Software.\n+\n+THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+SOFTWARE.\n", - "new_file": true, - "renamed_file": false, - "deleted_file": false - }] -}] +} ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index af069063f0c..f582b2ce250 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -231,7 +231,9 @@ module API class MergeRequestDiff < Grape::Entity expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha, :created_at, :merge_request_id, :state, :real_size + end + class MergeRequestDiffFull < MergeRequestDiff expose :commits, using: Entities::RepoCommit expose :diffs, using: Entities::RepoDiff do |compare, _| diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index a2f92f81107..07435d78468 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -21,6 +21,25 @@ module API authorize! :read_merge_request, merge_request present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff end + + desc 'Get a single merge request diff version' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::MergeRequestDiffFull + end + + params do + requires :id, type: String, desc: 'The ID of a project' + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' + requires :version_id, type: Integer, desc: 'The ID of a merge request diff version' + end + + get ":id/merge_requests/:merge_request_id/versions/:version_id" do + merge_request = user_project.merge_requests. + find(params[:merge_request_id]) + + authorize! :read_merge_request, merge_request + present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull + end end end end diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index db076b4b9a5..8f1e5ac9891 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -29,4 +29,21 @@ describe API::API, 'MergeRequestDiffs', api: true do expect(response).to have_http_status(404) end end + + describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do + context 'valid merge request' do + before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user) } + let(:merge_request_diff) { merge_request.merge_request_diffs.first } + + it { expect(response.status).to eq 200 } + it { expect(json_response['id']).to eq(merge_request_diff.id) } + it { expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha) } + it { expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size) } + end + + it 'returns a 404 when merge_request_id not found' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user) + expect(response).to have_http_status(404) + end + end end -- cgit v1.2.1 From a8fe213ebbb4a1aa48a20c908ba989190d20f2a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 12 Aug 2016 15:40:00 +0300 Subject: Add documentation for merge request versions Signed-off-by: Dmitriy Zaporozhets --- doc/workflow/merge_requests.md | 8 ++++++++ doc/workflow/merge_requests/versions.png | Bin 0 -> 100566 bytes 2 files changed, 8 insertions(+) create mode 100644 doc/workflow/merge_requests/versions.png diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index d2ec56e6504..a81b1080ca6 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -61,3 +61,11 @@ If you click the "Hide whitespace changes" button, you can see the diff without It is also working on commits compare view. ![Commit Compare](merge_requests/commit_compare.png) + +## Merge Requests versions + +Every time you push to merge request branch, a new version of merge request diff +is created. When you visit the merge request page you see latest version of changes. +However you can select an older one from version dropdown + +![Merge Request Versions](merge_requests/versions.png) diff --git a/doc/workflow/merge_requests/versions.png b/doc/workflow/merge_requests/versions.png new file mode 100644 index 00000000000..c0a6dfe6806 Binary files /dev/null and b/doc/workflow/merge_requests/versions.png differ -- cgit v1.2.1 From 29ac60d7fbb8208880408dbf98a94acd0ae73730 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 15 Aug 2016 15:20:36 +0300 Subject: Change the way old merge request diff handled Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request.rb | 2 +- app/models/merge_request_diff.rb | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7e3444882ea..b7c2afb0920 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -261,7 +261,7 @@ class MergeRequest < ActiveRecord::Base end def diff_refs - if merge_request_diff + if persisted? merge_request_diff.diff_refs else start_sha = target_branch_sha diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 24e09c4d57c..4c18775c44a 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -22,10 +22,6 @@ class MergeRequestDiff < ActiveRecord::Base serialize :st_commits serialize :st_diffs - # For compatibility with old MergeRequestDiff which - # does not store those variables in database - after_initialize :ensure_commits_sha, if: :persisted? - # All diff information is collected from repository after object is created. # It allows you to override variables like head_commit_sha before getting diff. after_create :save_git_content, unless: :importing? @@ -50,6 +46,20 @@ class MergeRequestDiff < ActiveRecord::Base self.base_commit_sha ||= find_base_sha end + # This method will rely on repository branch sha + # in case start_commit_sha is nil. Its necesarry for old merge request diff + # created before version 8.4 to work + def safe_start_commit_sha + start_commit_sha || merge_request.target_branch_sha + end + + # This method will rely on repository branch sha + # in case head_commit_sha is nil. Its necesarry for old merge request diff + # created before version 8.4 to work + def safe_head_commit_sha + head_commit_sha || last_commit.try(:sha) || merge_request.source_branch_sha + end + def size real_size.presence || diffs.size end @@ -60,8 +70,8 @@ class MergeRequestDiff < ActiveRecord::Base begin compare = Gitlab::Git::Compare.new( repository.raw_repository, - start_commit_sha, - head_commit_sha + safe_start_commit_sha, + safe_head_commit_sha ) compare.diffs(options) end @@ -126,8 +136,8 @@ class MergeRequestDiff < ActiveRecord::Base Gitlab::Git::Compare.new( repository.raw_repository, - start_commit_sha, - head_commit_sha + safe_start_commit_sha, + safe_head_commit_sha ) end end @@ -216,9 +226,6 @@ class MergeRequestDiff < ActiveRecord::Base return unless head_commit_sha && start_commit_sha project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha) - rescue Rugged::OdbError - # In case head or start commit does not exist in the repository any more - nil end def utf8_st_diffs -- cgit v1.2.1 From 94a7198ade54595d72797cab09db2c2a89172535 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 15 Aug 2016 16:25:29 +0300 Subject: Fix merge request diff create and head_commit_sha compatibility Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 4c18775c44a..950b00f1001 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -42,8 +42,19 @@ class MergeRequestDiff < ActiveRecord::Base def ensure_commits_sha self.start_commit_sha ||= merge_request.target_branch_sha - self.head_commit_sha ||= last_commit.try(:sha) || merge_request.source_branch_sha + self.head_commit_sha ||= merge_request.source_branch_sha self.base_commit_sha ||= find_base_sha + save + end + + # Override head_commit_sha to keep compatibility with merge request diff + # created before version 8.4 that does not store head_commit_sha in separate db field. + def head_commit_sha + if persisted? && super.nil? + last_commit.try(:sha) + else + super + end end # This method will rely on repository branch sha @@ -57,7 +68,7 @@ class MergeRequestDiff < ActiveRecord::Base # in case head_commit_sha is nil. Its necesarry for old merge request diff # created before version 8.4 to work def safe_head_commit_sha - head_commit_sha || last_commit.try(:sha) || merge_request.source_branch_sha + head_commit_sha || merge_request.source_branch_sha end def size @@ -111,7 +122,7 @@ class MergeRequestDiff < ActiveRecord::Base end def head_commit - return last_commit unless head_commit_sha + return unless head_commit_sha project.commit(head_commit_sha) end -- cgit v1.2.1 From 643a368fa437725cbfffcfdc251055c4d125438c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 15 Aug 2016 17:57:19 +0300 Subject: Make merge request diff works with new FileCollection logic Signed-off-by: Dmitriy Zaporozhets --- .../projects/merge_requests_controller.rb | 2 +- app/models/merge_request.rb | 6 +- app/models/merge_request_diff.rb | 14 +++-- .../projects/merge_requests/show/_diffs.html.haml | 1 + lib/gitlab/diff/file_collection/merge_request.rb | 73 ---------------------- .../diff/file_collection/merge_request_diff.rb | 73 ++++++++++++++++++++++ 6 files changed, 87 insertions(+), 82 deletions(-) delete mode 100644 lib/gitlab/diff/file_collection/merge_request.rb create mode 100644 lib/gitlab/diff/file_collection/merge_request_diff.rb diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a0b80c823fb..a4b1262896c 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -91,7 +91,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html { define_discussion_vars } format.json do - @diffs = @merge_request.diffs(diff_options) + @diffs = @merge_request_diff.diffs(diff_options) render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7c3845ef538..104f9c7223d 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -172,10 +172,10 @@ class MergeRequest < ActiveRecord::Base end def diffs(diff_options = nil) - if self.compare - self.compare.diffs(diff_options) + if compare + compare.diffs(diff_options) else - Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) + merge_request_diff.diffs(diff_options) end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 8920641cfec..9a34d099acd 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -141,7 +141,13 @@ class MergeRequestDiff < ActiveRecord::Base base_commit_sha? && head_commit_sha? && start_commit_sha? end - private + def diffs(diff_options = nil) + Gitlab::Diff::FileCollection::MergeRequestDiff.new(self, diff_options: diff_options) + end + + def project + merge_request.target_project + end def compare @compare ||= @@ -157,6 +163,8 @@ class MergeRequestDiff < ActiveRecord::Base end end + private + def dump_commits(commits) commits.map(&:to_hash) end @@ -229,10 +237,6 @@ class MergeRequestDiff < ActiveRecord::Base update_columns_serialized(new_attributes) end - def project - merge_request.target_project - end - def repository project.repository end diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 013b05628fa..99c71e1454a 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,4 +1,5 @@ - if @merge_request_diff.collected? + = render 'projects/merge_requests/show/versions' = render "projects/diffs/diffs", diffs: @diffs - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} diff --git a/lib/gitlab/diff/file_collection/merge_request.rb b/lib/gitlab/diff/file_collection/merge_request.rb deleted file mode 100644 index 4f946908e2f..00000000000 --- a/lib/gitlab/diff/file_collection/merge_request.rb +++ /dev/null @@ -1,73 +0,0 @@ -module Gitlab - module Diff - module FileCollection - class MergeRequest < Base - def initialize(merge_request, diff_options:) - @merge_request = merge_request - - super(merge_request, - project: merge_request.project, - diff_options: diff_options, - diff_refs: merge_request.diff_refs) - end - - def diff_files - super.tap { |_| store_highlight_cache } - end - - private - - # Extracted method to highlight in the same iteration to the diff_collection. - def decorate_diff!(diff) - diff_file = super - cache_highlight!(diff_file) if cacheable? - diff_file - end - - def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) - diff_file.highlighted_diff_lines = cache_diff_lines.map do |line| - Gitlab::Diff::Line.init_from_hash(line) - end - end - - # - # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted) - # for the highlighted ones, so we just skip their execution. - # If the highlighted diff files lines are not cached we calculate and cache them. - # - # The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of - # hashes that represent serialized diff lines. - # - def cache_highlight!(diff_file) - file_path = diff_file.file_path - - if highlight_cache[file_path] - highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path]) - else - highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) - end - end - - def highlight_cache - return @highlight_cache if defined?(@highlight_cache) - - @highlight_cache = Rails.cache.read(cache_key) || {} - @highlight_cache_was_empty = @highlight_cache.empty? - @highlight_cache - end - - def store_highlight_cache - Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty - end - - def cacheable? - @merge_request.merge_request_diff.present? - end - - def cache_key - [@merge_request.merge_request_diff, 'highlighted-diff-files', diff_options] - end - end - end - end -end diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb new file mode 100644 index 00000000000..36348b33943 --- /dev/null +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -0,0 +1,73 @@ +module Gitlab + module Diff + module FileCollection + class MergeRequestDiff < Base + def initialize(merge_request_diff, diff_options:) + @merge_request_diff = merge_request_diff + + super(merge_request_diff, + project: merge_request_diff.project, + diff_options: diff_options, + diff_refs: merge_request_diff.diff_refs) + end + + def diff_files + super.tap { |_| store_highlight_cache } + end + + private + + # Extracted method to highlight in the same iteration to the diff_collection. + def decorate_diff!(diff) + diff_file = super + cache_highlight!(diff_file) if cacheable? + diff_file + end + + def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) + diff_file.highlighted_diff_lines = cache_diff_lines.map do |line| + Gitlab::Diff::Line.init_from_hash(line) + end + end + + # + # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted) + # for the highlighted ones, so we just skip their execution. + # If the highlighted diff files lines are not cached we calculate and cache them. + # + # The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of + # hashes that represent serialized diff lines. + # + def cache_highlight!(diff_file) + file_path = diff_file.file_path + + if highlight_cache[file_path] + highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path]) + else + highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) + end + end + + def highlight_cache + return @highlight_cache if defined?(@highlight_cache) + + @highlight_cache = Rails.cache.read(cache_key) || {} + @highlight_cache_was_empty = @highlight_cache.empty? + @highlight_cache + end + + def store_highlight_cache + Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty + end + + def cacheable? + @merge_request_diff.present? + end + + def cache_key + [@merge_request_diff, 'highlighted-diff-files', diff_options] + end + end + end + end +end -- cgit v1.2.1 From 49d63dc131da899ac2c91c26fe7e22f02da34dbd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 15 Aug 2016 18:11:18 +0300 Subject: Fix and refactor merge request diff_refs method Signed-off-by: Dmitriy Zaporozhets --- app/models/diff_note.rb | 6 +----- app/models/merge_request.rb | 17 ++--------------- app/models/merge_request_diff.rb | 2 +- spec/models/merge_request_spec.rb | 13 +++++++------ 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index c816deb4e0c..364e1a8341b 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -79,11 +79,7 @@ class DiffNote < Note end def noteable_diff_refs - if noteable.respond_to?(:diff_sha_refs) - noteable.diff_sha_refs - else - noteable.diff_refs - end + noteable.diff_refs end def set_original_position diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 104f9c7223d..007c2aa74e1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -270,7 +270,7 @@ class MergeRequest < ActiveRecord::Base end def diff_refs - if persisted? + if merge_request_diff && merge_request_diff.diff_refs_by_sha? merge_request_diff.diff_refs else start_sha = target_branch_sha @@ -287,19 +287,6 @@ class MergeRequest < ActiveRecord::Base end end - # Return diff_refs instance trying to not touch the git repository - def diff_sha_refs - if merge_request_diff && merge_request_diff.diff_refs_by_sha? - return Gitlab::Diff::DiffRefs.new( - base_sha: merge_request_diff.base_commit_sha, - start_sha: merge_request_diff.start_commit_sha, - head_sha: merge_request_diff.head_commit_sha - ) - else - diff_refs - end - end - def validate_branches if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" @@ -716,7 +703,7 @@ class MergeRequest < ActiveRecord::Base end def support_new_diff_notes? - diff_sha_refs && diff_sha_refs.complete? + diff_refs && diff_refs.complete? end def update_diff_notes_positions(old_diff_refs:, new_diff_refs:) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 9a34d099acd..a769d31ac93 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -128,7 +128,7 @@ class MergeRequestDiff < ActiveRecord::Base end def diff_refs - return unless start_commit || base_commit + return unless start_commit_sha || base_commit_sha Gitlab::Diff::DiffRefs.new( base_sha: base_commit_sha, diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 9de9f19a27c..7242c4fc8d3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -723,7 +723,6 @@ describe MergeRequest, models: true do end end -<<<<<<< HEAD describe '#branch_merge_base_commit' do context 'source and target branch exist' do it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } @@ -737,8 +736,11 @@ describe MergeRequest, models: true do it 'returns nil' do expect(subject.branch_merge_base_commit).to be_nil -======= - describe "#diff_sha_refs" do + end + end + end + + describe "#diff_refs" do context "with diffs" do subject { create(:merge_request, :with_diffs) } @@ -747,7 +749,7 @@ describe MergeRequest, models: true do expect_any_instance_of(Repository).not_to receive(:commit) - subject.diff_sha_refs + subject.diff_refs end it "returns expected diff_refs" do @@ -757,8 +759,7 @@ describe MergeRequest, models: true do head_sha: subject.merge_request_diff.head_commit_sha ) - expect(subject.diff_sha_refs).to eq(expected_diff_refs) ->>>>>>> master + expect(subject.diff_refs).to eq(expected_diff_refs) end end end -- cgit v1.2.1 From a94553a90c16b58c9cc9d56488e3aa3c35b8d699 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 15 Aug 2016 18:22:26 +0300 Subject: Fix merge request diffs spec Signed-off-by: Dmitriy Zaporozhets --- spec/models/merge_request_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 7242c4fc8d3..c066b65e7ba 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -159,7 +159,7 @@ describe MergeRequest, models: true do context 'when there are MR diffs' do it 'delegates to the MR diffs' do - merge_request.merge_request_diffs.build + merge_request.save expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)) -- cgit v1.2.1 From d79fb3e3ca47f6d6cd7aa81811d884340a0b0a64 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 00:43:06 +0800 Subject: Fix tests which broke in the merge --- spec/requests/api/commits_spec.rb | 4 ++-- spec/services/ci/image_for_build_service_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 7ca75d77673..5b3dc60aba2 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -95,7 +95,7 @@ describe API::API, api: true do end it "returns status for CI" do - pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master') + pipeline = project.ensure_pipeline('master', project.repository.commit.sha) pipeline.update(status: 'success') get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) @@ -105,7 +105,7 @@ describe API::API, api: true do end it "returns status for CI when pipeline is created" do - project.ensure_pipeline(project.repository.commit.sha, 'master') + project.ensure_pipeline('master', project.repository.commit.sha) get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb index c931c3e4829..b3e0a7b9b58 100644 --- a/spec/services/ci/image_for_build_service_spec.rb +++ b/spec/services/ci/image_for_build_service_spec.rb @@ -5,7 +5,7 @@ module Ci let(:service) { ImageForBuildService.new } let(:project) { FactoryGirl.create(:empty_project) } let(:commit_sha) { '01234567890123456789' } - let(:pipeline) { project.ensure_pipeline(commit_sha, 'master') } + let(:pipeline) { project.ensure_pipeline('master', commit_sha) } let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) } describe '#execute' do -- cgit v1.2.1 From abf1cffff8afd6dcb181e532378ed1548dd62078 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 00:46:51 +0800 Subject: Fix tests, explicitly set the status --- spec/requests/projects/artifacts_controller_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 952b9fb99b7..61d5e3d9a7d 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -8,7 +8,8 @@ describe Projects::ArtifactsController do create(:ci_pipeline, project: project, sha: project.commit.sha, - ref: project.default_branch) + ref: project.default_branch, + status: 'success') end let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } -- cgit v1.2.1 From 73bbaffbfcfb24942111726ae6e04170f1b61ccc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 01:03:08 +0800 Subject: Use URL helper, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13880889 --- app/controllers/projects/artifacts_controller.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 05112571225..b7c395a01a3 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -35,10 +35,14 @@ class Projects::ArtifactsController < Projects::ApplicationController end def search - if params[:path] - url = namespace_project_build_url(project.namespace, project, build) + path = params[:path] - redirect_to "#{url}/artifacts/#{params[:path]}" + if %w[download browse file].include?(path) + redirect_to send( + "#{path}_namespace_project_build_artifacts_url", + project.namespace, + project, + build) else render_404 end -- cgit v1.2.1 From 16b63664f9d73a2ab83feac6eadf959f530208c9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 01:15:29 +0800 Subject: It's project, not @project --- app/views/projects/tags/_download.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 10e8f77ac0c..27e0cd7b1f9 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -18,7 +18,7 @@ - if artifacts.any? %li.dropdown-header Artifacts - unless pipeline.latest? - - latest_pipeline = @project.pipeline_for(ref) + - latest_pipeline = project.pipeline_for(ref) %li %span= latest_pipeline.status.humanize %li.dropdown-header Previous Artifacts -- cgit v1.2.1 From d8dfa56e95c794a91c0a1185e5e6c0017e144b25 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 01:43:58 +0800 Subject: Fix test by assigning the proper SHA --- spec/features/projects/tags/download_buttons_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index ebc5204cf1d..6e0022c179f 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -10,7 +10,7 @@ feature 'Download buttons in tags page', feature: true do given(:pipeline) do create(:ci_pipeline, project: project, - sha: project.commit.sha, + sha: project.commit(tag).sha, ref: tag, status: status) end -- cgit v1.2.1 From 1501b1abc4be78eaa4cb4c28969d9bc59bcf284c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 15 Aug 2016 23:48:43 +0200 Subject: Fixed bug when a pipeline for latest SHA does not exist --- app/helpers/ci_status_helper.rb | 5 +++++ app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/repositories/_download_archive.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index ea2f5f9281a..0f7fcc0416c 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -25,6 +25,11 @@ module CiStatusHelper end end + def ci_status_for_statuseable(subject) + status = subject.try(:status) || 'not found' + status.humanize + end + def ci_icon_for_status(status) icon_name = case status diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 8a33cd1502c..402e37f4ec6 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -42,7 +42,7 @@ - unless pipeline.latest? - latest_pipeline = @project.pipeline_for(branch.name) %li - %span= latest_pipeline.status.humanize + %span= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 84135a6d049..a86561ca90b 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -22,7 +22,7 @@ - unless pipeline.latest? - latest_pipeline = @project.pipeline_for(@ref) %li - %span= latest_pipeline.status.humanize + %span= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index c9a0b74e47b..e482bb72df4 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -33,7 +33,7 @@ - unless pipeline.latest? - latest_pipeline = @project.pipeline_for(ref) %li - %span= latest_pipeline.status.humanize + %span= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 27e0cd7b1f9..b3bf18cfa50 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -20,7 +20,7 @@ - unless pipeline.latest? - latest_pipeline = project.pipeline_for(ref) %li - %span= latest_pipeline.status.humanize + %span= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li -- cgit v1.2.1 From ad320577595610ee1cb8f945cdfe6f739e9a0ebb Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 15 Aug 2016 19:07:42 -0500 Subject: Add unclickable state to running build artifacts --- app/assets/stylesheets/framework/dropdowns.scss | 6 ++++++ app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/repositories/_download_archive.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index e8eafa15899..cbbff49ad9c 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -176,6 +176,12 @@ .separator + .dropdown-header { padding-top: 2px; } + + .unclickable { + cursor: not-allowed; + padding: 5px 8px; + color: $dropdown-header-color; + } } .dropdown-menu-large { diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 402e37f4ec6..12b78be4be0 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -42,7 +42,7 @@ - unless pipeline.latest? - latest_pipeline = @project.pipeline_for(branch.name) %li - %span= ci_status_for_statuseable(latest_pipeline) + .unclickable= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index a86561ca90b..45998343fda 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -22,7 +22,7 @@ - unless pipeline.latest? - latest_pipeline = @project.pipeline_for(@ref) %li - %span= ci_status_for_statuseable(latest_pipeline) + .unclickable= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index e482bb72df4..48502498171 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -33,7 +33,7 @@ - unless pipeline.latest? - latest_pipeline = @project.pipeline_for(ref) %li - %span= ci_status_for_statuseable(latest_pipeline) + .unclickable= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index b3bf18cfa50..60f1e2cd2ee 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -20,7 +20,7 @@ - unless pipeline.latest? - latest_pipeline = project.pipeline_for(ref) %li - %span= ci_status_for_statuseable(latest_pipeline) + .unclickable= ci_status_for_statuseable(latest_pipeline) %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li -- cgit v1.2.1 From 11f840bfa5b93fdd0687c9c4f2a5a2e7abbc17ac Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 21:04:06 +0800 Subject: An empty line after guard, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13904931 --- app/models/merge_request.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index cb60b626e75..96fecf7712a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -676,6 +676,7 @@ class MergeRequest < ActiveRecord::Base def pipeline return unless diff_head_sha && source_project + @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end -- cgit v1.2.1 From f86a507745695f3b073d6edb6029836ab115765a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 22:10:10 +0800 Subject: Rename to latest_succeeded, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13908017 --- app/controllers/projects/artifacts_controller.rb | 2 +- app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/tags/_download.html.haml | 2 +- config/routes.rb | 7 ++++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index b7c395a01a3..5cc6d643b64 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -34,7 +34,7 @@ class Projects::ArtifactsController < Projects::ApplicationController redirect_to namespace_project_build_path(project.namespace, project, build) end - def search + def latest_succeeded path = params[:path] if %w[download browse file].include?(path) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 12b78be4be0..5634abf3641 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -46,7 +46,7 @@ %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li - = link_to search_namespace_project_artifacts_path(@project.namespace, @project, branch.name, 'download', job: job.name), rel: 'nofollow' do + = link_to latest_succeeded_namespace_project_artifacts_path(@project.namespace, @project, branch.name, 'download', job: job.name), rel: 'nofollow' do %span Download '#{job.name}' - if can_remove_branch?(@project, branch.name) diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 45998343fda..177946dcd42 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -26,5 +26,5 @@ %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li - = link_to search_namespace_project_artifacts_path(@project.namespace, @project, @ref, 'download', job: job.name), rel: 'nofollow' do + = link_to latest_succeeded_namespace_project_artifacts_path(@project.namespace, @project, @ref, 'download', job: job.name), rel: 'nofollow' do %span Download '#{job.name}' diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 60f1e2cd2ee..316d03dd12a 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -24,5 +24,5 @@ %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li - = link_to search_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do + = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do %span Download '#{job.name}' diff --git a/config/routes.rb b/config/routes.rb index 70bdb1d5beb..da10c5609f6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -765,9 +765,10 @@ Rails.application.routes.draw do resources :artifacts, only: [] do collection do - get :search, path: ':ref_name/*path', - format: false, - constraints: { ref_name: /.+/ } # could have / + get :latest_succeeded, + path: ':ref_name/*path', + format: false, + constraints: { ref_name: /.+/ } # could have / end end end -- cgit v1.2.1 From 1c88ed7a515141b7468b238a679e2ff5cf86e3f9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 16 Aug 2016 22:14:27 +0800 Subject: Not sure why missed this one --- app/views/projects/repositories/_download_archive.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 48502498171..c0a5909d3a6 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -37,5 +37,5 @@ %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li - = link_to search_namespace_project_artifacts_path(@project.namespace, @project, ref, 'download', job: job.name), rel: 'nofollow' do + = link_to latest_succeeded_namespace_project_artifacts_path(@project.namespace, @project, ref, 'download', job: job.name), rel: 'nofollow' do %span Download '#{job.name}' -- cgit v1.2.1 From 75df5f6c73670def1912150c6f3390ffdfadb17a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 17 Aug 2016 13:42:06 +0800 Subject: Fixed a missing rename --- spec/requests/projects/artifacts_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 61d5e3d9a7d..1c68ec9117b 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -25,7 +25,7 @@ describe Projects::ArtifactsController do def path_from_ref( ref = pipeline.ref, job = build.name, path = 'browse') - search_namespace_project_artifacts_path( + latest_succeeded_namespace_project_artifacts_path( project.namespace, project, ref, -- cgit v1.2.1 From ee33b3e6e84bb566e84062e70d45c6d84ace4ee7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 17 Aug 2016 17:06:31 +0800 Subject: Use partials for downloading artifacts button --- app/views/projects/branches/_branch.html.haml | 30 +++++++--------------- app/views/projects/buttons/_artifacts.html.haml | 14 ++++++++++ app/views/projects/buttons/_download.html.haml | 15 +---------- .../repositories/_download_archive.html.haml | 15 +---------- app/views/projects/tags/_download.html.haml | 15 +---------- 5 files changed, 26 insertions(+), 63 deletions(-) create mode 100644 app/views/projects/buttons/_artifacts.html.haml diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 5634abf3641..21ac675ffe0 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,27 +27,15 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare - - pipeline = @project.pipelines.latest_successful_for(branch.name) - - if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - .dropdown.inline.artifacts-btn - %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } - = icon('download') - %span.caret - %span.sr-only - Select Archive Format - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = @project.pipeline_for(branch.name) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_namespace_project_artifacts_path(@project.namespace, @project, branch.name, 'download', job: job.name), rel: 'nofollow' do - %span Download '#{job.name}' + - if @project.latest_successful_builds_for(branch.name).any? + .dropdown.inline.artifacts-btn + %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } + = icon('download') + %span.caret + %span.sr-only + Select Archive Format + %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + = render 'projects/buttons/artifacts', project: @project, ref: branch.name - if can_remove_branch?(@project, branch.name) = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do diff --git a/app/views/projects/buttons/_artifacts.html.haml b/app/views/projects/buttons/_artifacts.html.haml new file mode 100644 index 00000000000..a52677ebf0a --- /dev/null +++ b/app/views/projects/buttons/_artifacts.html.haml @@ -0,0 +1,14 @@ +- pipeline = project.pipelines.latest_successful_for(ref) +- if pipeline + - artifacts = pipeline.builds.latest.with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + - latest_pipeline = project.pipeline_for(ref) + %li + .unclickable= ci_status_for_statuseable(latest_pipeline) + %li.dropdown-header Previous Artifacts + - artifacts.each do |job| + %li + = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do + %span Download '#{job.name}' diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 177946dcd42..5e748c44b08 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -14,17 +14,4 @@ %li = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - pipeline = @project.pipelines.latest_successful_for(@ref) - - if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = @project.pipeline_for(@ref) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_namespace_project_artifacts_path(@project.namespace, @project, @ref, 'download', job: job.name), rel: 'nofollow' do - %span Download '#{job.name}' + = render 'projects/buttons/artifacts', project: @project, ref: @ref diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index c0a5909d3a6..4f40696a190 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -25,17 +25,4 @@ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do %i.fa.fa-download %span Download tar - - pipeline = @project.pipelines.latest_successful_for(ref) - - if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = @project.pipeline_for(ref) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_namespace_project_artifacts_path(@project.namespace, @project, ref, 'download', job: job.name), rel: 'nofollow' do - %span Download '#{job.name}' + = render 'projects/buttons/artifacts', project: @project, ref: ref diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml index 316d03dd12a..6985eb74ca7 100644 --- a/app/views/projects/tags/_download.html.haml +++ b/app/views/projects/tags/_download.html.haml @@ -12,17 +12,4 @@ %li = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do %span Download tar.gz - - pipeline = project.pipelines.latest_successful_for(ref) - - if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = project.pipeline_for(ref) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do - %span Download '#{job.name}' + = render 'projects/buttons/artifacts', project: project, ref: ref -- cgit v1.2.1 From 4fbe044b74aa6a24133732ef8a9bc5063ecef5dd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 17 Aug 2016 17:38:38 +0800 Subject: Use switch case in a helper, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_13988401 --- app/controllers/projects/artifacts_controller.rb | 10 +++------- app/helpers/gitlab_routing_helper.rb | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 5cc6d643b64..8261a73c642 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -35,14 +35,10 @@ class Projects::ArtifactsController < Projects::ApplicationController end def latest_succeeded - path = params[:path] + target_url = artifacts_action_url(params[:path], project, build) - if %w[download browse file].include?(path) - redirect_to send( - "#{path}_namespace_project_build_artifacts_url", - project.namespace, - project, - build) + if target_url + redirect_to(target_url) else render_404 end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 5386ddadc62..bc4d976ae68 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -149,4 +149,19 @@ module GitlabRoutingHelper def resend_invite_group_member_path(group_member, *args) resend_invite_group_group_member_path(group_member.source, group_member) end + + # Artifacts + + def artifacts_action_url(path, project, build) + args = [project.namespace, project, build] + + case path + when 'download' + download_namespace_project_build_artifacts_url(*args) + when 'browse' + browse_namespace_project_build_artifacts_url(*args) + when 'file' + file_namespace_project_build_artifacts_url(*args) + end + end end -- cgit v1.2.1 From e8b03baf6b67a14c0db6dbf3a1abaa4a6a173213 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 18 Aug 2016 15:31:20 +0800 Subject: Use path rather than URL because it should work for http 302: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14035941 --- app/controllers/projects/artifacts_controller.rb | 4 ++-- app/helpers/gitlab_routing_helper.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 8261a73c642..16ab7ec409d 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -35,10 +35,10 @@ class Projects::ArtifactsController < Projects::ApplicationController end def latest_succeeded - target_url = artifacts_action_url(params[:path], project, build) + target_path = artifacts_action_path(params[:path], project, build) if target_url - redirect_to(target_url) + redirect_to(target_path) else render_404 end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index bc4d976ae68..cd526f17b99 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -152,16 +152,16 @@ module GitlabRoutingHelper # Artifacts - def artifacts_action_url(path, project, build) + def artifacts_action_path(path, project, build) args = [project.namespace, project, build] case path when 'download' - download_namespace_project_build_artifacts_url(*args) + download_namespace_project_build_artifacts_path(*args) when 'browse' - browse_namespace_project_build_artifacts_url(*args) + browse_namespace_project_build_artifacts_path(*args) when 'file' - file_namespace_project_build_artifacts_url(*args) + file_namespace_project_build_artifacts_path(*args) end end end -- cgit v1.2.1 From 17d0406546885bedf2196c61a5991092b3fbe7c0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 18 Aug 2016 19:30:20 +0800 Subject: Not sure why I missed this renaming --- app/controllers/projects/artifacts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 16ab7ec409d..60e432d68d8 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -37,7 +37,7 @@ class Projects::ArtifactsController < Projects::ApplicationController def latest_succeeded target_path = artifacts_action_path(params[:path], project, build) - if target_url + if target_path redirect_to(target_path) else render_404 -- cgit v1.2.1 From b4398de5c5381a81f225c390e32f99d4e0e7d627 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 15 May 2016 00:50:15 +0100 Subject: Added new non-selectable selector exclusions to fix arrow key events, fixed the simulated clicking of a row and fixed the conflict between enter key form submit and enter key row selection Added bootstrap dropdown event triggers to invoke the open and close methods of the dropdown, allowing for the binding of array key events Added #17465 fix entry to CHANGELOG Fixed multi-dropdown selected row index conflict Fixed whitespace diff Added padding to the dropdown content iterative scroll as well as new conditional scrolls to scroll all the way to the top when the first item of a list is selected and to scroll all the way to the bottom when the last item of a list is selected Added conditionals to the enable and disable autocomplete methods to stop multiple invocations without any enabled/disabled state change Fixes some incorrect firing of requests. The dropdown box was invoking a new query every time it closed and the GitLabDropdownRemote callback was invoking a new query which was causing the dropdown double render issue. Added .selectable css class to dropdown list items that are not dividers or headers and altered selectors to account for that. Moved scroll padding Number to variable. Removed unused method Started Dropdown tests Added fixture and began first test Almost finished, navigation done, action and close needed YAY. TESTS DONE. Altered test and fixed click started removing selectable class use Fixed as reviewed altered selection method Fixed autocomplete shutting dropdown on arrow key use patched XSS vulns updated tests f Added click fixes --- app/assets/javascripts/gl_dropdown.js.coffee | 657 +++++++++++++++++++++ .../javascripts/search_autocomplete.js.coffee | 349 +++++++++++ spec/javascripts/fixtures/gl_dropdown.html.haml | 16 + spec/javascripts/gl_dropdown_spec.js.coffee | 96 +++ 4 files changed, 1118 insertions(+) create mode 100644 app/assets/javascripts/gl_dropdown.js.coffee create mode 100644 app/assets/javascripts/search_autocomplete.js.coffee create mode 100644 spec/javascripts/fixtures/gl_dropdown.html.haml create mode 100644 spec/javascripts/gl_dropdown_spec.js.coffee diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee new file mode 100644 index 00000000000..e096effaade --- /dev/null +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -0,0 +1,657 @@ +class GitLabDropdownFilter + BLUR_KEYCODES = [27, 40] + ARROW_KEY_CODES = [38, 40] + HAS_VALUE_CLASS = "has-value" + + constructor: (@input, @options) -> + { + @filterInputBlur = true + } = @options + + $inputContainer = @input.parent() + $clearButton = $inputContainer.find('.js-dropdown-input-clear') + + @indeterminateIds = [] + + # Clear click + $clearButton.on 'click', (e) => + e.preventDefault() + e.stopPropagation() + @input + .val('') + .trigger('keyup') + .focus() + + # Key events + timeout = "" + @input.on "keyup", (e) => + keyCode = e.which + + return if ARROW_KEY_CODES.indexOf(keyCode) >= 0 + + if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS + $inputContainer.addClass HAS_VALUE_CLASS + else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS + $inputContainer.removeClass HAS_VALUE_CLASS + + if keyCode is 13 + return false + + # Only filter asynchronously only if option remote is set + if @options.remote + clearTimeout timeout + timeout = setTimeout => + blur_field = @shouldBlur keyCode + + if blur_field and @filterInputBlur + @input.blur() + + @options.query @input.val(), (data) => + @options.callback(data) + , 250 + else + @filter @input.val() + + shouldBlur: (keyCode) -> + return BLUR_KEYCODES.indexOf(keyCode) >= 0 + + filter: (search_text) -> + @options.onFilter(search_text) if @options.onFilter + data = @options.data() + + if data? and not @options.filterByText + results = data + + if search_text isnt '' + # When data is an array of objects therefore [object Array] e.g. + # [ + # { prop: 'foo' }, + # { prop: 'baz' } + # ] + if _.isArray(data) + results = fuzzaldrinPlus.filter(data, search_text, + key: @options.keys + ) + else + # If data is grouped therefore an [object Object]. e.g. + # { + # groupName1: [ + # { prop: 'foo' }, + # { prop: 'baz' } + # ], + # groupName2: [ + # { prop: 'abc' }, + # { prop: 'def' } + # ] + # } + if gl.utils.isObject data + results = {} + for key, group of data + tmp = fuzzaldrinPlus.filter(group, search_text, + key: @options.keys + ) + + if tmp.length + results[key] = tmp.map (item) -> item + + @options.callback results + else + elements = @options.elements() + + if search_text + elements.each -> + $el = $(@) + matches = fuzzaldrinPlus.match($el.text().trim(), search_text) + + unless $el.is('.dropdown-header') + if matches.length + $el.show() + else + $el.hide() + else + elements.show() + +class GitLabDropdownRemote + constructor: (@dataEndpoint, @options) -> + + execute: -> + if typeof @dataEndpoint is "string" + @fetchData() + else if typeof @dataEndpoint is "function" + if @options.beforeSend + @options.beforeSend() + + # Fetch the data by calling the data funcfion + @dataEndpoint "", (data) => + if @options.success + @options.success(data) + + if @options.beforeSend + @options.beforeSend() + + # Fetch the data through ajax if the data is a string + fetchData: -> + $.ajax( + url: @dataEndpoint, + dataType: @options.dataType, + beforeSend: => + if @options.beforeSend + @options.beforeSend() + success: (data) => + if @options.success + @options.success(data) + ) + +class GitLabDropdown + LOADING_CLASS = "is-loading" + PAGE_TWO_CLASS = "is-page-two" + ACTIVE_CLASS = "is-active" + INDETERMINATE_CLASS = "is-indeterminate" + NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link' + SELECTABLE_CLASSES = ".dropdown-content li:not(#{NON_SELECTABLE_CLASSES})" + FILTER_INPUT = '.dropdown-input .dropdown-input-field' + currentIndex = -1 + CURSOR_SELECT_SCROLL_PADDING = 5 + + constructor: (@el, @options) -> + self = @ + selector = $(@el).data "target" + @dropdown = if selector? then $(selector) else $(@el).parent() + + # Set Defaults + { + # If no input is passed create a default one + @filterInput = @getElement(FILTER_INPUT) + @highlight = false + @filterInputBlur = true + } = @options + + self = @ + + # If selector was passed + if _.isString(@filterInput) + @filterInput = @getElement(@filterInput) + + searchFields = if @options.search then @options.search.fields else [] + + if @options.data + # If we provided data + # data could be an array of objects or a group of arrays + if _.isObject(@options.data) and not _.isFunction(@options.data) + @fullData = @options.data + @parseData @options.data + else + # Remote data + @remote = new GitLabDropdownRemote @options.data, { + dataType: @options.dataType, + beforeSend: @toggleLoading.bind(@) + success: (data) => + @fullData = data + + # Reset selected row index on new data + currentIndex = -1 + @parseData @fullData + + @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input + } + + # Init filterable + if @options.filterable + @filter = new GitLabDropdownFilter @filterInput, + filterInputBlur: @filterInputBlur + filterByText: @options.filterByText + onFilter: @options.onFilter + remote: @options.filterRemote + query: @options.data + keys: searchFields + elements: => + selector = SELECTABLE_CLASSES + + if @dropdown.find('.dropdown-toggle-page').length + selector = ".dropdown-page-one #{selector}" + + return $(selector) + data: => + return @fullData + callback: (data) => + @parseData data + + unless @filterInput.val() is '' + selector = '.dropdown-content li:not(.divider):visible' + + if @dropdown.find('.dropdown-toggle-page').length + selector = ".dropdown-page-one #{selector}" + + $(selector, @dropdown) + .first() + .find('a') + .addClass('is-focused') + + currentIndex = 0 + + + # Event listeners + + @dropdown.on "shown.bs.dropdown", @opened + @dropdown.on "hidden.bs.dropdown", @hidden + $(@el).on "update.label", @updateLabel + @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate + @dropdown.on 'keyup', (e) => + if e.which is 27 # Escape key + $('.dropdown-menu-close', @dropdown).trigger 'click' + @dropdown.on 'blur', 'a', (e) => + if e.relatedTarget? + $relatedTarget = $(e.relatedTarget) + $dropdownMenu = $relatedTarget.closest('.dropdown-menu') + + if $dropdownMenu.length is 0 + @dropdown.removeClass('open') + + if @dropdown.find(".dropdown-toggle-page").length + @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => + e.preventDefault() + e.stopPropagation() + + @togglePage() + + if @options.selectable + selector = ".dropdown-content a" + + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one .dropdown-content a" + + @dropdown.on "click", selector, (e) -> + $el = $(@) + selected = self.rowClicked $el + + if self.options.clicked + self.options.clicked(selected, $el, e) + + $el.trigger('blur') + + # Finds an element inside wrapper element + getElement: (selector) -> + @dropdown.find selector + + toggleLoading: -> + $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS + + togglePage: -> + menu = $('.dropdown-menu', @dropdown) + + if menu.hasClass(PAGE_TWO_CLASS) + if @remote + @remote.execute() + + menu.toggleClass PAGE_TWO_CLASS + + # Focus first visible input on active page + @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus() + + parseData: (data) -> + @renderedData = data + + if @options.filterable and data.length is 0 + # render no matching results + html = [@noResults()] + else + # Handle array groups + if gl.utils.isObject data + html = [] + for name, groupData of data + # Add header for each group + html.push(@renderItem(header: name, name)) + + @renderData(groupData, name) + .map (item) -> + html.push item + else + # Render each row + html = @renderData(data) + + # Render the full menu + full_html = @renderMenu(html) + + @appendMenu(full_html) + + renderData: (data, group = false) -> + data.map (obj, index) => + return @renderItem(obj, group, index) + + shouldPropagate: (e) => + if @options.multiSelect + $target = $(e.target) + + if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link') + e.stopPropagation() + return false + else + return true + + opened: => + @resetRows() + @addArrowKeyEvent() + + if @options.setIndeterminateIds + @options.setIndeterminateIds.call(@) + + if @options.setActiveIds + @options.setActiveIds.call(@) + + # Makes indeterminate items effective + if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update') + @parseData @fullData + + contentHtml = $('.dropdown-content', @dropdown).html() + if @remote && contentHtml is "" + @remote.execute() + + if @options.filterable + @filterInput.focus() + + @dropdown.trigger('shown.gl.dropdown') + + hidden: (e) => + @resetRows() + @removeArrayKeyEvent() + + $input = @dropdown.find(".dropdown-input-field") + + if @options.filterable + $input + .blur() + .val("") + + # Triggering 'keyup' will re-render the dropdown which is not always required + # specially if we want to keep the state of the dropdown needed for bulk-assignment + if not @options.persistWhenHide + $input.trigger("keyup") + + if @dropdown.find(".dropdown-toggle-page").length + $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS + + if @options.hidden + @options.hidden.call(@,e) + + @dropdown.trigger('hidden.gl.dropdown') + + + # Render the full menu + renderMenu: (html) -> + menu_html = "" + + if @options.renderMenu + menu_html = @options.renderMenu(html) + else + menu_html = $('
    ') + .append(html) + + return menu_html + + # Append the menu into the dropdown + appendMenu: (html) -> + selector = '.dropdown-content' + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one .dropdown-content" + $(selector, @dropdown) + .empty() + .append(html) + + # Render the row + renderItem: (data, group = false, index = false) -> + html = "" + + # Divider + return "
  • " if data is "divider" + + # Separator is a full-width divider + return "
  • " if data is "separator" + + # Header + return _.template("") header: data.header if data.header? + + if @options.renderRow + # Call the render function + html = @options.renderRow.call(@options, data, @) + else + if not selected + value = if @options.id then @options.id(data) else data.id + fieldName = @options.fieldName + field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") + if field.length + selected = true + + # Set URL + if @options.url? + url = @options.url(data) + else + url = if data.url? then data.url else '#' + + # Set Text + if @options.text? + text = @options.text(data) + else + text = if data.text? then data.text else '' + + cssClass = "" + + if selected + cssClass = "is-active" + + if @highlight + text = @highlightTextMatches(text, @filterInput.val()) + + if group + groupAttrs = "data-group=#{group} data-index=#{index}" + else + groupAttrs = '' + html = _.template("
  • + class='<%- cssClass %>'> + <%= text %> + +
  • ")({ + url: url + groupAttrs: groupAttrs + cssClass: cssClass + text: text + }) + + return html + + highlightTextMatches: (text, term) -> + occurrences = fuzzaldrinPlus.match(text, term) + text.split('').map((character, i) -> + if i in occurrences then "#{character}" else character + ).join('') + + noResults: -> + html = "" + + rowClicked: (el) -> + fieldName = @options.fieldName + isInput = $(@el).is('input') + + if @renderedData + groupName = el.data('group') + if groupName + selectedIndex = el.data('index') + selectedObject = @renderedData[groupName][selectedIndex] + else + selectedIndex = el.closest('li').index() + selectedObject = @renderedData[selectedIndex] + + value = if @options.id then @options.id(selectedObject, el) else selectedObject.id + + if isInput + field = $(@el) + else + field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") + + if el.hasClass(ACTIVE_CLASS) + el.removeClass(ACTIVE_CLASS) + + if isInput + field.val('') + else + field.remove() + + # Toggle the dropdown label + if @options.toggleLabel + @updateLabel(selectedObject, el, @) + else + selectedObject + else if el.hasClass(INDETERMINATE_CLASS) + el.addClass ACTIVE_CLASS + el.removeClass INDETERMINATE_CLASS + + if not value? + field.remove() + + if not field.length and fieldName + @addInput(fieldName, value) + + return selectedObject + else + if not @options.multiSelect or el.hasClass('dropdown-clear-active') + @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS + + unless isInput + @dropdown.parent().find("input[name='#{fieldName}']").remove() + + if !value? + field.remove() + + # Toggle active class for the tick mark + el.addClass ACTIVE_CLASS + + # Toggle the dropdown label + if @options.toggleLabel + @updateLabel(selectedObject, el, @) + if value? + if !field.length and fieldName + @addInput(fieldName, value) + else + field + .val value + .trigger 'change' + + return selectedObject + + addInput: (fieldName, value)-> + # Create hidden input for form + $input = $('').attr('type', 'hidden') + .attr('name', fieldName) + .val(value) + + if @options.inputId? + $input.attr('id', @options.inputId) + + @dropdown.before $input + + selectRowAtIndex: (e, index) -> + # Dropdown list item link selector, excluding non-selectable list items + selector = "#{SELECTABLE_CLASSES}:eq(#{index}) a" + + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one #{selector}" + + # simulate a click on the first link + $el = $(selector, @dropdown) + if $el.length + e.preventDefault() + e.stopImmediatePropagation() + $el.first().trigger 'click' + href = $el.attr 'href' + Turbolinks.visit(href) if href + + addArrowKeyEvent: -> + ARROW_KEY_CODES = [38, 40] + $input = @dropdown.find(".dropdown-input-field") + + # Dropdown list item selector, excluding non-selectable list items + selector = SELECTABLE_CLASSES + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one #{selector}" + + $('body').on 'keydown', (e) => + currentKeyCode = e.which + + if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0 + e.preventDefault() + e.stopImmediatePropagation() + + PREV_INDEX = currentIndex + $listItems = $(selector, @dropdown) + + # if @options.filterable + # $input.blur() + + if currentKeyCode is 40 + # Move down + currentIndex += 1 if currentIndex < ($listItems.length - 1) + else if currentKeyCode is 38 + # Move up + currentIndex -= 1 if currentIndex > 0 + + @highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX + + return false + + # If enter is pressed and a row is highlighted, select it + if currentKeyCode is 13 and currentIndex != -1 + e.preventDefault() + e.stopImmediatePropagation() + @selectRowAtIndex e, currentIndex + + removeArrayKeyEvent: -> + $('body').off 'keydown' + + # Resets the currently selected item row index and removes all highlights + resetRows: -> + currentIndex = -1 + $('.is-focused', @dropdown).removeClass 'is-focused' + + highlightRowAtIndex: ($listItems, index) -> + # Remove the class for the previously focused row + $('.is-focused', @dropdown).removeClass 'is-focused' + + # Update the class for the row at the specific index + $listItem = $listItems.eq(index) + $listItem.find('a:first-child').addClass "is-focused" + + # Dropdown content scroll area + $dropdownContent = $listItem.closest('.dropdown-content') + dropdownScrollTop = $dropdownContent.scrollTop() + dropdownContentHeight = $dropdownContent.outerHeight() + dropdownContentTop = $dropdownContent.prop('offsetTop') + dropdownContentBottom = dropdownContentTop + dropdownContentHeight + + # Get the offset bottom of the list item + listItemHeight = $listItem.outerHeight() + listItemTop = $listItem.prop('offsetTop') + listItemBottom = listItemTop + listItemHeight + + if index is 0 + # If this is the first item in the list, scroll to the top + $dropdownContent.scrollTop(0) + else if index is $listItems.length - 1 + # If this is the last item in the list, scroll to the bottom + $dropdownContent.scrollTop($dropdownContent[0].scrollHeight) + else if listItemBottom > dropdownContentBottom + dropdownScrollTop + # Scroll the dropdown content down with a little padding + $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING) + else if listItemTop < dropdownContentTop + dropdownScrollTop + # Scroll the dropdown content up with a little padding + $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING) + + updateLabel: (selected = null, el = null, instance = null) => + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance) + +$.fn.glDropdown = (opts) -> + return @.each -> + if (!$.data @, 'glDropdown') + $.data(@, 'glDropdown', new GitLabDropdown @, opts) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee new file mode 100644 index 00000000000..a3baea1ae01 --- /dev/null +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -0,0 +1,349 @@ +class @SearchAutocomplete + + KEYCODE = + ESCAPE: 27 + BACKSPACE: 8 + ENTER: 13 + UP: 38 + DOWN: 40 + + constructor: (opts = {}) -> + { + @wrap = $('.search') + + @optsEl = @wrap.find('.search-autocomplete-opts') + @autocompletePath = @optsEl.data('autocomplete-path') + @projectId = @optsEl.data('autocomplete-project-id') || '' + @projectRef = @optsEl.data('autocomplete-project-ref') || '' + + } = opts + + # Dropdown Element + @dropdown = @wrap.find('.dropdown') + @dropdownContent = @dropdown.find('.dropdown-content') + + @locationBadgeEl = @getElement('.location-badge') + @scopeInputEl = @getElement('#scope') + @searchInput = @getElement('.search-input') + @projectInputEl = @getElement('#search_project_id') + @groupInputEl = @getElement('#group_id') + @searchCodeInputEl = @getElement('#search_code') + @repositoryInputEl = @getElement('#repository_ref') + @clearInput = @getElement('.js-clear-input') + + @saveOriginalState() + + # Only when user is logged in + @createAutocomplete() if gon.current_user_id + + @searchInput.addClass('disabled') + + @saveTextLength() + + @bindEvents() + + # Finds an element inside wrapper element + getElement: (selector) -> + @wrap.find(selector) + + saveOriginalState: -> + @originalState = @serializeState() + + saveTextLength: -> + @lastTextLength = @searchInput.val().length + + createAutocomplete: -> + @searchInput.glDropdown + filterInputBlur: false + filterable: true + filterRemote: true + highlight: true + enterCallback: false + filterInput: 'input#search' + search: + fields: ['text'] + data: @getData.bind(@) + selectable: true + clicked: @onClick.bind(@) + + getData: (term, callback) -> + _this = @ + + unless term + if contents = @getCategoryContents() + @searchInput.data('glDropdown').filter.options.callback contents + @enableAutocomplete() + + return + + # Prevent multiple ajax calls + return if @loadingSuggestions + + @loadingSuggestions = true + + jqXHR = $.get(@autocompletePath, { + project_id: @projectId + project_ref: @projectRef + term: term + }, (response) -> + # Hide dropdown menu if no suggestions returns + if !response.length + _this.disableAutocomplete() + return + + data = [] + + # List results + firstCategory = true + for suggestion in response + + # Add group header before list each group + if lastCategory isnt suggestion.category + data.push 'separator' if !firstCategory + + firstCategory = false if firstCategory + + data.push + header: suggestion.category + + lastCategory = suggestion.category + + data.push + id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}" + category: suggestion.category + text: suggestion.label + url: suggestion.url + + # Add option to proceed with the search + if data.length + data.push('separator') + data.push + text: "Result name contains \"#{term}\"" + url: "/search?\ + search=#{term}\ + &project_id=#{_this.projectInputEl.val()}\ + &group_id=#{_this.groupInputEl.val()}" + + callback(data) + ).always -> + _this.loadingSuggestions = false + + + getCategoryContents: -> + + userId = gon.current_user_id + { utils, projectOptions, groupOptions, dashboardOptions } = gl + + if utils.isInGroupsPage() and groupOptions + options = groupOptions[utils.getGroupSlug()] + + else if utils.isInProjectPage() and projectOptions + options = projectOptions[utils.getProjectSlug()] + + else if dashboardOptions + options = dashboardOptions + + { issuesPath, mrPath, name } = options + + items = [ + { header: "#{name}" } + { text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" } + { text: "Issues I've created", url: "#{issuesPath}/?author_id=#{userId}" } + 'separator' + { text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" } + { text: "Merge requests I've created", url: "#{mrPath}/?author_id=#{userId}" } + ] + + items.splice 0, 1 unless name + + return items + + + serializeState: -> + { + # Search Criteria + search_project_id: @projectInputEl.val() + group_id: @groupInputEl.val() + search_code: @searchCodeInputEl.val() + repository_ref: @repositoryInputEl.val() + scope: @scopeInputEl.val() + + # Location badge + _location: @locationBadgeEl.text() + } + + bindEvents: -> + @searchInput.on 'keydown', @onSearchInputKeyDown + @searchInput.on 'keyup', @onSearchInputKeyUp + @searchInput.on 'click', @onSearchInputClick + @searchInput.on 'focus', @onSearchInputFocus + @searchInput.on 'blur', @onSearchInputBlur + @clearInput.on 'click', @onClearInputClick + @locationBadgeEl.on 'click', => + @searchInput.focus() + + enableAutocomplete: -> + # No need to enable anything if user is not logged in + return if !gon.current_user_id + + unless @dropdown.hasClass('open') + _this = @ + @loadingSuggestions = false + + # If not enabled already, enable + if not @dropdown.hasClass('open') + # Open dropdown and invoke its opened() method + @dropdown.addClass('open') + .trigger('shown.bs.dropdown') + @searchInput.removeClass('disabled') + + onSearchInputKeyDown: => + # Saves last length of the entered text + @saveTextLength() + + onSearchInputKeyUp: (e) => + switch e.keyCode + when KEYCODE.BACKSPACE + # when trying to remove the location badge + if @lastTextLength is 0 and @badgePresent() + @removeLocationBadge() + + # When removing the last character and no badge is present + if @lastTextLength is 1 + @disableAutocomplete() + + # When removing any character from existin value + if @lastTextLength > 1 + @enableAutocomplete() + + when KEYCODE.ESCAPE + @restoreOriginalState() + + # Close autocomplete on enter + when KEYCODE.ENTER + @disableAutocomplete() + + when KEYCODE.UP, KEYCODE.DOWN + return + + else + # Handle the case when deleting the input value other than backspace + # e.g. Pressing ctrl + backspace or ctrl + x + if @searchInput.val() is '' + @disableAutocomplete() + else + # We should display the menu only when input is not empty + @enableAutocomplete() + + @wrap.toggleClass 'has-value', !!e.target.value + + # Avoid falsy value to be returned + return + + onSearchInputClick: (e) => + # Prevents closing the dropdown menu + e.stopImmediatePropagation() + + onSearchInputFocus: => + @isFocused = true + @wrap.addClass('search-active') + + @getData() if @getValue() is '' + + + getValue: -> return @searchInput.val() + + + onClearInputClick: (e) => + e.preventDefault() + @searchInput.val('').focus() + + onSearchInputBlur: (e) => + @isFocused = false + @wrap.removeClass('search-active') + + # If input is blank then restore state + if @searchInput.val() is '' + @restoreOriginalState() + + addLocationBadge: (item) -> + category = if item.category? then "#{item.category}: " else '' + value = if item.value? then item.value else '' + + badgeText = "#{category}#{value}" + @locationBadgeEl.text(badgeText).show() + @wrap.addClass('has-location-badge') + + + hasLocationBadge: -> return @wrap.is '.has-location-badge' + + + restoreOriginalState: -> + inputs = Object.keys @originalState + + for input in inputs + @getElement("##{input}").val(@originalState[input]) + + if @originalState._location is '' + @locationBadgeEl.hide() + else + @addLocationBadge( + value: @originalState._location + ) + + badgePresent: -> + @locationBadgeEl.length + + resetSearchState: -> + inputs = Object.keys @originalState + + for input in inputs + + # _location isnt a input + break if input is '_location' + + @getElement("##{input}").val('') + + + removeLocationBadge: -> + + @locationBadgeEl.hide() + @resetSearchState() + @wrap.removeClass('has-location-badge') + @disableAutocomplete() + + + disableAutocomplete: -> + # If not disabled already, disable + if not @searchInput.hasClass('disabled') && @dropdown.hasClass('open') + @searchInput.addClass('disabled') + # Close dropdown and invoke its hidden() method + @dropdown.removeClass('open') + .trigger('hidden.bs.dropdown') + @restoreMenu() + + restoreMenu: -> + html = "" + @dropdownContent.html(html) + + onClick: (item, $el, e) -> + if location.pathname.indexOf(item.url) isnt -1 + e.preventDefault() + if not @badgePresent + if item.category is 'Projects' + @projectInputEl.val(item.id) + @addLocationBadge( + value: 'This project' + ) + + if item.category is 'Groups' + @groupInputEl.val(item.id) + @addLocationBadge( + value: 'This group' + ) + + $el.removeClass('is-active') + @disableAutocomplete() + @searchInput.val('').focus() diff --git a/spec/javascripts/fixtures/gl_dropdown.html.haml b/spec/javascripts/fixtures/gl_dropdown.html.haml new file mode 100644 index 00000000000..a20390c08ee --- /dev/null +++ b/spec/javascripts/fixtures/gl_dropdown.html.haml @@ -0,0 +1,16 @@ +%div + .dropdown.inline + %button#js-project-dropdown.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + Projects + %i.fa.fa-chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle + .dropdown-menu.dropdown-select.dropdown-menu-selectable + .dropdown-title + %span Go to project + %button.dropdown-title-button.dropdown-menu-close{aria: {label: 'Close'}} + %i.fa.fa-times.dropdown-menu-close-icon + .dropdown-input + %input.dropdown-input-field{type: 'search', placeholder: 'Filter results'} + %i.fa.fa-search.dropdown-input-search + .dropdown-content + .dropdown-loading + %i.fa.fa-spinner.fa-spin diff --git a/spec/javascripts/gl_dropdown_spec.js.coffee b/spec/javascripts/gl_dropdown_spec.js.coffee new file mode 100644 index 00000000000..46a0af32a7d --- /dev/null +++ b/spec/javascripts/gl_dropdown_spec.js.coffee @@ -0,0 +1,96 @@ +#= require jquery +#= require gl_dropdown +#= require turbolinks +#= require lib/utils/common_utils +#= require lib/utils/type_utility + +NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link' +ITEM_SELECTOR = ".dropdown-content li:not(#{NON_SELECTABLE_CLASSES})" +FOCUSED_ITEM_SELECTOR = ITEM_SELECTOR + ' a.is-focused' + +ARROW_KEYS = + DOWN: 40 + UP: 38 + ENTER: 13 + ESC: 27 + +navigateWithKeys = (direction, steps, cb, i) -> + i = i || 0 + $('body').trigger + type: 'keydown' + which: ARROW_KEYS[direction.toUpperCase()] + keyCode: ARROW_KEYS[direction.toUpperCase()] + i++ + if i <= steps + navigateWithKeys direction, steps, cb, i + else + cb() + +initDropdown = -> + @dropdownContainerElement = $('.dropdown.inline') + @dropdownMenuElement = $('.dropdown-menu', @dropdownContainerElement) + @projectsData = fixture.load('projects.json')[0] + @dropdownButtonElement = $('#js-project-dropdown', @dropdownContainerElement).glDropdown + selectable: true + data: @projectsData + text: (project) -> + (project.name_with_namespace or project.name) + id: (project) -> + project.id + +describe 'Dropdown', -> + fixture.preload 'gl_dropdown.html' + fixture.preload 'projects.json' + + beforeEach -> + fixture.load 'gl_dropdown.html' + initDropdown.call this + + afterEach -> + $('body').unbind 'keydown' + @dropdownContainerElement.unbind 'keyup' + + it 'should open on click', -> + expect(@dropdownContainerElement).not.toHaveClass 'open' + @dropdownButtonElement.click() + expect(@dropdownContainerElement).toHaveClass 'open' + + describe 'that is open', -> + beforeEach -> + @dropdownButtonElement.click() + + it 'should select a following item on DOWN keypress', -> + expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 0 + randomIndex = Math.floor(Math.random() * (@projectsData.length - 1)) + 0 + navigateWithKeys 'down', randomIndex, => + expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 1 + expect($("#{ITEM_SELECTOR}:eq(#{randomIndex}) a", @dropdownMenuElement)).toHaveClass 'is-focused' + + it 'should select a previous item on UP keypress', -> + expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 0 + navigateWithKeys 'down', (@projectsData.length - 1), => + expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 1 + randomIndex = Math.floor(Math.random() * (@projectsData.length - 2)) + 0 + navigateWithKeys 'up', randomIndex, => + expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 1 + expect($("#{ITEM_SELECTOR}:eq(#{((@projectsData.length - 2) - randomIndex)}) a", @dropdownMenuElement)).toHaveClass 'is-focused' + + it 'should click the selected item on ENTER keypress', -> + expect(@dropdownContainerElement).toHaveClass 'open' + randomIndex = Math.floor(Math.random() * (@projectsData.length - 1)) + 0 + navigateWithKeys 'down', randomIndex, => + spyOn(Turbolinks, 'visit').and.stub() + navigateWithKeys 'enter', null, => + link = $("#{ITEM_SELECTOR}:eq(#{randomIndex}) a", @dropdownMenuElement) + expect(link).toHaveClass 'is-active' + if link.attr 'href' + expect(Turbolinks.visit).toHaveBeenCalledWith link.attr 'href' + expect(@dropdownContainerElement).not.toHaveClass 'open' + + it 'should close on ESC keypress', -> + expect(@dropdownContainerElement).toHaveClass 'open' + @dropdownContainerElement.trigger + type: 'keyup' + which: ARROW_KEYS.ESC + keyCode: ARROW_KEYS.ESC + expect(@dropdownContainerElement).not.toHaveClass 'open' -- cgit v1.2.1 From 85cd73c768b57aa13b1cd6fc75b5c855b0b8fe89 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Thu, 7 Jul 2016 15:08:34 +0100 Subject: improved turbolinks.visit conditional --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index e096effaade..7a4224d9032 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -566,7 +566,7 @@ class GitLabDropdown e.stopImmediatePropagation() $el.first().trigger 'click' href = $el.attr 'href' - Turbolinks.visit(href) if href + Turbolinks.visit(href) if href and href isnt '#' addArrowKeyEvent: -> ARROW_KEY_CODES = [38, 40] -- cgit v1.2.1 From 40d869eefa2c491446b6aa75ec1337add6ce3e4a Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Thu, 7 Jul 2016 22:18:40 +0100 Subject: Review changes and final formatting --- app/assets/javascripts/gl_dropdown.js.coffee | 255 ++++++++++++--------------- 1 file changed, 115 insertions(+), 140 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 7a4224d9032..4e439e0de84 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -1,7 +1,7 @@ class GitLabDropdownFilter BLUR_KEYCODES = [27, 40] ARROW_KEY_CODES = [38, 40] - HAS_VALUE_CLASS = "has-value" + HAS_VALUE_CLASS = 'has-value' constructor: (@input, @options) -> { @@ -9,7 +9,7 @@ class GitLabDropdownFilter } = @options $inputContainer = @input.parent() - $clearButton = $inputContainer.find('.js-dropdown-input-clear') + $clearButton = $inputContainer.find '.js-dropdown-input-clear' @indeterminateIds = [] @@ -18,24 +18,23 @@ class GitLabDropdownFilter e.preventDefault() e.stopPropagation() @input - .val('') - .trigger('keyup') + .val '' + .trigger 'keyup' .focus() # Key events - timeout = "" - @input.on "keyup", (e) => + timeout = '' + @input.on 'keyup', (e) => keyCode = e.which return if ARROW_KEY_CODES.indexOf(keyCode) >= 0 - if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS + if @input.val() isnt '' and not $inputContainer.hasClass HAS_VALUE_CLASS $inputContainer.addClass HAS_VALUE_CLASS - else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS + else if @input.val() is '' and $inputContainer.hasClass HAS_VALUE_CLASS $inputContainer.removeClass HAS_VALUE_CLASS - if keyCode is 13 - return false + return false if keyCode is 13 # Only filter asynchronously only if option remote is set if @options.remote @@ -47,13 +46,13 @@ class GitLabDropdownFilter @input.blur() @options.query @input.val(), (data) => - @options.callback(data) + @options.callback data , 250 else @filter @input.val() shouldBlur: (keyCode) -> - return BLUR_KEYCODES.indexOf(keyCode) >= 0 + BLUR_KEYCODES.indexOf(keyCode) >= 0 filter: (search_text) -> @options.onFilter(search_text) if @options.onFilter @@ -69,9 +68,8 @@ class GitLabDropdownFilter # { prop: 'baz' } # ] if _.isArray(data) - results = fuzzaldrinPlus.filter(data, search_text, + results = fuzzaldrinPlus.filter data, search_text, key: @options.keys - ) else # If data is grouped therefore an [object Object]. e.g. # { @@ -87,9 +85,8 @@ class GitLabDropdownFilter if gl.utils.isObject data results = {} for key, group of data - tmp = fuzzaldrinPlus.filter(group, search_text, + tmp = fuzzaldrinPlus.filter group, search_text, key: @options.keys - ) if tmp.length results[key] = tmp.map (item) -> item @@ -100,10 +97,10 @@ class GitLabDropdownFilter if search_text elements.each -> - $el = $(@) - matches = fuzzaldrinPlus.match($el.text().trim(), search_text) + $el = $(this) + matches = fuzzaldrinPlus.match $el.text().trim(), search_text - unless $el.is('.dropdown-header') + unless $el.is '.dropdown-header' if matches.length $el.show() else @@ -115,38 +112,31 @@ class GitLabDropdownRemote constructor: (@dataEndpoint, @options) -> execute: -> - if typeof @dataEndpoint is "string" + if typeof @dataEndpoint is 'string' @fetchData() - else if typeof @dataEndpoint is "function" - if @options.beforeSend - @options.beforeSend() + else if typeof @dataEndpoint is 'function' + @options.beforeSend() if @options.beforeSend # Fetch the data by calling the data funcfion - @dataEndpoint "", (data) => - if @options.success - @options.success(data) - - if @options.beforeSend - @options.beforeSend() + @dataEndpoint '', (data) => + @options.success(data) if @options.success + @options.beforeSend() if @options.beforeSend # Fetch the data through ajax if the data is a string fetchData: -> - $.ajax( + $.ajax url: @dataEndpoint, dataType: @options.dataType, beforeSend: => - if @options.beforeSend - @options.beforeSend() + @options.beforeSend() if @options.beforeSend success: (data) => - if @options.success - @options.success(data) - ) + @options.success(data) if @options.success class GitLabDropdown - LOADING_CLASS = "is-loading" - PAGE_TWO_CLASS = "is-page-two" - ACTIVE_CLASS = "is-active" - INDETERMINATE_CLASS = "is-indeterminate" + LOADING_CLASS = 'is-loading' + PAGE_TWO_CLASS = 'is-page-two' + ACTIVE_CLASS = 'is-active' + INDETERMINATE_CLASS = 'is-indeterminate' NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link' SELECTABLE_CLASSES = ".dropdown-content li:not(#{NON_SELECTABLE_CLASSES})" FILTER_INPUT = '.dropdown-input .dropdown-input-field' @@ -154,37 +144,36 @@ class GitLabDropdown CURSOR_SELECT_SCROLL_PADDING = 5 constructor: (@el, @options) -> - self = @ - selector = $(@el).data "target" + self = this + selector = $(@el).data 'target' @dropdown = if selector? then $(selector) else $(@el).parent() # Set Defaults { # If no input is passed create a default one - @filterInput = @getElement(FILTER_INPUT) + @filterInput = @getElement FILTER_INPUT @highlight = false @filterInputBlur = true } = @options - self = @ + self = this # If selector was passed - if _.isString(@filterInput) - @filterInput = @getElement(@filterInput) + @filterInput = @getElement(@filterInput) if _.isString @filterInput searchFields = if @options.search then @options.search.fields else [] if @options.data # If we provided data # data could be an array of objects or a group of arrays - if _.isObject(@options.data) and not _.isFunction(@options.data) + if _.isObject(@options.data) and not _.isFunction @options.data @fullData = @options.data @parseData @options.data else # Remote data - @remote = new GitLabDropdownRemote @options.data, { + @remote = new GitLabDropdownRemote @options.data, dataType: @options.dataType, - beforeSend: @toggleLoading.bind(@) + beforeSend: @toggleLoading.bind this success: (data) => @fullData = data @@ -192,8 +181,8 @@ class GitLabDropdown currentIndex = -1 @parseData @fullData - @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input - } + if @options.filterable and @filter and @filter.input + @filter.input.trigger 'keyup' # Init filterable if @options.filterable @@ -210,9 +199,9 @@ class GitLabDropdown if @dropdown.find('.dropdown-toggle-page').length selector = ".dropdown-page-one #{selector}" - return $(selector) + $(selector) data: => - return @fullData + @fullData callback: (data) => @parseData data @@ -232,36 +221,35 @@ class GitLabDropdown # Event listeners - @dropdown.on "shown.bs.dropdown", @opened - @dropdown.on "hidden.bs.dropdown", @hidden - $(@el).on "update.label", @updateLabel - @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate + @dropdown.on 'shown.bs.dropdown', @opened + @dropdown.on 'hidden.bs.dropdown', @hidden + $(@el).on 'update.label', @updateLabel + @dropdown.on 'click', '.dropdown-menu, .dropdown-menu-close', @shouldPropagate @dropdown.on 'keyup', (e) => - if e.which is 27 # Escape key - $('.dropdown-menu-close', @dropdown).trigger 'click' + $('.dropdown-menu-close', @dropdown).trigger 'click' if e.which is 27 + @dropdown.on 'blur', 'a', (e) => if e.relatedTarget? $relatedTarget = $(e.relatedTarget) $dropdownMenu = $relatedTarget.closest('.dropdown-menu') - if $dropdownMenu.length is 0 - @dropdown.removeClass('open') + @dropdown.removeClass('open') if $dropdownMenu.length is 0 - if @dropdown.find(".dropdown-toggle-page").length - @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => + if @dropdown.find('.dropdown-toggle-page').length + @dropdown.find('.dropdown-toggle-page, .dropdown-menu-back').on 'click', (e) => e.preventDefault() e.stopPropagation() @togglePage() if @options.selectable - selector = ".dropdown-content a" + selector = '.dropdown-content a' - if @dropdown.find(".dropdown-toggle-page").length - selector = ".dropdown-page-one .dropdown-content a" + if @dropdown.find('.dropdown-toggle-page').length + selector = '.dropdown-page-one .dropdown-content a' - @dropdown.on "click", selector, (e) -> - $el = $(@) + @dropdown.on 'click', selector, (e) -> + $el = $(this) selected = self.rowClicked $el if self.options.clicked @@ -280,8 +268,7 @@ class GitLabDropdown menu = $('.dropdown-menu', @dropdown) if menu.hasClass(PAGE_TWO_CLASS) - if @remote - @remote.execute() + @remote.execute() if @remote menu.toggleClass PAGE_TWO_CLASS @@ -316,38 +303,32 @@ class GitLabDropdown renderData: (data, group = false) -> data.map (obj, index) => - return @renderItem(obj, group, index) + @renderItem(obj, group, index) shouldPropagate: (e) => - if @options.multiSelect - $target = $(e.target) - - if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link') - e.stopPropagation() - return false - else - return true + $target = $(e.target) if @options.multiSelect + unless $target.hasClass('dropdown-menu-close') and $target.hasClass('dropdown-menu-close-icon') and $target.data('is-link') + e.stopPropagation() + false + else + true opened: => @resetRows() @addArrowKeyEvent() - if @options.setIndeterminateIds - @options.setIndeterminateIds.call(@) + @options.setIndeterminateIds.call this if @options.setIndeterminateIds - if @options.setActiveIds - @options.setActiveIds.call(@) + @options.setActiveIds.call this if @options.setActiveIds # Makes indeterminate items effective if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update') @parseData @fullData contentHtml = $('.dropdown-content', @dropdown).html() - if @remote && contentHtml is "" - @remote.execute() + @remote.execute() if @remote and contentHtml is '' - if @options.filterable - @filterInput.focus() + @filterInput.focus() if @options.filterable @dropdown.trigger('shown.gl.dropdown') @@ -355,71 +336,67 @@ class GitLabDropdown @resetRows() @removeArrayKeyEvent() - $input = @dropdown.find(".dropdown-input-field") + $input = @dropdown.find('.dropdown-input-field') if @options.filterable $input .blur() - .val("") + .val('') # Triggering 'keyup' will re-render the dropdown which is not always required # specially if we want to keep the state of the dropdown needed for bulk-assignment - if not @options.persistWhenHide - $input.trigger("keyup") + $input.trigger('keyup') unless @options.persistWhenHide - if @dropdown.find(".dropdown-toggle-page").length + if @dropdown.find('.dropdown-toggle-page').length $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS - if @options.hidden - @options.hidden.call(@,e) + @options.hidden.call this, e if @options.hidden @dropdown.trigger('hidden.gl.dropdown') # Render the full menu renderMenu: (html) -> - menu_html = "" + menu_html = '' if @options.renderMenu - menu_html = @options.renderMenu(html) + menu_html = @options.renderMenu html else - menu_html = $('
      ') - .append(html) + menu_html = $('
        ').append html - return menu_html + menu_html # Append the menu into the dropdown appendMenu: (html) -> selector = '.dropdown-content' - if @dropdown.find(".dropdown-toggle-page").length - selector = ".dropdown-page-one .dropdown-content" + if @dropdown.find('.dropdown-toggle-page').length + selector = '.dropdown-page-one .dropdown-content' $(selector, @dropdown) .empty() - .append(html) + .append html # Render the row renderItem: (data, group = false, index = false) -> - html = "" + html = '' # Divider - return "
      • " if data is "divider" + return '
      • ' if data is 'divider' # Separator is a full-width divider - return "
      • " if data is "separator" + return '
      • ' if data is 'separator' # Header - return _.template("") header: data.header if data.header? + return _.template('')({ header: data.header }) if data.header? if @options.renderRow # Call the render function html = @options.renderRow.call(@options, data, @) else - if not selected + unless selected value = if @options.id then @options.id(data) else data.id fieldName = @options.fieldName field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") - if field.length - selected = true + selected = true if field.length # Set URL if @options.url? @@ -433,10 +410,9 @@ class GitLabDropdown else text = if data.text? then data.text else '' - cssClass = "" + cssClass = '' - if selected - cssClass = "is-active" + cssClass = 'is-active' if selected if @highlight text = @highlightTextMatches(text, @filterInput.val()) @@ -445,18 +421,18 @@ class GitLabDropdown groupAttrs = "data-group=#{group} data-index=#{index}" else groupAttrs = '' - html = _.template("
      • - class='<%- cssClass %>'> + html = _.template('
      • + class="<%- cssClass %>"> <%= text %> -
      • ")({ + ')({ url: url groupAttrs: groupAttrs cssClass: cssClass text: text }) - return html + html highlightTextMatches: (text, term) -> occurrences = fuzzaldrinPlus.match(text, term) @@ -465,20 +441,20 @@ class GitLabDropdown ).join('') noResults: -> - html = "" + ' rowClicked: (el) -> fieldName = @options.fieldName isInput = $(@el).is('input') if @renderedData - groupName = el.data('group') + groupName = el.data 'group' if groupName - selectedIndex = el.data('index') + selectedIndex = el.data 'index' selectedObject = @renderedData[groupName][selectedIndex] else selectedIndex = el.closest('li').index() @@ -501,20 +477,19 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el, @) + @updateLabel(selectedObject, el, this) else selectedObject else if el.hasClass(INDETERMINATE_CLASS) el.addClass ACTIVE_CLASS el.removeClass INDETERMINATE_CLASS - if not value? - field.remove() + field.remove() unless value? if not field.length and fieldName @addInput(fieldName, value) - return selectedObject + selectedObject else if not @options.multiSelect or el.hasClass('dropdown-clear-active') @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS @@ -522,24 +497,23 @@ class GitLabDropdown unless isInput @dropdown.parent().find("input[name='#{fieldName}']").remove() - if !value? - field.remove() + field.remove() unless value? # Toggle active class for the tick mark el.addClass ACTIVE_CLASS # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el, @) + @updateLabel(selectedObject, el, this) if value? - if !field.length and fieldName + if not field.length and fieldName @addInput(fieldName, value) else field .val value .trigger 'change' - return selectedObject + selectedObject addInput: (fieldName, value)-> # Create hidden input for form @@ -556,7 +530,7 @@ class GitLabDropdown # Dropdown list item link selector, excluding non-selectable list items selector = "#{SELECTABLE_CLASSES}:eq(#{index}) a" - if @dropdown.find(".dropdown-toggle-page").length + if @dropdown.find('.dropdown-toggle-page').length selector = ".dropdown-page-one #{selector}" # simulate a click on the first link @@ -570,11 +544,11 @@ class GitLabDropdown addArrowKeyEvent: -> ARROW_KEY_CODES = [38, 40] - $input = @dropdown.find(".dropdown-input-field") + $input = @dropdown.find '.dropdown-input-field' # Dropdown list item selector, excluding non-selectable list items selector = SELECTABLE_CLASSES - if @dropdown.find(".dropdown-toggle-page").length + if @dropdown.find('.dropdown-toggle-page').length selector = ".dropdown-page-one #{selector}" $('body').on 'keydown', (e) => @@ -597,12 +571,13 @@ class GitLabDropdown # Move up currentIndex -= 1 if currentIndex > 0 - @highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX + if currentIndex isnt PREV_INDEX + @highlightRowAtIndex($listItems, currentIndex) return false # If enter is pressed and a row is highlighted, select it - if currentKeyCode is 13 and currentIndex != -1 + if currentKeyCode is 13 and currentIndex isnt -1 e.preventDefault() e.stopImmediatePropagation() @selectRowAtIndex e, currentIndex @@ -621,7 +596,7 @@ class GitLabDropdown # Update the class for the row at the specific index $listItem = $listItems.eq(index) - $listItem.find('a:first-child').addClass "is-focused" + $listItem.find('a:first-child').addClass 'is-focused' # Dropdown content scroll area $dropdownContent = $listItem.closest('.dropdown-content') @@ -640,7 +615,7 @@ class GitLabDropdown $dropdownContent.scrollTop(0) else if index is $listItems.length - 1 # If this is the last item in the list, scroll to the bottom - $dropdownContent.scrollTop($dropdownContent[0].scrollHeight) + $dropdownContent.scrollTop $dropdownContent.prop 'scrollHeight' else if listItemBottom > dropdownContentBottom + dropdownScrollTop # Scroll the dropdown content down with a little padding $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING) @@ -649,9 +624,9 @@ class GitLabDropdown $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING) updateLabel: (selected = null, el = null, instance = null) => - $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance) + $(@el).find('.dropdown-toggle-text').text @options.toggleLabel(selected, el, instance) $.fn.glDropdown = (opts) -> - return @.each -> - if (!$.data @, 'glDropdown') - $.data(@, 'glDropdown', new GitLabDropdown @, opts) + @each -> + unless $.data this, 'glDropdown' + $.data this, 'glDropdown', new GitLabDropdown this, opts -- cgit v1.2.1 From 1ac5f764cecdeaa4b41a77353a979f941cf0e72b Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 8 Jul 2016 02:00:45 +0100 Subject: Fixed test turbolink condition --- app/assets/javascripts/gl_dropdown.js.coffee | 10 +++++----- spec/javascripts/gl_dropdown_spec.js.coffee | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 4e439e0de84..5e626b148f3 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -307,7 +307,7 @@ class GitLabDropdown shouldPropagate: (e) => $target = $(e.target) if @options.multiSelect - unless $target.hasClass('dropdown-menu-close') and $target.hasClass('dropdown-menu-close-icon') and $target.data('is-link') + if $target and not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link') e.stopPropagation() false else @@ -380,10 +380,10 @@ class GitLabDropdown html = '' # Divider - return '
      • ' if data is 'divider' + return '
      • ' if data is 'divider' # Separator is a full-width divider - return '
      • ' if data is 'separator' + return '
      • ' if data is 'separator' # Header return _.template('')({ header: data.header }) if data.header? @@ -518,8 +518,8 @@ class GitLabDropdown addInput: (fieldName, value)-> # Create hidden input for form $input = $('').attr('type', 'hidden') - .attr('name', fieldName) - .val(value) + .attr('name', fieldName) + .val(value) if @options.inputId? $input.attr('id', @options.inputId) diff --git a/spec/javascripts/gl_dropdown_spec.js.coffee b/spec/javascripts/gl_dropdown_spec.js.coffee index 46a0af32a7d..a4b3f519745 100644 --- a/spec/javascripts/gl_dropdown_spec.js.coffee +++ b/spec/javascripts/gl_dropdown_spec.js.coffee @@ -81,11 +81,12 @@ describe 'Dropdown', -> navigateWithKeys 'down', randomIndex, => spyOn(Turbolinks, 'visit').and.stub() navigateWithKeys 'enter', null, => + expect(@dropdownContainerElement).not.toHaveClass 'open' link = $("#{ITEM_SELECTOR}:eq(#{randomIndex}) a", @dropdownMenuElement) expect(link).toHaveClass 'is-active' - if link.attr 'href' - expect(Turbolinks.visit).toHaveBeenCalledWith link.attr 'href' - expect(@dropdownContainerElement).not.toHaveClass 'open' + linkedLocation = link.attr 'href' + if linkedLocation and linkedLocation isnt '#' + expect(Turbolinks.visit).toHaveBeenCalledWith linkedLocation it 'should close on ESC keypress', -> expect(@dropdownContainerElement).toHaveClass 'open' -- cgit v1.2.1 From affdf201d2acb7ae582df89ee6013f5563925877 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 19 Jul 2016 13:15:14 +0100 Subject: Further review changes and removed large ammounts of reformatting --- app/assets/javascripts/gl_dropdown.js.coffee | 234 ++++++++++++--------- .../javascripts/search_autocomplete.js.coffee | 5 +- 2 files changed, 132 insertions(+), 107 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 5e626b148f3..51f739ae201 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -1,7 +1,7 @@ class GitLabDropdownFilter BLUR_KEYCODES = [27, 40] ARROW_KEY_CODES = [38, 40] - HAS_VALUE_CLASS = 'has-value' + HAS_VALUE_CLASS = "has-value" constructor: (@input, @options) -> { @@ -9,7 +9,7 @@ class GitLabDropdownFilter } = @options $inputContainer = @input.parent() - $clearButton = $inputContainer.find '.js-dropdown-input-clear' + $clearButton = $inputContainer.find('.js-dropdown-input-clear') @indeterminateIds = [] @@ -18,23 +18,24 @@ class GitLabDropdownFilter e.preventDefault() e.stopPropagation() @input - .val '' - .trigger 'keyup' + .val('') + .trigger('keyup') .focus() # Key events - timeout = '' - @input.on 'keyup', (e) => + timeout = "" + @input.on "keyup", (e) => keyCode = e.which return if ARROW_KEY_CODES.indexOf(keyCode) >= 0 - if @input.val() isnt '' and not $inputContainer.hasClass HAS_VALUE_CLASS + if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS $inputContainer.addClass HAS_VALUE_CLASS - else if @input.val() is '' and $inputContainer.hasClass HAS_VALUE_CLASS + else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS $inputContainer.removeClass HAS_VALUE_CLASS - return false if keyCode is 13 + if keyCode is 13 + return false # Only filter asynchronously only if option remote is set if @options.remote @@ -46,13 +47,13 @@ class GitLabDropdownFilter @input.blur() @options.query @input.val(), (data) => - @options.callback data + @options.callback(data) , 250 else @filter @input.val() shouldBlur: (keyCode) -> - BLUR_KEYCODES.indexOf(keyCode) >= 0 + return BLUR_KEYCODES.indexOf(keyCode) >= 0 filter: (search_text) -> @options.onFilter(search_text) if @options.onFilter @@ -68,8 +69,9 @@ class GitLabDropdownFilter # { prop: 'baz' } # ] if _.isArray(data) - results = fuzzaldrinPlus.filter data, search_text, + results = fuzzaldrinPlus.filter(data, search_text, key: @options.keys + ) else # If data is grouped therefore an [object Object]. e.g. # { @@ -85,8 +87,9 @@ class GitLabDropdownFilter if gl.utils.isObject data results = {} for key, group of data - tmp = fuzzaldrinPlus.filter group, search_text, + tmp = fuzzaldrinPlus.filter(group, search_text, key: @options.keys + ) if tmp.length results[key] = tmp.map (item) -> item @@ -97,10 +100,10 @@ class GitLabDropdownFilter if search_text elements.each -> - $el = $(this) - matches = fuzzaldrinPlus.match $el.text().trim(), search_text + $el = $(@) + matches = fuzzaldrinPlus.match($el.text().trim(), search_text) - unless $el.is '.dropdown-header' + unless $el.is('.dropdown-header') if matches.length $el.show() else @@ -112,68 +115,75 @@ class GitLabDropdownRemote constructor: (@dataEndpoint, @options) -> execute: -> - if typeof @dataEndpoint is 'string' + if typeof @dataEndpoint is "string" @fetchData() - else if typeof @dataEndpoint is 'function' - @options.beforeSend() if @options.beforeSend + else if typeof @dataEndpoint is "function" + if @options.beforeSend + @options.beforeSend() # Fetch the data by calling the data funcfion - @dataEndpoint '', (data) => - @options.success(data) if @options.success - @options.beforeSend() if @options.beforeSend + @dataEndpoint "", (data) => + if @options.success + @options.success(data) + if @options.beforeSend + @options.beforeSend() # Fetch the data through ajax if the data is a string fetchData: -> - $.ajax + $.ajax( url: @dataEndpoint, dataType: @options.dataType, beforeSend: => - @options.beforeSend() if @options.beforeSend + if @options.beforeSend + @options.beforeSend() success: (data) => - @options.success(data) if @options.success + if @options.success + @options.success(data) + ) class GitLabDropdown - LOADING_CLASS = 'is-loading' - PAGE_TWO_CLASS = 'is-page-two' - ACTIVE_CLASS = 'is-active' - INDETERMINATE_CLASS = 'is-indeterminate' + LOADING_CLASS = "is-loading" + PAGE_TWO_CLASS = "is-page-two" + ACTIVE_CLASS = "is-active" + INDETERMINATE_CLASS = "is-indeterminate" + currentIndex = -1 NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link' SELECTABLE_CLASSES = ".dropdown-content li:not(#{NON_SELECTABLE_CLASSES})" FILTER_INPUT = '.dropdown-input .dropdown-input-field' - currentIndex = -1 CURSOR_SELECT_SCROLL_PADDING = 5 constructor: (@el, @options) -> - self = this - selector = $(@el).data 'target' + self = @ + selector = $(@el).data "target" @dropdown = if selector? then $(selector) else $(@el).parent() # Set Defaults { # If no input is passed create a default one - @filterInput = @getElement FILTER_INPUT + @filterInput = @getElement(FILTER_INPUT) @highlight = false @filterInputBlur = true } = @options - self = this + self = @ # If selector was passed - @filterInput = @getElement(@filterInput) if _.isString @filterInput + if _.isString(@filterInput) + @filterInput = @getElement(@filterInput) searchFields = if @options.search then @options.search.fields else [] if @options.data # If we provided data # data could be an array of objects or a group of arrays - if _.isObject(@options.data) and not _.isFunction @options.data + if _.isObject(@options.data) and not _.isFunction(@options.data) @fullData = @options.data @parseData @options.data else # Remote data - @remote = new GitLabDropdownRemote @options.data, + @remote = new GitLabDropdownRemote @options.data, { dataType: @options.dataType, - beforeSend: @toggleLoading.bind this + beforeSend: @toggleLoading.bind(@) success: (data) => @fullData = data @@ -181,8 +191,8 @@ class GitLabDropdown currentIndex = -1 @parseData @fullData - if @options.filterable and @filter and @filter.input - @filter.input.trigger 'keyup' + @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input + } # Init filterable if @options.filterable @@ -199,9 +209,9 @@ class GitLabDropdown if @dropdown.find('.dropdown-toggle-page').length selector = ".dropdown-page-one #{selector}" - $(selector) + return $(selector) data: => - @fullData + return @fullData callback: (data) => @parseData data @@ -221,35 +231,37 @@ class GitLabDropdown # Event listeners - @dropdown.on 'shown.bs.dropdown', @opened - @dropdown.on 'hidden.bs.dropdown', @hidden - $(@el).on 'update.label', @updateLabel - @dropdown.on 'click', '.dropdown-menu, .dropdown-menu-close', @shouldPropagate + @dropdown.on "shown.bs.dropdown", @opened + @dropdown.on "hidden.bs.dropdown", @hidden + $(@el).on "update.label", @updateLabel + @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate @dropdown.on 'keyup', (e) => - $('.dropdown-menu-close', @dropdown).trigger 'click' if e.which is 27 + if e.which is 27 # Escape key + $('.dropdown-menu-close', @dropdown).trigger 'click' @dropdown.on 'blur', 'a', (e) => if e.relatedTarget? $relatedTarget = $(e.relatedTarget) $dropdownMenu = $relatedTarget.closest('.dropdown-menu') - @dropdown.removeClass('open') if $dropdownMenu.length is 0 + if $dropdownMenu.length is 0 + @dropdown.removeClass('open') - if @dropdown.find('.dropdown-toggle-page').length - @dropdown.find('.dropdown-toggle-page, .dropdown-menu-back').on 'click', (e) => + if @dropdown.find(".dropdown-toggle-page").length + @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => e.preventDefault() e.stopPropagation() @togglePage() if @options.selectable - selector = '.dropdown-content a' + selector = ".dropdown-content a" - if @dropdown.find('.dropdown-toggle-page').length - selector = '.dropdown-page-one .dropdown-content a' + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one .dropdown-content a" - @dropdown.on 'click', selector, (e) -> - $el = $(this) + @dropdown.on "click", selector, (e) -> + $el = $(@) selected = self.rowClicked $el if self.options.clicked @@ -268,7 +280,8 @@ class GitLabDropdown menu = $('.dropdown-menu', @dropdown) if menu.hasClass(PAGE_TWO_CLASS) - @remote.execute() if @remote + if @remote + @remote.execute() menu.toggleClass PAGE_TWO_CLASS @@ -303,32 +316,38 @@ class GitLabDropdown renderData: (data, group = false) -> data.map (obj, index) => - @renderItem(obj, group, index) + return @renderItem(obj, group, index) shouldPropagate: (e) => - $target = $(e.target) if @options.multiSelect + if @options.multiSelect + $target = $(e.target) + if $target and not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link') e.stopPropagation() - false + return false else - true + return true opened: => @resetRows() @addArrowKeyEvent() - @options.setIndeterminateIds.call this if @options.setIndeterminateIds + if @options.setIndeterminateIds + @options.setIndeterminateIds.call(@) - @options.setActiveIds.call this if @options.setActiveIds + if @options.setActiveIds + @options.setActiveIds.call(@) # Makes indeterminate items effective if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update') @parseData @fullData contentHtml = $('.dropdown-content', @dropdown).html() - @remote.execute() if @remote and contentHtml is '' + if @remote && contentHtml is "" + @remote.execute() - @filterInput.focus() if @options.filterable + if @options.filterable + @filterInput.focus() @dropdown.trigger('shown.gl.dropdown') @@ -336,48 +355,51 @@ class GitLabDropdown @resetRows() @removeArrayKeyEvent() - $input = @dropdown.find('.dropdown-input-field') + $input = @dropdown.find(".dropdown-input-field") if @options.filterable $input .blur() - .val('') + .val("") # Triggering 'keyup' will re-render the dropdown which is not always required # specially if we want to keep the state of the dropdown needed for bulk-assignment - $input.trigger('keyup') unless @options.persistWhenHide + if not @options.persistWhenHide + $input.trigger("keyup") - if @dropdown.find('.dropdown-toggle-page').length + if @dropdown.find(".dropdown-toggle-page").length $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS - @options.hidden.call this, e if @options.hidden + if @options.hidden + @options.hidden.call(@,e) @dropdown.trigger('hidden.gl.dropdown') # Render the full menu renderMenu: (html) -> - menu_html = '' + menu_html = "" if @options.renderMenu - menu_html = @options.renderMenu html + menu_html = @options.renderMenu(html) else - menu_html = $('
          ').append html + menu_html = $('
            ') + .append(html) - menu_html + return menu_html # Append the menu into the dropdown appendMenu: (html) -> selector = '.dropdown-content' - if @dropdown.find('.dropdown-toggle-page').length - selector = '.dropdown-page-one .dropdown-content' + if @dropdown.find(".dropdown-toggle-page").length + selector = ".dropdown-page-one .dropdown-content" $(selector, @dropdown) .empty() - .append html + .append(html) # Render the row renderItem: (data, group = false, index = false) -> - html = '' + html = "" # Divider return '
          • ' if data is 'divider' @@ -392,11 +414,12 @@ class GitLabDropdown # Call the render function html = @options.renderRow.call(@options, data, @) else - unless selected + if not selected value = if @options.id then @options.id(data) else data.id fieldName = @options.fieldName field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") - selected = true if field.length + if field.length + selected = true # Set URL if @options.url? @@ -410,15 +433,16 @@ class GitLabDropdown else text = if data.text? then data.text else '' - cssClass = '' + cssClass = "" - cssClass = 'is-active' if selected + if selected + cssClass = "is-active" if @highlight text = @highlightTextMatches(text, @filterInput.val()) if group - groupAttrs = "data-group=#{group} data-index=#{index}" + groupAttrs = "data-group='#{group}' data-index='#{index}'" else groupAttrs = '' html = _.template('
          • @@ -432,7 +456,7 @@ class GitLabDropdown text: text }) - html + return html highlightTextMatches: (text, term) -> occurrences = fuzzaldrinPlus.match(text, term) @@ -452,9 +476,9 @@ class GitLabDropdown isInput = $(@el).is('input') if @renderedData - groupName = el.data 'group' + groupName = el.data('group') if groupName - selectedIndex = el.data 'index' + selectedIndex = el.data('index') selectedObject = @renderedData[groupName][selectedIndex] else selectedIndex = el.closest('li').index() @@ -477,19 +501,20 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el, this) + @updateLabel(selectedObject, el, @) else selectedObject else if el.hasClass(INDETERMINATE_CLASS) el.addClass ACTIVE_CLASS el.removeClass INDETERMINATE_CLASS - field.remove() unless value? + if not value? + field.remove() if not field.length and fieldName @addInput(fieldName, value) - selectedObject + return selectedObject else if not @options.multiSelect or el.hasClass('dropdown-clear-active') @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS @@ -497,29 +522,30 @@ class GitLabDropdown unless isInput @dropdown.parent().find("input[name='#{fieldName}']").remove() - field.remove() unless value? + if !value? + field.remove() # Toggle active class for the tick mark el.addClass ACTIVE_CLASS # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el, this) + @updateLabel(selectedObject, el, @) if value? - if not field.length and fieldName + if !field.length and fieldName @addInput(fieldName, value) else field .val value .trigger 'change' - selectedObject + return selectedObject addInput: (fieldName, value)-> # Create hidden input for form $input = $('').attr('type', 'hidden') - .attr('name', fieldName) - .val(value) + .attr('name', fieldName) + .val(value) if @options.inputId? $input.attr('id', @options.inputId) @@ -530,21 +556,22 @@ class GitLabDropdown # Dropdown list item link selector, excluding non-selectable list items selector = "#{SELECTABLE_CLASSES}:eq(#{index}) a" - if @dropdown.find('.dropdown-toggle-page').length + if @dropdown.find(".dropdown-toggle-page").length selector = ".dropdown-page-one #{selector}" # simulate a click on the first link $el = $(selector, @dropdown) + if $el.length e.preventDefault() e.stopImmediatePropagation() - $el.first().trigger 'click' + $el.first().trigger('click') href = $el.attr 'href' Turbolinks.visit(href) if href and href isnt '#' addArrowKeyEvent: -> ARROW_KEY_CODES = [38, 40] - $input = @dropdown.find '.dropdown-input-field' + $input = @dropdown.find(".dropdown-input-field") # Dropdown list item selector, excluding non-selectable list items selector = SELECTABLE_CLASSES @@ -571,8 +598,7 @@ class GitLabDropdown # Move up currentIndex -= 1 if currentIndex > 0 - if currentIndex isnt PREV_INDEX - @highlightRowAtIndex($listItems, currentIndex) + @highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX return false @@ -596,7 +622,7 @@ class GitLabDropdown # Update the class for the row at the specific index $listItem = $listItems.eq(index) - $listItem.find('a:first-child').addClass 'is-focused' + $listItem.find('a:first-child').addClass "is-focused" # Dropdown content scroll area $dropdownContent = $listItem.closest('.dropdown-content') @@ -624,9 +650,9 @@ class GitLabDropdown $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING) updateLabel: (selected = null, el = null, instance = null) => - $(@el).find('.dropdown-toggle-text').text @options.toggleLabel(selected, el, instance) + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance) $.fn.glDropdown = (opts) -> - @each -> - unless $.data this, 'glDropdown' - $.data this, 'glDropdown', new GitLabDropdown this, opts + return @.each -> + if (!$.data @, 'glDropdown') + $.data(@, 'glDropdown', new GitLabDropdown @, opts) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index a3baea1ae01..fc74959b1e1 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -315,11 +315,10 @@ class @SearchAutocomplete disableAutocomplete: -> # If not disabled already, disable - if not @searchInput.hasClass('disabled') && @dropdown.hasClass('open') + if not @searchInput.hasClass('disabled') and @dropdown.hasClass 'open' @searchInput.addClass('disabled') # Close dropdown and invoke its hidden() method - @dropdown.removeClass('open') - .trigger('hidden.bs.dropdown') + @dropdown.removeClass('open').trigger 'hidden.bs.dropdown' @restoreMenu() restoreMenu: -> -- cgit v1.2.1 From e74d12a9b3e76c6d49ff1c57bd6471073d34be4a Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 22 Jul 2016 16:17:43 +0100 Subject: Fix failed tests --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 51f739ae201..e0dae228f7a 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -442,7 +442,7 @@ class GitLabDropdown text = @highlightTextMatches(text, @filterInput.val()) if group - groupAttrs = "data-group='#{group}' data-index='#{index}'" + groupAttrs = "data-group=#{group} data-index=#{index}" else groupAttrs = '' html = _.template('
          • -- cgit v1.2.1 From dcf09a532b7eff230bf2a646fdb96e9aa43ff189 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 27 Jul 2016 01:17:07 +0100 Subject: Moved changes across to es5 and changed spec to es6 --- app/assets/javascripts/gl_dropdown.js | 65 +- app/assets/javascripts/gl_dropdown.js.coffee | 658 --------------------- app/assets/javascripts/search_autocomplete.js | 18 +- .../javascripts/search_autocomplete.js.coffee | 348 ----------- spec/javascripts/gl_dropdown_spec.js.coffee | 97 --- spec/javascripts/gl_dropdown_spec.js.es6 | 120 ++++ 6 files changed, 175 insertions(+), 1131 deletions(-) delete mode 100644 app/assets/javascripts/gl_dropdown.js.coffee delete mode 100644 app/assets/javascripts/search_autocomplete.js.coffee delete mode 100644 spec/javascripts/gl_dropdown_spec.js.coffee create mode 100644 spec/javascripts/gl_dropdown_spec.js.es6 diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d3394fae3f9..aacc49be1ac 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -191,6 +191,12 @@ currentIndex = -1; + NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; + + SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")"; + + CURSOR_SELECT_SCROLL_PADDING = 5 + FILTER_INPUT = '.dropdown-input .dropdown-input-field'; function GitLabDropdown(el1, options) { @@ -213,6 +219,7 @@ if (this.options.data) { if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) { this.fullData = this.options.data; + currentIndex = -1 this.parseData(this.options.data); } else { this.remote = new GitLabDropdownRemote(this.options.data, { @@ -240,7 +247,7 @@ keys: searchFields, elements: (function(_this) { return function() { - selector = '.dropdown-content li:not(.divider)'; + selector = SELECTABLE_CLASSES; if (_this.dropdown.find('.dropdown-toggle-page').length) { selector = ".dropdown-page-one " + selector; } @@ -376,7 +383,7 @@ var $target; if (this.options.multiSelect) { $target = $(e.target); - if (!$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) { + if ($target && !$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) { e.stopPropagation(); return false; } else { @@ -387,7 +394,7 @@ GitLabDropdown.prototype.opened = function() { var contentHtml; - currentIndex = -1; + this.resetRows(); this.addArrowKeyEvent(); if (this.options.setIndeterminateIds) { this.options.setIndeterminateIds.call(this); @@ -410,6 +417,7 @@ GitLabDropdown.prototype.hidden = function(e) { var $input; + this.resetRows(); this.removeArrayKeyEvent(); $input = this.dropdown.find(".dropdown-input-field"); if (this.options.filterable) { @@ -463,7 +471,7 @@ return "
          • "; } if (data.header != null) { - return ""; + return _.template('')({ header: data.header }); } if (this.options.renderRow) { html = this.options.renderRow.call(this.options, data, this); @@ -498,7 +506,12 @@ } else { groupAttrs = ''; } - html = "
          • " + text + "
          • "; + html = _.template('
          • class="<%- cssClass %>"><%= text %>
          • ')({ + url: url, + groupAttrs: groupAttrs, + cssClass: cssClass, + text: text + }); } return html; }; @@ -520,17 +533,6 @@ return html = ""; }; - GitLabDropdown.prototype.highlightRow = function(index) { - var selector; - if (this.filterInput.val() !== "") { - selector = '.dropdown-content li:first-child a'; - if (this.dropdown.find(".dropdown-toggle-page").length) { - selector = ".dropdown-page-one .dropdown-content li:first-child a"; - } - return this.getElement(selector).addClass('is-focused'); - } - }; - GitLabDropdown.prototype.rowClicked = function(el) { var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; fieldName = this.options.fieldName; @@ -609,13 +611,17 @@ GitLabDropdown.prototype.selectRowAtIndex = function(index) { var $el, selector; - selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a"; + selector = SELECTABLE_CLASSES + ":eq(" + index + ") a"; if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one " + selector; } $el = $(selector, this.dropdown); if ($el.length) { - return $el.first().trigger('click'); + e.preventDefault(); + e.stopImmediatePropagation(); + $el.first().trigger('click'); + href = $el.attr('href'); + if (href && href !== '#') Turbolinks.visit(href); } }; @@ -623,7 +629,7 @@ var $input, ARROW_KEY_CODES, selector; ARROW_KEY_CODES = [38, 40]; $input = this.dropdown.find(".dropdown-input-field"); - selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible'; + selector = SELECTABLE_CLASSES; if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one " + selector; } @@ -651,7 +657,9 @@ return false; } if (currentKeyCode === 13 && currentIndex !== -1) { - return _this.selectRowAtIndex($('.is-focused', _this.dropdown).closest('li').index() - 1); + e.preventDefault() + e.stopImmediatePropagation() + return _this.selectRowAtIndex(currentIndex); } }; })(this)); @@ -661,6 +669,11 @@ return $('body').off('keydown'); }; + GitLabDropdown.prototype.resetRows = function resetRows() { + currentIndex = -1; + $('.is-focused', this.dropdown).removeClass('is-focused'); + }; + GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) { var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop; $('.is-focused', this.dropdown).removeClass('is-focused'); @@ -674,10 +687,14 @@ listItemHeight = $listItem.outerHeight(); listItemTop = $listItem.prop('offsetTop'); listItemBottom = listItemTop + listItemHeight; - if (listItemBottom > dropdownContentBottom + dropdownScrollTop) { - return $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom); - } else if (listItemTop < dropdownContentTop + dropdownScrollTop) { - return $dropdownContent.scrollTop(listItemTop - dropdownContentTop); + if (!index) { + $dropdownContent.scrollTop(0) + } else if (index === ($listItems.length - 1)) { + $dropdownContent.scrollTop $dropdownContent.prop('scrollHeight'); + } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) + $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); + } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { + return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING); } }; diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee deleted file mode 100644 index e0dae228f7a..00000000000 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ /dev/null @@ -1,658 +0,0 @@ -class GitLabDropdownFilter - BLUR_KEYCODES = [27, 40] - ARROW_KEY_CODES = [38, 40] - HAS_VALUE_CLASS = "has-value" - - constructor: (@input, @options) -> - { - @filterInputBlur = true - } = @options - - $inputContainer = @input.parent() - $clearButton = $inputContainer.find('.js-dropdown-input-clear') - - @indeterminateIds = [] - - # Clear click - $clearButton.on 'click', (e) => - e.preventDefault() - e.stopPropagation() - @input - .val('') - .trigger('keyup') - .focus() - - # Key events - timeout = "" - @input.on "keyup", (e) => - keyCode = e.which - - return if ARROW_KEY_CODES.indexOf(keyCode) >= 0 - - if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS - $inputContainer.addClass HAS_VALUE_CLASS - else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS - $inputContainer.removeClass HAS_VALUE_CLASS - - if keyCode is 13 - return false - - # Only filter asynchronously only if option remote is set - if @options.remote - clearTimeout timeout - timeout = setTimeout => - blur_field = @shouldBlur keyCode - - if blur_field and @filterInputBlur - @input.blur() - - @options.query @input.val(), (data) => - @options.callback(data) - , 250 - else - @filter @input.val() - - shouldBlur: (keyCode) -> - return BLUR_KEYCODES.indexOf(keyCode) >= 0 - - filter: (search_text) -> - @options.onFilter(search_text) if @options.onFilter - data = @options.data() - - if data? and not @options.filterByText - results = data - - if search_text isnt '' - # When data is an array of objects therefore [object Array] e.g. - # [ - # { prop: 'foo' }, - # { prop: 'baz' } - # ] - if _.isArray(data) - results = fuzzaldrinPlus.filter(data, search_text, - key: @options.keys - ) - else - # If data is grouped therefore an [object Object]. e.g. - # { - # groupName1: [ - # { prop: 'foo' }, - # { prop: 'baz' } - # ], - # groupName2: [ - # { prop: 'abc' }, - # { prop: 'def' } - # ] - # } - if gl.utils.isObject data - results = {} - for key, group of data - tmp = fuzzaldrinPlus.filter(group, search_text, - key: @options.keys - ) - - if tmp.length - results[key] = tmp.map (item) -> item - - @options.callback results - else - elements = @options.elements() - - if search_text - elements.each -> - $el = $(@) - matches = fuzzaldrinPlus.match($el.text().trim(), search_text) - - unless $el.is('.dropdown-header') - if matches.length - $el.show() - else - $el.hide() - else - elements.show() - -class GitLabDropdownRemote - constructor: (@dataEndpoint, @options) -> - - execute: -> - if typeof @dataEndpoint is "string" - @fetchData() - else if typeof @dataEndpoint is "function" - if @options.beforeSend - @options.beforeSend() - - # Fetch the data by calling the data funcfion - @dataEndpoint "", (data) => - if @options.success - @options.success(data) - if @options.beforeSend - @options.beforeSend() - - # Fetch the data through ajax if the data is a string - fetchData: -> - $.ajax( - url: @dataEndpoint, - dataType: @options.dataType, - beforeSend: => - if @options.beforeSend - @options.beforeSend() - success: (data) => - if @options.success - @options.success(data) - ) - -class GitLabDropdown - LOADING_CLASS = "is-loading" - PAGE_TWO_CLASS = "is-page-two" - ACTIVE_CLASS = "is-active" - INDETERMINATE_CLASS = "is-indeterminate" - currentIndex = -1 - NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link' - SELECTABLE_CLASSES = ".dropdown-content li:not(#{NON_SELECTABLE_CLASSES})" - FILTER_INPUT = '.dropdown-input .dropdown-input-field' - CURSOR_SELECT_SCROLL_PADDING = 5 - - constructor: (@el, @options) -> - self = @ - selector = $(@el).data "target" - @dropdown = if selector? then $(selector) else $(@el).parent() - - # Set Defaults - { - # If no input is passed create a default one - @filterInput = @getElement(FILTER_INPUT) - @highlight = false - @filterInputBlur = true - } = @options - - self = @ - - # If selector was passed - if _.isString(@filterInput) - @filterInput = @getElement(@filterInput) - - searchFields = if @options.search then @options.search.fields else [] - - if @options.data - # If we provided data - # data could be an array of objects or a group of arrays - if _.isObject(@options.data) and not _.isFunction(@options.data) - @fullData = @options.data - @parseData @options.data - else - # Remote data - @remote = new GitLabDropdownRemote @options.data, { - dataType: @options.dataType, - beforeSend: @toggleLoading.bind(@) - success: (data) => - @fullData = data - - # Reset selected row index on new data - currentIndex = -1 - @parseData @fullData - - @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input - } - - # Init filterable - if @options.filterable - @filter = new GitLabDropdownFilter @filterInput, - filterInputBlur: @filterInputBlur - filterByText: @options.filterByText - onFilter: @options.onFilter - remote: @options.filterRemote - query: @options.data - keys: searchFields - elements: => - selector = SELECTABLE_CLASSES - - if @dropdown.find('.dropdown-toggle-page').length - selector = ".dropdown-page-one #{selector}" - - return $(selector) - data: => - return @fullData - callback: (data) => - @parseData data - - unless @filterInput.val() is '' - selector = '.dropdown-content li:not(.divider):visible' - - if @dropdown.find('.dropdown-toggle-page').length - selector = ".dropdown-page-one #{selector}" - - $(selector, @dropdown) - .first() - .find('a') - .addClass('is-focused') - - currentIndex = 0 - - - # Event listeners - - @dropdown.on "shown.bs.dropdown", @opened - @dropdown.on "hidden.bs.dropdown", @hidden - $(@el).on "update.label", @updateLabel - @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate - @dropdown.on 'keyup', (e) => - if e.which is 27 # Escape key - $('.dropdown-menu-close', @dropdown).trigger 'click' - - @dropdown.on 'blur', 'a', (e) => - if e.relatedTarget? - $relatedTarget = $(e.relatedTarget) - $dropdownMenu = $relatedTarget.closest('.dropdown-menu') - - if $dropdownMenu.length is 0 - @dropdown.removeClass('open') - - if @dropdown.find(".dropdown-toggle-page").length - @dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) => - e.preventDefault() - e.stopPropagation() - - @togglePage() - - if @options.selectable - selector = ".dropdown-content a" - - if @dropdown.find(".dropdown-toggle-page").length - selector = ".dropdown-page-one .dropdown-content a" - - @dropdown.on "click", selector, (e) -> - $el = $(@) - selected = self.rowClicked $el - - if self.options.clicked - self.options.clicked(selected, $el, e) - - $el.trigger('blur') - - # Finds an element inside wrapper element - getElement: (selector) -> - @dropdown.find selector - - toggleLoading: -> - $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS - - togglePage: -> - menu = $('.dropdown-menu', @dropdown) - - if menu.hasClass(PAGE_TWO_CLASS) - if @remote - @remote.execute() - - menu.toggleClass PAGE_TWO_CLASS - - # Focus first visible input on active page - @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus() - - parseData: (data) -> - @renderedData = data - - if @options.filterable and data.length is 0 - # render no matching results - html = [@noResults()] - else - # Handle array groups - if gl.utils.isObject data - html = [] - for name, groupData of data - # Add header for each group - html.push(@renderItem(header: name, name)) - - @renderData(groupData, name) - .map (item) -> - html.push item - else - # Render each row - html = @renderData(data) - - # Render the full menu - full_html = @renderMenu(html) - - @appendMenu(full_html) - - renderData: (data, group = false) -> - data.map (obj, index) => - return @renderItem(obj, group, index) - - shouldPropagate: (e) => - if @options.multiSelect - $target = $(e.target) - - if $target and not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link') - e.stopPropagation() - return false - else - return true - - opened: => - @resetRows() - @addArrowKeyEvent() - - if @options.setIndeterminateIds - @options.setIndeterminateIds.call(@) - - if @options.setActiveIds - @options.setActiveIds.call(@) - - # Makes indeterminate items effective - if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update') - @parseData @fullData - - contentHtml = $('.dropdown-content', @dropdown).html() - if @remote && contentHtml is "" - @remote.execute() - - if @options.filterable - @filterInput.focus() - - @dropdown.trigger('shown.gl.dropdown') - - hidden: (e) => - @resetRows() - @removeArrayKeyEvent() - - $input = @dropdown.find(".dropdown-input-field") - - if @options.filterable - $input - .blur() - .val("") - - # Triggering 'keyup' will re-render the dropdown which is not always required - # specially if we want to keep the state of the dropdown needed for bulk-assignment - if not @options.persistWhenHide - $input.trigger("keyup") - - if @dropdown.find(".dropdown-toggle-page").length - $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS - - if @options.hidden - @options.hidden.call(@,e) - - @dropdown.trigger('hidden.gl.dropdown') - - - # Render the full menu - renderMenu: (html) -> - menu_html = "" - - if @options.renderMenu - menu_html = @options.renderMenu(html) - else - menu_html = $('
              ') - .append(html) - - return menu_html - - # Append the menu into the dropdown - appendMenu: (html) -> - selector = '.dropdown-content' - if @dropdown.find(".dropdown-toggle-page").length - selector = ".dropdown-page-one .dropdown-content" - $(selector, @dropdown) - .empty() - .append(html) - - # Render the row - renderItem: (data, group = false, index = false) -> - html = "" - - # Divider - return '
            • ' if data is 'divider' - - # Separator is a full-width divider - return '
            • ' if data is 'separator' - - # Header - return _.template('')({ header: data.header }) if data.header? - - if @options.renderRow - # Call the render function - html = @options.renderRow.call(@options, data, @) - else - if not selected - value = if @options.id then @options.id(data) else data.id - fieldName = @options.fieldName - field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") - if field.length - selected = true - - # Set URL - if @options.url? - url = @options.url(data) - else - url = if data.url? then data.url else '#' - - # Set Text - if @options.text? - text = @options.text(data) - else - text = if data.text? then data.text else '' - - cssClass = "" - - if selected - cssClass = "is-active" - - if @highlight - text = @highlightTextMatches(text, @filterInput.val()) - - if group - groupAttrs = "data-group=#{group} data-index=#{index}" - else - groupAttrs = '' - html = _.template('
            • - class="<%- cssClass %>"> - <%= text %> - -
            • ')({ - url: url - groupAttrs: groupAttrs - cssClass: cssClass - text: text - }) - - return html - - highlightTextMatches: (text, term) -> - occurrences = fuzzaldrinPlus.match(text, term) - text.split('').map((character, i) -> - if i in occurrences then "#{character}" else character - ).join('') - - noResults: -> - html = '' - - rowClicked: (el) -> - fieldName = @options.fieldName - isInput = $(@el).is('input') - - if @renderedData - groupName = el.data('group') - if groupName - selectedIndex = el.data('index') - selectedObject = @renderedData[groupName][selectedIndex] - else - selectedIndex = el.closest('li').index() - selectedObject = @renderedData[selectedIndex] - - value = if @options.id then @options.id(selectedObject, el) else selectedObject.id - - if isInput - field = $(@el) - else - field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") - - if el.hasClass(ACTIVE_CLASS) - el.removeClass(ACTIVE_CLASS) - - if isInput - field.val('') - else - field.remove() - - # Toggle the dropdown label - if @options.toggleLabel - @updateLabel(selectedObject, el, @) - else - selectedObject - else if el.hasClass(INDETERMINATE_CLASS) - el.addClass ACTIVE_CLASS - el.removeClass INDETERMINATE_CLASS - - if not value? - field.remove() - - if not field.length and fieldName - @addInput(fieldName, value) - - return selectedObject - else - if not @options.multiSelect or el.hasClass('dropdown-clear-active') - @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS - - unless isInput - @dropdown.parent().find("input[name='#{fieldName}']").remove() - - if !value? - field.remove() - - # Toggle active class for the tick mark - el.addClass ACTIVE_CLASS - - # Toggle the dropdown label - if @options.toggleLabel - @updateLabel(selectedObject, el, @) - if value? - if !field.length and fieldName - @addInput(fieldName, value) - else - field - .val value - .trigger 'change' - - return selectedObject - - addInput: (fieldName, value)-> - # Create hidden input for form - $input = $('').attr('type', 'hidden') - .attr('name', fieldName) - .val(value) - - if @options.inputId? - $input.attr('id', @options.inputId) - - @dropdown.before $input - - selectRowAtIndex: (e, index) -> - # Dropdown list item link selector, excluding non-selectable list items - selector = "#{SELECTABLE_CLASSES}:eq(#{index}) a" - - if @dropdown.find(".dropdown-toggle-page").length - selector = ".dropdown-page-one #{selector}" - - # simulate a click on the first link - $el = $(selector, @dropdown) - - if $el.length - e.preventDefault() - e.stopImmediatePropagation() - $el.first().trigger('click') - href = $el.attr 'href' - Turbolinks.visit(href) if href and href isnt '#' - - addArrowKeyEvent: -> - ARROW_KEY_CODES = [38, 40] - $input = @dropdown.find(".dropdown-input-field") - - # Dropdown list item selector, excluding non-selectable list items - selector = SELECTABLE_CLASSES - if @dropdown.find('.dropdown-toggle-page').length - selector = ".dropdown-page-one #{selector}" - - $('body').on 'keydown', (e) => - currentKeyCode = e.which - - if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0 - e.preventDefault() - e.stopImmediatePropagation() - - PREV_INDEX = currentIndex - $listItems = $(selector, @dropdown) - - # if @options.filterable - # $input.blur() - - if currentKeyCode is 40 - # Move down - currentIndex += 1 if currentIndex < ($listItems.length - 1) - else if currentKeyCode is 38 - # Move up - currentIndex -= 1 if currentIndex > 0 - - @highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX - - return false - - # If enter is pressed and a row is highlighted, select it - if currentKeyCode is 13 and currentIndex isnt -1 - e.preventDefault() - e.stopImmediatePropagation() - @selectRowAtIndex e, currentIndex - - removeArrayKeyEvent: -> - $('body').off 'keydown' - - # Resets the currently selected item row index and removes all highlights - resetRows: -> - currentIndex = -1 - $('.is-focused', @dropdown).removeClass 'is-focused' - - highlightRowAtIndex: ($listItems, index) -> - # Remove the class for the previously focused row - $('.is-focused', @dropdown).removeClass 'is-focused' - - # Update the class for the row at the specific index - $listItem = $listItems.eq(index) - $listItem.find('a:first-child').addClass "is-focused" - - # Dropdown content scroll area - $dropdownContent = $listItem.closest('.dropdown-content') - dropdownScrollTop = $dropdownContent.scrollTop() - dropdownContentHeight = $dropdownContent.outerHeight() - dropdownContentTop = $dropdownContent.prop('offsetTop') - dropdownContentBottom = dropdownContentTop + dropdownContentHeight - - # Get the offset bottom of the list item - listItemHeight = $listItem.outerHeight() - listItemTop = $listItem.prop('offsetTop') - listItemBottom = listItemTop + listItemHeight - - if index is 0 - # If this is the first item in the list, scroll to the top - $dropdownContent.scrollTop(0) - else if index is $listItems.length - 1 - # If this is the last item in the list, scroll to the bottom - $dropdownContent.scrollTop $dropdownContent.prop 'scrollHeight' - else if listItemBottom > dropdownContentBottom + dropdownScrollTop - # Scroll the dropdown content down with a little padding - $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING) - else if listItemTop < dropdownContentTop + dropdownScrollTop - # Scroll the dropdown content up with a little padding - $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING) - - updateLabel: (selected = null, el = null, instance = null) => - $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance) - -$.fn.glDropdown = (opts) -> - return @.each -> - if (!$.data @, 'glDropdown') - $.data(@, 'glDropdown', new GitLabDropdown @, opts) diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 990f6536eb2..cd39f5f94e2 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -7,7 +7,9 @@ KEYCODE = { ESCAPE: 27, BACKSPACE: 8, - ENTER: 13 + ENTER: 13, + UP: 38, + DOWN: 40 }; function SearchAutocomplete(opts) { @@ -223,6 +225,12 @@ case KEYCODE.ESCAPE: this.restoreOriginalState(); break; + case KEYCODE.ENTER: + this.disableAutocomplete(); + break; + case KEYCODE.UP, + case KEYCODE.DOWN: + return; default: if (this.searchInput.val() === '') { this.disableAutocomplete(); @@ -319,9 +327,11 @@ }; SearchAutocomplete.prototype.disableAutocomplete = function() { - this.searchInput.addClass('disabled'); - this.dropdown.removeClass('open'); - return this.restoreMenu(); + if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) { + this.searchInput.addClass('disabled'); + this.dropdown.removeClass('open').trigger('hidden.bs.dropdown'); + this.restoreMenu(); + } }; SearchAutocomplete.prototype.restoreMenu = function() { diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee deleted file mode 100644 index fc74959b1e1..00000000000 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ /dev/null @@ -1,348 +0,0 @@ -class @SearchAutocomplete - - KEYCODE = - ESCAPE: 27 - BACKSPACE: 8 - ENTER: 13 - UP: 38 - DOWN: 40 - - constructor: (opts = {}) -> - { - @wrap = $('.search') - - @optsEl = @wrap.find('.search-autocomplete-opts') - @autocompletePath = @optsEl.data('autocomplete-path') - @projectId = @optsEl.data('autocomplete-project-id') || '' - @projectRef = @optsEl.data('autocomplete-project-ref') || '' - - } = opts - - # Dropdown Element - @dropdown = @wrap.find('.dropdown') - @dropdownContent = @dropdown.find('.dropdown-content') - - @locationBadgeEl = @getElement('.location-badge') - @scopeInputEl = @getElement('#scope') - @searchInput = @getElement('.search-input') - @projectInputEl = @getElement('#search_project_id') - @groupInputEl = @getElement('#group_id') - @searchCodeInputEl = @getElement('#search_code') - @repositoryInputEl = @getElement('#repository_ref') - @clearInput = @getElement('.js-clear-input') - - @saveOriginalState() - - # Only when user is logged in - @createAutocomplete() if gon.current_user_id - - @searchInput.addClass('disabled') - - @saveTextLength() - - @bindEvents() - - # Finds an element inside wrapper element - getElement: (selector) -> - @wrap.find(selector) - - saveOriginalState: -> - @originalState = @serializeState() - - saveTextLength: -> - @lastTextLength = @searchInput.val().length - - createAutocomplete: -> - @searchInput.glDropdown - filterInputBlur: false - filterable: true - filterRemote: true - highlight: true - enterCallback: false - filterInput: 'input#search' - search: - fields: ['text'] - data: @getData.bind(@) - selectable: true - clicked: @onClick.bind(@) - - getData: (term, callback) -> - _this = @ - - unless term - if contents = @getCategoryContents() - @searchInput.data('glDropdown').filter.options.callback contents - @enableAutocomplete() - - return - - # Prevent multiple ajax calls - return if @loadingSuggestions - - @loadingSuggestions = true - - jqXHR = $.get(@autocompletePath, { - project_id: @projectId - project_ref: @projectRef - term: term - }, (response) -> - # Hide dropdown menu if no suggestions returns - if !response.length - _this.disableAutocomplete() - return - - data = [] - - # List results - firstCategory = true - for suggestion in response - - # Add group header before list each group - if lastCategory isnt suggestion.category - data.push 'separator' if !firstCategory - - firstCategory = false if firstCategory - - data.push - header: suggestion.category - - lastCategory = suggestion.category - - data.push - id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}" - category: suggestion.category - text: suggestion.label - url: suggestion.url - - # Add option to proceed with the search - if data.length - data.push('separator') - data.push - text: "Result name contains \"#{term}\"" - url: "/search?\ - search=#{term}\ - &project_id=#{_this.projectInputEl.val()}\ - &group_id=#{_this.groupInputEl.val()}" - - callback(data) - ).always -> - _this.loadingSuggestions = false - - - getCategoryContents: -> - - userId = gon.current_user_id - { utils, projectOptions, groupOptions, dashboardOptions } = gl - - if utils.isInGroupsPage() and groupOptions - options = groupOptions[utils.getGroupSlug()] - - else if utils.isInProjectPage() and projectOptions - options = projectOptions[utils.getProjectSlug()] - - else if dashboardOptions - options = dashboardOptions - - { issuesPath, mrPath, name } = options - - items = [ - { header: "#{name}" } - { text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" } - { text: "Issues I've created", url: "#{issuesPath}/?author_id=#{userId}" } - 'separator' - { text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" } - { text: "Merge requests I've created", url: "#{mrPath}/?author_id=#{userId}" } - ] - - items.splice 0, 1 unless name - - return items - - - serializeState: -> - { - # Search Criteria - search_project_id: @projectInputEl.val() - group_id: @groupInputEl.val() - search_code: @searchCodeInputEl.val() - repository_ref: @repositoryInputEl.val() - scope: @scopeInputEl.val() - - # Location badge - _location: @locationBadgeEl.text() - } - - bindEvents: -> - @searchInput.on 'keydown', @onSearchInputKeyDown - @searchInput.on 'keyup', @onSearchInputKeyUp - @searchInput.on 'click', @onSearchInputClick - @searchInput.on 'focus', @onSearchInputFocus - @searchInput.on 'blur', @onSearchInputBlur - @clearInput.on 'click', @onClearInputClick - @locationBadgeEl.on 'click', => - @searchInput.focus() - - enableAutocomplete: -> - # No need to enable anything if user is not logged in - return if !gon.current_user_id - - unless @dropdown.hasClass('open') - _this = @ - @loadingSuggestions = false - - # If not enabled already, enable - if not @dropdown.hasClass('open') - # Open dropdown and invoke its opened() method - @dropdown.addClass('open') - .trigger('shown.bs.dropdown') - @searchInput.removeClass('disabled') - - onSearchInputKeyDown: => - # Saves last length of the entered text - @saveTextLength() - - onSearchInputKeyUp: (e) => - switch e.keyCode - when KEYCODE.BACKSPACE - # when trying to remove the location badge - if @lastTextLength is 0 and @badgePresent() - @removeLocationBadge() - - # When removing the last character and no badge is present - if @lastTextLength is 1 - @disableAutocomplete() - - # When removing any character from existin value - if @lastTextLength > 1 - @enableAutocomplete() - - when KEYCODE.ESCAPE - @restoreOriginalState() - - # Close autocomplete on enter - when KEYCODE.ENTER - @disableAutocomplete() - - when KEYCODE.UP, KEYCODE.DOWN - return - - else - # Handle the case when deleting the input value other than backspace - # e.g. Pressing ctrl + backspace or ctrl + x - if @searchInput.val() is '' - @disableAutocomplete() - else - # We should display the menu only when input is not empty - @enableAutocomplete() - - @wrap.toggleClass 'has-value', !!e.target.value - - # Avoid falsy value to be returned - return - - onSearchInputClick: (e) => - # Prevents closing the dropdown menu - e.stopImmediatePropagation() - - onSearchInputFocus: => - @isFocused = true - @wrap.addClass('search-active') - - @getData() if @getValue() is '' - - - getValue: -> return @searchInput.val() - - - onClearInputClick: (e) => - e.preventDefault() - @searchInput.val('').focus() - - onSearchInputBlur: (e) => - @isFocused = false - @wrap.removeClass('search-active') - - # If input is blank then restore state - if @searchInput.val() is '' - @restoreOriginalState() - - addLocationBadge: (item) -> - category = if item.category? then "#{item.category}: " else '' - value = if item.value? then item.value else '' - - badgeText = "#{category}#{value}" - @locationBadgeEl.text(badgeText).show() - @wrap.addClass('has-location-badge') - - - hasLocationBadge: -> return @wrap.is '.has-location-badge' - - - restoreOriginalState: -> - inputs = Object.keys @originalState - - for input in inputs - @getElement("##{input}").val(@originalState[input]) - - if @originalState._location is '' - @locationBadgeEl.hide() - else - @addLocationBadge( - value: @originalState._location - ) - - badgePresent: -> - @locationBadgeEl.length - - resetSearchState: -> - inputs = Object.keys @originalState - - for input in inputs - - # _location isnt a input - break if input is '_location' - - @getElement("##{input}").val('') - - - removeLocationBadge: -> - - @locationBadgeEl.hide() - @resetSearchState() - @wrap.removeClass('has-location-badge') - @disableAutocomplete() - - - disableAutocomplete: -> - # If not disabled already, disable - if not @searchInput.hasClass('disabled') and @dropdown.hasClass 'open' - @searchInput.addClass('disabled') - # Close dropdown and invoke its hidden() method - @dropdown.removeClass('open').trigger 'hidden.bs.dropdown' - @restoreMenu() - - restoreMenu: -> - html = "" - @dropdownContent.html(html) - - onClick: (item, $el, e) -> - if location.pathname.indexOf(item.url) isnt -1 - e.preventDefault() - if not @badgePresent - if item.category is 'Projects' - @projectInputEl.val(item.id) - @addLocationBadge( - value: 'This project' - ) - - if item.category is 'Groups' - @groupInputEl.val(item.id) - @addLocationBadge( - value: 'This group' - ) - - $el.removeClass('is-active') - @disableAutocomplete() - @searchInput.val('').focus() diff --git a/spec/javascripts/gl_dropdown_spec.js.coffee b/spec/javascripts/gl_dropdown_spec.js.coffee deleted file mode 100644 index a4b3f519745..00000000000 --- a/spec/javascripts/gl_dropdown_spec.js.coffee +++ /dev/null @@ -1,97 +0,0 @@ -#= require jquery -#= require gl_dropdown -#= require turbolinks -#= require lib/utils/common_utils -#= require lib/utils/type_utility - -NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link' -ITEM_SELECTOR = ".dropdown-content li:not(#{NON_SELECTABLE_CLASSES})" -FOCUSED_ITEM_SELECTOR = ITEM_SELECTOR + ' a.is-focused' - -ARROW_KEYS = - DOWN: 40 - UP: 38 - ENTER: 13 - ESC: 27 - -navigateWithKeys = (direction, steps, cb, i) -> - i = i || 0 - $('body').trigger - type: 'keydown' - which: ARROW_KEYS[direction.toUpperCase()] - keyCode: ARROW_KEYS[direction.toUpperCase()] - i++ - if i <= steps - navigateWithKeys direction, steps, cb, i - else - cb() - -initDropdown = -> - @dropdownContainerElement = $('.dropdown.inline') - @dropdownMenuElement = $('.dropdown-menu', @dropdownContainerElement) - @projectsData = fixture.load('projects.json')[0] - @dropdownButtonElement = $('#js-project-dropdown', @dropdownContainerElement).glDropdown - selectable: true - data: @projectsData - text: (project) -> - (project.name_with_namespace or project.name) - id: (project) -> - project.id - -describe 'Dropdown', -> - fixture.preload 'gl_dropdown.html' - fixture.preload 'projects.json' - - beforeEach -> - fixture.load 'gl_dropdown.html' - initDropdown.call this - - afterEach -> - $('body').unbind 'keydown' - @dropdownContainerElement.unbind 'keyup' - - it 'should open on click', -> - expect(@dropdownContainerElement).not.toHaveClass 'open' - @dropdownButtonElement.click() - expect(@dropdownContainerElement).toHaveClass 'open' - - describe 'that is open', -> - beforeEach -> - @dropdownButtonElement.click() - - it 'should select a following item on DOWN keypress', -> - expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 0 - randomIndex = Math.floor(Math.random() * (@projectsData.length - 1)) + 0 - navigateWithKeys 'down', randomIndex, => - expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 1 - expect($("#{ITEM_SELECTOR}:eq(#{randomIndex}) a", @dropdownMenuElement)).toHaveClass 'is-focused' - - it 'should select a previous item on UP keypress', -> - expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 0 - navigateWithKeys 'down', (@projectsData.length - 1), => - expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 1 - randomIndex = Math.floor(Math.random() * (@projectsData.length - 2)) + 0 - navigateWithKeys 'up', randomIndex, => - expect($(FOCUSED_ITEM_SELECTOR, @dropdownMenuElement).length).toBe 1 - expect($("#{ITEM_SELECTOR}:eq(#{((@projectsData.length - 2) - randomIndex)}) a", @dropdownMenuElement)).toHaveClass 'is-focused' - - it 'should click the selected item on ENTER keypress', -> - expect(@dropdownContainerElement).toHaveClass 'open' - randomIndex = Math.floor(Math.random() * (@projectsData.length - 1)) + 0 - navigateWithKeys 'down', randomIndex, => - spyOn(Turbolinks, 'visit').and.stub() - navigateWithKeys 'enter', null, => - expect(@dropdownContainerElement).not.toHaveClass 'open' - link = $("#{ITEM_SELECTOR}:eq(#{randomIndex}) a", @dropdownMenuElement) - expect(link).toHaveClass 'is-active' - linkedLocation = link.attr 'href' - if linkedLocation and linkedLocation isnt '#' - expect(Turbolinks.visit).toHaveBeenCalledWith linkedLocation - - it 'should close on ESC keypress', -> - expect(@dropdownContainerElement).toHaveClass 'open' - @dropdownContainerElement.trigger - type: 'keyup' - which: ARROW_KEYS.ESC - keyCode: ARROW_KEYS.ESC - expect(@dropdownContainerElement).not.toHaveClass 'open' diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 new file mode 100644 index 00000000000..733fccc51d9 --- /dev/null +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -0,0 +1,120 @@ +/*= require jquery */ +/*= require gl_dropdown */ +/*= require turbolinks */ +/*= require lib/utils/common_utils */ +/*= require lib/utils/type_utility */ + +const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; +const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`; +const FOCUSED_ITEM_SELECTOR = `${ITEM_SELECTOR} a.is-focused`; + +const ARROW_KEYS = { + DOWN: 40, + UP: 38, + ENTER: 13, + ESC: 27 +}; + +var navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { + i = i || 0; + $('body').trigger({ + type: 'keydown', + which: ARROW_KEYS[direction.toUpperCase()], + keyCode: ARROW_KEYS[direction.toUpperCase()] + }); + i++; + if (i <= steps) { + navigateWithKeys(direction, steps, cb, i); + } else { + cb(); + } +}; + +var initDropdown = function initDropdown() { + this.dropdownContainerElement = $('.dropdown.inline'); + this.dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); + this.projectsData = fixture.load('projects.json')[0]; + this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({ + selectable: true, + data: this.projectsData, + text: (project) => { + (project.name_with_namespace || project.name) + }, + id: (project) => { + project.id + } + }); +}; + +describe('Dropdown', function describeDropdown() { + fixture.preload('gl_dropdown.html'); + fixture.preload('projects.json'); + + function beforeEach() { + fixture.load('gl_dropdown.html'); + initDropdown.call(this); + } + + function afterEach() { + $('body').unbind('keydown'); + this.dropdownContainerElement.unbind('keyup'); + } + + it('should open on click', () => { + expect(this.dropdownContainerElement).not.toHaveClass('open'); + this.dropdownButtonElement.click(); + expect(this.dropdownContainerElement).toHaveClass('open'); + }); + + describe('that is open', function describeThatIsOpen() { + function beforeEach() { + this.dropdownButtonElement.click(); + } + + it('should select a following item on DOWN keypress', () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(0); + let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0); + navigateWithKeys('down', randomIndex, () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); + expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.dropdownMenuElement)).toHaveClass('is-focused'); + }); + }); + + it('should select a previous item on UP keypress', () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(0); + navigateWithKeys('down', (this.projectsData.length - 1), () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); + let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0); + navigateWithKeys('up', randomIndex, () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); + expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.dropdownMenuElement)).toHaveClass('is-focused'); + }); + }); + }); + + it('should click the selected item on ENTER keypress', () => { + expect(this.dropdownContainerElement).toHaveClass('open') + let randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0 + navigateWithKeys('down', randomIndex, () => { + spyOn(Turbolinks, 'visit').and.stub(); + navigateWithKeys('enter', null, () => { + expect(this.dropdownContainerElement).not.toHaveClass('open'); + let link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.dropdownMenuElement); + expect(link).toHaveClass('is-active'); + let linkedLocation = link.attr('href'); + if (linkedLocation && linkedLocation !== '#') expect(Turbolinks.visit).toHaveBeenCalledWith(linkedLocation); + }); + }); + }); + + it('should close on ESC keypress', () => { + expect(this.dropdownContainerElement).toHaveClass('open'); + this.dropdownContainerElement.trigger({ + type: 'keyup', + which: ARROW_KEYS.ESC, + keyCode: ARROW_KEYS.ESC + }); + expect(this.dropdownContainerElement).not.toHaveClass('open'); + }); + }); +}); -- cgit v1.2.1 From f91fd18f25626dc3e76c9aefef2841f685fab26c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 27 Jul 2016 17:38:08 +0100 Subject: Improved spec, waiting on sprockets-es6 to work with rspec.... --- spec/javascripts/gl_dropdown_spec.js.es6 | 186 ++++++++++++++++--------------- 1 file changed, 94 insertions(+), 92 deletions(-) diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 index 733fccc51d9..2dda566beb6 100644 --- a/spec/javascripts/gl_dropdown_spec.js.es6 +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -4,117 +4,119 @@ /*= require lib/utils/common_utils */ /*= require lib/utils/type_utility */ -const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; -const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`; -const FOCUSED_ITEM_SELECTOR = `${ITEM_SELECTOR} a.is-focused`; +(() => { + const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; + const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`; + const FOCUSED_ITEM_SELECTOR = `${ITEM_SELECTOR} a.is-focused`; -const ARROW_KEYS = { - DOWN: 40, - UP: 38, - ENTER: 13, - ESC: 27 -}; + const ARROW_KEYS = { + DOWN: 40, + UP: 38, + ENTER: 13, + ESC: 27 + }; -var navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { - i = i || 0; - $('body').trigger({ - type: 'keydown', - which: ARROW_KEYS[direction.toUpperCase()], - keyCode: ARROW_KEYS[direction.toUpperCase()] - }); - i++; - if (i <= steps) { - navigateWithKeys(direction, steps, cb, i); - } else { - cb(); - } -}; - -var initDropdown = function initDropdown() { - this.dropdownContainerElement = $('.dropdown.inline'); - this.dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); - this.projectsData = fixture.load('projects.json')[0]; - this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({ - selectable: true, - data: this.projectsData, - text: (project) => { - (project.name_with_namespace || project.name) - }, - id: (project) => { - project.id + var navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { + i = i || 0; + $('body').trigger({ + type: 'keydown', + which: ARROW_KEYS[direction.toUpperCase()], + keyCode: ARROW_KEYS[direction.toUpperCase()] + }); + i++; + if (i <= steps) { + navigateWithKeys(direction, steps, cb, i); + } else { + cb(); } - }); -}; + }; -describe('Dropdown', function describeDropdown() { - fixture.preload('gl_dropdown.html'); - fixture.preload('projects.json'); + var initDropdown = function initDropdown() { + this.dropdownContainerElement = $('.dropdown.inline'); + this.dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); + this.projectsData = fixture.load('projects.json')[0]; + this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({ + selectable: true, + data: this.projectsData, + text: (project) => { + (project.name_with_namespace || project.name) + }, + id: (project) => { + project.id + } + }); + }; - function beforeEach() { - fixture.load('gl_dropdown.html'); - initDropdown.call(this); - } + describe('Dropdown', function describeDropdown() { + fixture.preload('gl_dropdown.html'); + fixture.preload('projects.json'); - function afterEach() { - $('body').unbind('keydown'); - this.dropdownContainerElement.unbind('keyup'); - } + function beforeEach() { + fixture.load('gl_dropdown.html'); + initDropdown.call(this); + } - it('should open on click', () => { - expect(this.dropdownContainerElement).not.toHaveClass('open'); - this.dropdownButtonElement.click(); - expect(this.dropdownContainerElement).toHaveClass('open'); - }); + function afterEach() { + $('body').unbind('keydown'); + this.dropdownContainerElement.unbind('keyup'); + } - describe('that is open', function describeThatIsOpen() { - function beforeEach() { + it('should open on click', () => { + expect(this.dropdownContainerElement).not.toHaveClass('open'); this.dropdownButtonElement.click(); - } + expect(this.dropdownContainerElement).toHaveClass('open'); + }); + + describe('that is open', function describeThatIsOpen() { + function beforeEach() { + this.dropdownButtonElement.click(); + } - it('should select a following item on DOWN keypress', () => { - expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(0); - let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0); - navigateWithKeys('down', randomIndex, () => { - expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); - expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.dropdownMenuElement)).toHaveClass('is-focused'); + it('should select a following item on DOWN keypress', () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(0); + let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 1)) + 0); + navigateWithKeys('down', randomIndex, () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); + expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.dropdownMenuElement)).toHaveClass('is-focused'); + }); }); - }); - it('should select a previous item on UP keypress', () => { - expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(0); - navigateWithKeys('down', (this.projectsData.length - 1), () => { - expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); - let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0); - navigateWithKeys('up', randomIndex, () => { + it('should select a previous item on UP keypress', () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(0); + navigateWithKeys('down', (this.projectsData.length - 1), () => { expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); - expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.dropdownMenuElement)).toHaveClass('is-focused'); + let randomIndex = (Math.floor(Math.random() * (this.projectsData.length - 2)) + 0); + navigateWithKeys('up', randomIndex, () => { + expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(1); + expect($(`${ITEM_SELECTOR}:eq(${((this.projectsData.length - 2) - randomIndex)}) a`, this.dropdownMenuElement)).toHaveClass('is-focused'); + }); }); }); - }); - it('should click the selected item on ENTER keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('open') - let randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0 - navigateWithKeys('down', randomIndex, () => { - spyOn(Turbolinks, 'visit').and.stub(); - navigateWithKeys('enter', null, () => { - expect(this.dropdownContainerElement).not.toHaveClass('open'); - let link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.dropdownMenuElement); - expect(link).toHaveClass('is-active'); - let linkedLocation = link.attr('href'); - if (linkedLocation && linkedLocation !== '#') expect(Turbolinks.visit).toHaveBeenCalledWith(linkedLocation); + it('should click the selected item on ENTER keypress', () => { + expect(this.dropdownContainerElement).toHaveClass('open') + let randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0 + navigateWithKeys('down', randomIndex, () => { + spyOn(Turbolinks, 'visit').and.stub(); + navigateWithKeys('enter', null, () => { + expect(this.dropdownContainerElement).not.toHaveClass('open'); + let link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.dropdownMenuElement); + expect(link).toHaveClass('is-active'); + let linkedLocation = link.attr('href'); + if (linkedLocation && linkedLocation !== '#') expect(Turbolinks.visit).toHaveBeenCalledWith(linkedLocation); + }); }); }); - }); - it('should close on ESC keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('open'); - this.dropdownContainerElement.trigger({ - type: 'keyup', - which: ARROW_KEYS.ESC, - keyCode: ARROW_KEYS.ESC + it('should close on ESC keypress', () => { + expect(this.dropdownContainerElement).toHaveClass('open'); + this.dropdownContainerElement.trigger({ + type: 'keyup', + which: ARROW_KEYS.ESC, + keyCode: ARROW_KEYS.ESC + }); + expect(this.dropdownContainerElement).not.toHaveClass('open'); }); - expect(this.dropdownContainerElement).not.toHaveClass('open'); }); }); -}); +})(window); -- cgit v1.2.1 From 49c7ca2a27639809dd84975d7595787ab5f60647 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 28 Jul 2016 17:32:43 +0100 Subject: Fixed spec and improved formatting --- app/assets/javascripts/gl_dropdown.js | 4 +- app/assets/javascripts/search_autocomplete.js | 2 +- spec/javascripts/gl_dropdown_spec.js.es6 | 53 +++++++++++++-------------- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index aacc49be1ac..58e27acf2f1 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -690,8 +690,8 @@ if (!index) { $dropdownContent.scrollTop(0) } else if (index === ($listItems.length - 1)) { - $dropdownContent.scrollTop $dropdownContent.prop('scrollHeight'); - } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) + $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); + } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) { $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING); diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index cd39f5f94e2..4579214bd2f 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -228,7 +228,7 @@ case KEYCODE.ENTER: this.disableAutocomplete(); break; - case KEYCODE.UP, + case KEYCODE.UP: case KEYCODE.DOWN: return; default: diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 index 2dda566beb6..4ed194f5179 100644 --- a/spec/javascripts/gl_dropdown_spec.js.es6 +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -16,12 +16,13 @@ ESC: 27 }; - var navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { + let navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { i = i || 0; + if (!i) direction = direction.toUpperCase(); $('body').trigger({ type: 'keydown', - which: ARROW_KEYS[direction.toUpperCase()], - keyCode: ARROW_KEYS[direction.toUpperCase()] + which: ARROW_KEYS[direction], + keyCode: ARROW_KEYS[direction] }); i++; if (i <= steps) { @@ -31,35 +32,31 @@ } }; - var initDropdown = function initDropdown() { - this.dropdownContainerElement = $('.dropdown.inline'); - this.dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); - this.projectsData = fixture.load('projects.json')[0]; - this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({ - selectable: true, - data: this.projectsData, - text: (project) => { - (project.name_with_namespace || project.name) - }, - id: (project) => { - project.id - } - }); - }; - describe('Dropdown', function describeDropdown() { fixture.preload('gl_dropdown.html'); fixture.preload('projects.json'); - function beforeEach() { + beforeEach(() => { fixture.load('gl_dropdown.html'); - initDropdown.call(this); - } + this.dropdownContainerElement = $('.dropdown.inline'); + this.dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); + this.projectsData = fixture.load('projects.json')[0]; + this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({ + selectable: true, + data: this.projectsData, + text: (project) => { + (project.name_with_namespace || project.name) + }, + id: (project) => { + project.id + } + }); + }); - function afterEach() { + afterEach(() => { $('body').unbind('keydown'); this.dropdownContainerElement.unbind('keyup'); - } + }); it('should open on click', () => { expect(this.dropdownContainerElement).not.toHaveClass('open'); @@ -67,10 +64,10 @@ expect(this.dropdownContainerElement).toHaveClass('open'); }); - describe('that is open', function describeThatIsOpen() { - function beforeEach() { + describe('that is open', () => { + beforeEach(() => { this.dropdownButtonElement.click(); - } + }); it('should select a following item on DOWN keypress', () => { expect($(FOCUSED_ITEM_SELECTOR, this.dropdownMenuElement).length).toBe(0); @@ -119,4 +116,4 @@ }); }); }); -})(window); +})(); -- cgit v1.2.1 From 3e1e9b670fdb12159e31664c3757df6696db90e1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 28 Jul 2016 23:46:41 +0100 Subject: Fixed specs --- app/assets/javascripts/gl_dropdown.js | 5 +++-- spec/javascripts/search_autocomplete_spec.js | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 58e27acf2f1..7f7f76224c4 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -617,6 +617,7 @@ } $el = $(selector, this.dropdown); if ($el.length) { + return $el.first().trigger('click'); e.preventDefault(); e.stopImmediatePropagation(); $el.first().trigger('click'); @@ -690,8 +691,8 @@ if (!index) { $dropdownContent.scrollTop(0) } else if (index === ($listItems.length - 1)) { - $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); - } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) { + $dropdownContent.scrollTop $dropdownContent.prop('scrollHeight'); + } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING); diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 68d64483d67..324f5152780 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -105,13 +105,13 @@ a3 = "a[href='" + mrsAssignedToMeLink + "']"; a4 = "a[href='" + mrsIHaveCreatedLink + "']"; expect(list.find(a1).length).toBe(1); - expect(list.find(a1).text()).toBe(' Issues assigned to me '); + 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 "); + expect(list.find(a2).text()).toBe("Issues I've created"); expect(list.find(a3).length).toBe(1); - expect(list.find(a3).text()).toBe(' Merge requests assigned to me '); + 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 "); + return expect(list.find(a4).text()).toBe("Merge requests I've created"); }; describe('Search autocomplete dropdown', function() { -- cgit v1.2.1 From 2f8fada6c06d7ed67307119bf22e9743429c18be Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 11 Aug 2016 15:10:23 +0100 Subject: Fixed conflicts --- app/assets/javascripts/gl_dropdown.js | 27 ++++++++++++--------------- app/assets/javascripts/search_autocomplete.js | 2 +- spec/javascripts/gl_dropdown_spec.js.es6 | 4 ++-- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 7f7f76224c4..bbda6387c77 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -111,9 +111,9 @@ matches = fuzzaldrinPlus.match($el.text().trim(), search_text); if (!$el.is('.dropdown-header')) { if (matches.length) { - return $el.show(); + return $el.show().removeClass('option-hidden'); } else { - return $el.hide(); + return $el.hide().addClass('option-hidden'); } } }); @@ -191,9 +191,9 @@ currentIndex = -1; - NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; + NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden'; - SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")"; + SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")"; CURSOR_SELECT_SCROLL_PADDING = 5 @@ -247,7 +247,7 @@ keys: searchFields, elements: (function(_this) { return function() { - selector = SELECTABLE_CLASSES; + selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')'; if (_this.dropdown.find('.dropdown-toggle-page').length) { selector = ".dropdown-page-one " + selector; } @@ -263,7 +263,7 @@ return function(data) { _this.parseData(data); if (_this.filterInput.val() !== '') { - selector = '.dropdown-content li:not(.divider):visible'; + selector = SELECTABLE_CLASSES; if (_this.dropdown.find('.dropdown-toggle-page').length) { selector = ".dropdown-page-one " + selector; } @@ -502,7 +502,7 @@ text = this.highlightTextMatches(text, this.filterInput.val()); } if (group) { - groupAttrs = "data-group='" + group + "' data-index='" + index + "'"; + groupAttrs = 'data-group=' + group + ' data-index=' + index; } else { groupAttrs = ''; } @@ -617,11 +617,8 @@ } $el = $(selector, this.dropdown); if ($el.length) { - return $el.first().trigger('click'); - e.preventDefault(); - e.stopImmediatePropagation(); $el.first().trigger('click'); - href = $el.attr('href'); + var href = $el.attr('href'); if (href && href !== '#') Turbolinks.visit(href); } }; @@ -671,8 +668,8 @@ }; GitLabDropdown.prototype.resetRows = function resetRows() { - currentIndex = -1; - $('.is-focused', this.dropdown).removeClass('is-focused'); + currentIndex = -1; + $('.is-focused', this.dropdown).removeClass('is-focused'); }; GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) { @@ -691,8 +688,8 @@ if (!index) { $dropdownContent.scrollTop(0) } else if (index === ($listItems.length - 1)) { - $dropdownContent.scrollTop $dropdownContent.prop('scrollHeight'); - } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) + $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); + } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) { $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING); diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 4579214bd2f..37451d406ec 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -9,7 +9,7 @@ BACKSPACE: 8, ENTER: 13, UP: 38, - DOWN: 40 + DOWN: 40 }; function SearchAutocomplete(opts) { diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 index 4ed194f5179..b529ea6458d 100644 --- a/spec/javascripts/gl_dropdown_spec.js.es6 +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -45,10 +45,10 @@ selectable: true, data: this.projectsData, text: (project) => { - (project.name_with_namespace || project.name) + (project.name_with_namespace || project.name); }, id: (project) => { - project.id + project.id; } }); }); -- cgit v1.2.1 From 8086526cf013c4630402db2be47de9020223018c Mon Sep 17 00:00:00 2001 From: winniehell Date: Fri, 19 Aug 2016 10:39:47 +0200 Subject: Fix alignment of icon buttons (!5887) --- CHANGELOG | 1 + app/assets/stylesheets/framework/buttons.scss | 4 +++- app/views/projects/buttons/_fork.html.haml | 4 ++-- app/views/projects/forks/index.html.haml | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7ea2631f9f4..d1005c8c8a0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,7 @@ v 8.11.0 (unreleased) - Get issue and merge request description templates from repositories - Add hover state to todos !5361 (winniehell) - Fix icon alignment of star and fork buttons !5451 (winniehell) + - Fix alignment of icon buttons !5887 (winniehell) - Enforce 2FA restrictions on API authentication endpoints !5820 - Limit git rev-list output count to one in forced push check - Show deployment status on merge requests with external URLs diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 6c3786b49bb..716e79a2354 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -206,7 +206,9 @@ } svg, .fa { - margin-right: 3px; + &:not(:last-child) { + margin-right: 3px; + } } } diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index d78888e9fe4..22db33498f1 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -3,11 +3,11 @@ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do = custom_icon('icon_fork') - Fork + %span Fork - else = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do = custom_icon('icon_fork') - Fork + %span Fork %div.count-with-arrow %span.arrow = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index a1d79bdabda..bacc5708e4b 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -32,11 +32,11 @@ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do = custom_icon('icon_fork') - Fork + %span Fork - else = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do = custom_icon('icon_fork') - Fork + %span Fork = render 'projects', projects: @forks -- cgit v1.2.1 From e067e699c3250882ea08594243b6c6d3ad84d6e0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 19 Aug 2016 12:35:16 +0300 Subject: Make sure merge request is fetched before collecting base sha in merge request diff Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index a769d31ac93..42ab6b620bd 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -41,6 +41,7 @@ class MergeRequestDiff < ActiveRecord::Base end def ensure_commits_sha + merge_request.fetch_ref self.start_commit_sha ||= merge_request.target_branch_sha self.head_commit_sha ||= merge_request.source_branch_sha self.base_commit_sha ||= find_base_sha -- cgit v1.2.1 From 74461ccc1f1503c86102b7d8e790ebac0d28fc0b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 19 Aug 2016 12:35:40 +0300 Subject: Fix merge request versions api doc Signed-off-by: Dmitriy Zaporozhets --- doc/api/merge_requests.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 66957647d00..1cf4940198f 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -909,7 +909,7 @@ GET /projects/:id/merge_requests/:merge_request_id/versions | `merge_request_id` | integer | yes | The ID of the merge request | ```bash -curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions ``` Example response: @@ -951,7 +951,7 @@ GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id | `version_id` | integer | yes | The ID of the merge request diff version | ```bash -curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions/1 +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/merge_requests/1/versions/1 ``` Example response: -- cgit v1.2.1 From 1aba3a5c0e7c2b727f4317aab19bbc662f0fd727 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 19 Aug 2016 17:42:49 +0800 Subject: Unify pipeline_for(ref, nil) and pipeline_for(ref), feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14073464 --- app/models/project.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 678fca7afd1..e97e6abfef9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1094,8 +1094,11 @@ class Project < ActiveRecord::Base !namespace.share_with_group_lock end - def pipeline_for(ref, sha = commit(ref).try(:sha)) + def pipeline_for(ref, sha = nil) + sha ||= commit(ref).try(:sha) + return unless sha + pipelines.order(id: :desc).find_by(sha: sha, ref: ref) end -- cgit v1.2.1 From 2bd0ed4a5cb70884f1a31a54fc67c4bc86fa3340 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 19 Aug 2016 12:25:52 +0200 Subject: Fix typo in gitlab-workhorse header --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- lib/gitlab/middleware/rails_queue_duration.rb | 2 +- spec/lib/gitlab/middleware/rails_queue_duration_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index e7c7d3cc3c8..5b209ea2067 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.8 +0.7.10 diff --git a/doc/install/installation.md b/doc/install/installation.md index eb9606934cd..b0ec9b4a961 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -397,7 +397,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.8 + sudo -u git -H git checkout v0.7.10 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/lib/gitlab/middleware/rails_queue_duration.rb b/lib/gitlab/middleware/rails_queue_duration.rb index 56608b1b276..5d2d7d0026c 100644 --- a/lib/gitlab/middleware/rails_queue_duration.rb +++ b/lib/gitlab/middleware/rails_queue_duration.rb @@ -11,7 +11,7 @@ module Gitlab def call(env) trans = Gitlab::Metrics.current_transaction - proxy_start = env['HTTP_GITLAB_WORHORSE_PROXY_START'].presence + proxy_start = env['HTTP_GITLAB_WORKHORSE_PROXY_START'].presence if trans && proxy_start # Time in milliseconds since gitlab-workhorse started the request trans.set(:rails_queue_duration, Time.now.to_f * 1_000 - proxy_start.to_f / 1_000_000) diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb index fd6f684db0c..168090d5b5c 100644 --- a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb +++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Middleware::RailsQueueDuration do end it 'sets proxy_flight_time and calls the app when the header is present' do - env['HTTP_GITLAB_WORHORSE_PROXY_START'] = '123' + env['HTTP_GITLAB_WORKHORSE_PROXY_START'] = '123' expect(transaction).to receive(:set).with(:rails_queue_duration, an_instance_of(Float)) expect(middleware.call(env)).to eq('yay') end -- cgit v1.2.1 From eadb1dc547ac1d0e3eb964d077f0b1580d588a95 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 19 Aug 2016 18:46:15 +0800 Subject: Make sure the branch we're testing is on the 1st page! --- spec/features/projects/branches/download_buttons_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index a223786777b..04058300570 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -9,8 +9,8 @@ feature 'Download buttons in branches page', feature: true do given(:pipeline) do create(:ci_pipeline, project: project, - sha: project.commit.sha, - ref: project.default_branch, + sha: project.commit('binary-encoding').sha, + ref: 'binary-encoding', # make sure the branch is in the 1st page! status: status) end -- cgit v1.2.1 From cbcf00e1b972265713bb68cc679216398a44c993 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 19 Aug 2016 12:12:50 +0100 Subject: Keep committing ... spinner when conflict resolved When a conflict is resolved, we don't want to change the spinner back to the normal button before the new page load happens, so only do this on failure. --- app/assets/javascripts/merge_conflict_resolver.js.es6 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6 index 77bffbcb403..b56fd5aa658 100644 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ b/app/assets/javascripts/merge_conflict_resolver.js.es6 @@ -75,10 +75,8 @@ class MergeConflictResolver { window.location.href = data.redirect_to; }) .error(() => { - new Flash('Something went wrong!'); - }) - .always(() => { this.vue.isSubmitting = false; + new Flash('Something went wrong!'); }); } -- cgit v1.2.1 From 8ade8258603586560fad50784c23e4772706973f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 19 Aug 2016 17:17:17 +0100 Subject: review changes --- app/assets/javascripts/gl_dropdown.js | 15 ++++++--------- app/assets/javascripts/search_autocomplete.js | 6 +++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index bbda6387c77..f85aa6dd7b7 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -31,9 +31,8 @@ this.input .on('keydown', function (e) { var keyCode = e.which; - if (keyCode === 13) { - e.preventDefault() + e.preventDefault(); } }) .on('keyup', function(e) { @@ -179,7 +178,7 @@ })(); GitLabDropdown = (function() { - var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, currentIndex; + var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, currentIndex; LOADING_CLASS = "is-loading"; @@ -219,7 +218,7 @@ if (this.options.data) { if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) { this.fullData = this.options.data; - currentIndex = -1 + currentIndex = -1; this.parseData(this.options.data); } else { this.remote = new GitLabDropdownRemote(this.options.data, { @@ -619,7 +618,7 @@ if ($el.length) { $el.first().trigger('click'); var href = $el.attr('href'); - if (href && href !== '#') Turbolinks.visit(href); + if (href && href !== '#') Turbolinks.visit(href); } }; @@ -655,8 +654,6 @@ return false; } if (currentKeyCode === 13 && currentIndex !== -1) { - e.preventDefault() - e.stopImmediatePropagation() return _this.selectRowAtIndex(currentIndex); } }; @@ -687,8 +684,8 @@ listItemBottom = listItemTop + listItemHeight; if (!index) { $dropdownContent.scrollTop(0) - } else if (index === ($listItems.length - 1)) { - $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); + } else if (index === ($listItems.length - 1)) { + $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) { $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 37451d406ec..227e8c696b4 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -226,11 +226,11 @@ this.restoreOriginalState(); break; case KEYCODE.ENTER: - this.disableAutocomplete(); + this.disableAutocomplete(); break; - case KEYCODE.UP: + case KEYCODE.UP: case KEYCODE.DOWN: - return; + return; default: if (this.searchInput.val() === '') { this.disableAutocomplete(); -- cgit v1.2.1 From 0d3a9a6d31940a63ca450e3308f1e154ea4d2e5c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 18 Aug 2016 21:26:45 -0500 Subject: make input name to be a function --- app/assets/javascripts/gl_dropdown.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d3394fae3f9..2b8b934154e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -470,7 +470,8 @@ } else { if (!selected) { value = this.options.id ? this.options.id(data) : data.id; - fieldName = this.options.fieldName; + fieldName = _.isFunction(this.options.fieldName) ? this.options.fieldName() : this.options.fieldName; + field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); if (field.length) { selected = true; @@ -533,7 +534,6 @@ GitLabDropdown.prototype.rowClicked = function(el) { var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; - fieldName = this.options.fieldName; isInput = $(this.el).is('input'); if (this.renderedData) { groupName = el.data('group'); @@ -545,6 +545,7 @@ selectedObject = this.renderedData[selectedIndex]; } } + fieldName = _.isFunction(this.options.fieldName) ? this.options.fieldName(selectedObject) : this.options.fieldName; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); @@ -559,10 +560,9 @@ field.remove(); } if (this.options.toggleLabel) { - return this.updateLabel(selectedObject, el, this); - } else { - return selectedObject; + this.updateLabel(selectedObject, el, this); } + return selectedObject; } else if (el.hasClass(INDETERMINATE_CLASS)) { el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); @@ -570,7 +570,7 @@ field.remove(); } if (!field.length && fieldName) { - this.addInput(fieldName, value); + this.addInput(fieldName, value, selectedObject); } return selectedObject; } else { @@ -589,7 +589,7 @@ } if (value != null) { if (!field.length && fieldName) { - this.addInput(fieldName, value); + this.addInput(fieldName, value, selectedObject); } else { field.val(value).trigger('change'); } @@ -598,12 +598,15 @@ } }; - GitLabDropdown.prototype.addInput = function(fieldName, value) { + GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { var $input; $input = $('').attr('type', 'hidden').attr('name', fieldName).val(value); if (this.options.inputId != null) { $input.attr('id', this.options.inputId); } + if (selectedObject.type) { + $input.attr('data-type', selectedObject.type); + } return this.dropdown.before($input); }; -- cgit v1.2.1 From f3e12526e93a0b0269c41284138dc4381a24fcf8 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 19 Aug 2016 08:06:43 -0500 Subject: check if selectedObject is defined --- app/assets/javascripts/gl_dropdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 2b8b934154e..b7f27085b4f 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -604,7 +604,7 @@ if (this.options.inputId != null) { $input.attr('id', this.options.inputId); } - if (selectedObject.type) { + if (selectedObject && selectedObject.type) { $input.attr('data-type', selectedObject.type); } return this.dropdown.before($input); -- cgit v1.2.1 From 255dbb3228d55dae62ba7c1862f963ec76cd4b85 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Sat, 20 Aug 2016 00:24:12 +0530 Subject: Move protected branches access control spec into a shared example. 1. EE access control specs are significantly different, so this is a way to reduce potential merge conflicts. EE and CE specs go in separate files. --- .../protected_branches/access_control_ce_spec.rb | 71 ++++++++++++++++++++++ spec/features/protected_branches_spec.rb | 71 +--------------------- 2 files changed, 73 insertions(+), 69 deletions(-) create mode 100644 spec/features/protected_branches/access_control_ce_spec.rb diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/features/protected_branches/access_control_ce_spec.rb new file mode 100644 index 00000000000..395c61a4743 --- /dev/null +++ b/spec/features/protected_branches/access_control_ce_spec.rb @@ -0,0 +1,71 @@ +RSpec.shared_examples "protected branches > access control > CE" do + ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + it "allows creating protected branches that #{access_type_name} can push to" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + within('.new_protected_branch') do + allowed_to_push_button = find(".js-allowed-to-push") + + unless allowed_to_push_button.text == access_type_name + allowed_to_push_button.click + within(".dropdown.open .dropdown-menu") { click_on access_type_name } + end + end + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id]) + end + + it "allows updating protected branches so that #{access_type_name} can push to them" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + + within(".protected-branches-list") do + find(".js-allowed-to-push").click + within('.js-allowed-to-push-container') { click_on access_type_name } + end + + wait_for_ajax + expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id) + end + end + + ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| + it "allows creating protected branches that #{access_type_name} can merge to" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + within('.new_protected_branch') do + allowed_to_merge_button = find(".js-allowed-to-merge") + + unless allowed_to_merge_button.text == access_type_name + allowed_to_merge_button.click + within(".dropdown.open .dropdown-menu") { click_on access_type_name } + end + end + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id]) + end + + it "allows updating protected branches so that #{access_type_name} can merge to them" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + + within(".protected-branches-list") do + find(".js-allowed-to-merge").click + within('.js-allowed-to-merge-container') { click_on access_type_name } + end + + wait_for_ajax + expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id) + end + end +end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index a0ee6cab7ec..1a3f7b970f6 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +Dir["./spec/features/protected_branches/*.rb"].sort.each { |f| require f } feature 'Projected Branches', feature: true, js: true do include WaitForAjax @@ -88,74 +89,6 @@ feature 'Projected Branches', feature: true, js: true do end describe "access control" do - ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| - it "allows creating protected branches that #{access_type_name} can push to" do - visit namespace_project_protected_branches_path(project.namespace, project) - set_protected_branch_name('master') - within('.new_protected_branch') do - allowed_to_push_button = find(".js-allowed-to-push") - - unless allowed_to_push_button.text == access_type_name - allowed_to_push_button.click - within(".dropdown.open .dropdown-menu") { click_on access_type_name } - end - end - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id]) - end - - it "allows updating protected branches so that #{access_type_name} can push to them" do - visit namespace_project_protected_branches_path(project.namespace, project) - set_protected_branch_name('master') - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - - within(".protected-branches-list") do - find(".js-allowed-to-push").click - within('.js-allowed-to-push-container') { click_on access_type_name } - end - - wait_for_ajax - expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id) - end - end - - ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| - it "allows creating protected branches that #{access_type_name} can merge to" do - visit namespace_project_protected_branches_path(project.namespace, project) - set_protected_branch_name('master') - within('.new_protected_branch') do - allowed_to_merge_button = find(".js-allowed-to-merge") - - unless allowed_to_merge_button.text == access_type_name - allowed_to_merge_button.click - within(".dropdown.open .dropdown-menu") { click_on access_type_name } - end - end - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id]) - end - - it "allows updating protected branches so that #{access_type_name} can merge to them" do - visit namespace_project_protected_branches_path(project.namespace, project) - set_protected_branch_name('master') - click_on "Protect" - - expect(ProtectedBranch.count).to eq(1) - - within(".protected-branches-list") do - find(".js-allowed-to-merge").click - within('.js-allowed-to-merge-container') { click_on access_type_name } - end - - wait_for_ajax - expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id) - end - end + include_examples "protected branches > access control > CE" end end -- cgit v1.2.1 From a927a9bf6baa99be94181c3ab980947621ae2fe0 Mon Sep 17 00:00:00 2001 From: Gokmen Goksel Date: Mon, 25 Jul 2016 20:59:39 -0700 Subject: Support integration with Koding (online IDE) Koding: #index: landing page for Koding integration If enabled it will provide a link to open remote Koding instance url for now we are also providing the sneak preview video for how integration works in detail. Repository: check whether .koding.yml file exists on repository Projects: landing page: show Run in IDE (Koding) button if repo has stack file Projects: MR: show Run in IDE Koding button if repo has stack file on active branch ProjectHelpers: add_koding_stack: stack generator for provided project With this helper we will auto-generate the required stack template for a given project. For the feature we can request this base template from the running Koding instance on integration. Currently this will provide users to create a t2.nano instance on aws and it'll automatically configures the instance for basic requirements. Projects: empty state and landing page provide shortcuts to create stack projects_helper: use branch on checkout and provide an entry point This ${var.koding_queryString_branch} will be replaced with the branch provided in query string which will allow us to use same stack template for different branches of the same repository. ref: https://github.com/koding/koding/pull/8597/commits/b8c0e43c4c24bf132670aa8a3cfb0d634acfd09b projects_helper: provide sha info in query string to use existing vms With this change we'll be able to query existing vms on Koding side based on the commit id that they've created. ref: https://github.com/koding/koding/pull/8597/commits/1d630fadf31963fa6ccd3bed92e526761a30a343 Integration: Docs: Koding documentation added Disable /koding route if integration is disabled Use application settings to enable Koding Projects_helper: better indentation with strip_heredoc usage Projects_helper: return koding_url as is if there is no project provided current_settings: set koding_enabled: false by default Koding_Controller: to render not_found once integration is disabled Dashboard_specs: update spec for Koding enabled case Projects_Helper: make repo dynamic ref: https://github.com/koding/koding/pull/8597/commits/4d615242f45aaea4c4986be84ecc612b0bb1514c Updated documentation to have right format --- CHANGELOG | 1 + app/assets/images/koding-logo.svg | 8 + .../admin/application_settings_controller.rb | 2 + app/controllers/koding_controller.rb | 15 ++ app/helpers/application_settings_helper.rb | 4 + app/helpers/projects_helper.rb | 54 +++++ app/models/application_setting.rb | 6 + app/models/repository.rb | 10 +- .../admin/application_settings/_form.html.haml | 19 ++ app/views/koding/index.html.haml | 9 + app/views/layouts/koding.html.haml | 5 + app/views/layouts/nav/_dashboard.html.haml | 5 + app/views/projects/buttons/_koding.html.haml | 7 + app/views/projects/merge_requests/_show.html.haml | 3 + app/views/projects/show.html.haml | 4 +- config/routes.rb | 5 + ...817133006_add_koding_to_application_settings.rb | 10 + db/schema.rb | 2 + doc/integration/README.md | 1 + doc/integration/img/koding_build-in-progress.png | Bin 0 -> 70949 bytes doc/integration/img/koding_build-logs.png | Bin 0 -> 263623 bytes doc/integration/img/koding_build-success.png | Bin 0 -> 304666 bytes doc/integration/img/koding_commit-koding.yml.png | Bin 0 -> 302703 bytes .../img/koding_different-stack-on-mr-try.png | Bin 0 -> 333649 bytes doc/integration/img/koding_edit-on-ide.png | Bin 0 -> 330880 bytes doc/integration/img/koding_enable-koding.png | Bin 0 -> 73499 bytes doc/integration/img/koding_landing.png | Bin 0 -> 268455 bytes .../img/koding_open-gitlab-from-koding.png | Bin 0 -> 32559 bytes doc/integration/img/koding_run-in-ide.png | Bin 0 -> 65465 bytes doc/integration/img/koding_run-mr-in-ide.png | Bin 0 -> 339759 bytes doc/integration/img/koding_set-up-ide.png | Bin 0 -> 207481 bytes doc/integration/img/koding_stack-import.png | Bin 0 -> 500352 bytes doc/integration/img/koding_start-build.png | Bin 0 -> 105253 bytes doc/integration/koding-usage.md | 122 +++++++++++ doc/integration/koding.md | 239 +++++++++++++++++++++ lib/gitlab/current_settings.rb | 1 + spec/features/security/dashboard_access_spec.rb | 14 ++ spec/routing/routing_spec.rb | 9 +- 38 files changed, 552 insertions(+), 3 deletions(-) create mode 100644 app/assets/images/koding-logo.svg create mode 100644 app/controllers/koding_controller.rb create mode 100644 app/views/koding/index.html.haml create mode 100644 app/views/layouts/koding.html.haml create mode 100644 app/views/projects/buttons/_koding.html.haml create mode 100644 db/migrate/20160817133006_add_koding_to_application_settings.rb create mode 100644 doc/integration/img/koding_build-in-progress.png create mode 100644 doc/integration/img/koding_build-logs.png create mode 100644 doc/integration/img/koding_build-success.png create mode 100644 doc/integration/img/koding_commit-koding.yml.png create mode 100644 doc/integration/img/koding_different-stack-on-mr-try.png create mode 100644 doc/integration/img/koding_edit-on-ide.png create mode 100644 doc/integration/img/koding_enable-koding.png create mode 100644 doc/integration/img/koding_landing.png create mode 100644 doc/integration/img/koding_open-gitlab-from-koding.png create mode 100644 doc/integration/img/koding_run-in-ide.png create mode 100644 doc/integration/img/koding_run-mr-in-ide.png create mode 100644 doc/integration/img/koding_set-up-ide.png create mode 100644 doc/integration/img/koding_stack-import.png create mode 100644 doc/integration/img/koding_start-build.png create mode 100644 doc/integration/koding-usage.md create mode 100644 doc/integration/koding.md diff --git a/CHANGELOG b/CHANGELOG index db07afb9d96..1bb9409bb2d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.11.0 (unreleased) - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) + - Add Koding (online IDE) integration - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Add delimiter to project stars and forks count (ClemMakesApps) diff --git a/app/assets/images/koding-logo.svg b/app/assets/images/koding-logo.svg new file mode 100644 index 00000000000..ad89d684d94 --- /dev/null +++ b/app/assets/images/koding-logo.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 9e1dc15de84..6ef7cf0bae6 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -109,6 +109,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :sentry_dsn, :akismet_enabled, :akismet_api_key, + :koding_enabled, + :koding_url, :email_author_in_body, :repository_checks_enabled, :metrics_packet_size, diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb new file mode 100644 index 00000000000..bb89f3090f9 --- /dev/null +++ b/app/controllers/koding_controller.rb @@ -0,0 +1,15 @@ +class KodingController < ApplicationController + before_action :check_integration!, :authenticate_user!, :reject_blocked! + layout 'koding' + + def index + path = File.join(Rails.root, 'doc/integration/koding-usage.md') + @markdown = File.read(path) + end + + private + + def check_integration! + render_404 unless current_application_settings.koding_enabled? + end +end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 78c0b79d2bd..6de25bea654 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -31,6 +31,10 @@ module ApplicationSettingsHelper current_application_settings.akismet_enabled? end + def koding_enabled? + current_application_settings.koding_enabled? + end + def allowed_protocols_present? current_application_settings.enabled_git_access_protocol.present? end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 505545fbabb..249d18c4486 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -236,6 +236,60 @@ module ProjectsHelper ) end + def add_koding_stack_path(project) + namespace_project_new_blob_path( + project.namespace, + project, + project.default_branch || 'master', + file_name: '.koding.yml', + commit_message: "Add Koding stack script", + content: <<-CONTENT.strip_heredoc + provider: + aws: + access_key: '${var.aws_access_key}' + secret_key: '${var.aws_secret_key}' + resource: + aws_instance: + #{project.path}-vm: + instance_type: t2.nano + user_data: |- + + # Created by GitLab UI for :> + + echo _KD_NOTIFY_@Installing Base packages...@ + + apt-get update -y + apt-get install git -y + + echo _KD_NOTIFY_@Cloning #{project.name}...@ + + export KODING_USER=${var.koding_user_username} + export REPO_URL=#{root_url}${var.koding_queryString_repo}.git + export BRANCH=${var.koding_queryString_branch} + + sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH + + echo _KD_NOTIFY_@#{project.name} cloned.@ + CONTENT + ) + end + + def koding_project_url(project = nil, branch = nil, sha = nil) + if project + import_path = "/Home/Stacks/import" + + repo = project.path_with_namespace + branch ||= project.default_branch + sha ||= project.commit.short_id + + path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}" + + return URI.join(current_application_settings.koding_url, path).to_s + end + + current_application_settings.koding_url + end + def contribution_guide_path(project) if project && contribution_guide = project.repository.contribution_guide namespace_project_blob_path( diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 8c19d9dc9c8..f0bcb2d7cda 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -55,6 +55,10 @@ class ApplicationSetting < ActiveRecord::Base presence: true, if: :akismet_enabled + validates :koding_url, + presence: true, + if: :koding_enabled + validates :max_attachment_size, presence: true, numericality: { only_integer: true, greater_than: 0 } @@ -149,6 +153,8 @@ class ApplicationSetting < ActiveRecord::Base two_factor_grace_period: 48, recaptcha_enabled: false, akismet_enabled: false, + koding_enabled: false, + koding_url: nil, repository_checks_enabled: true, disabled_oauth_sign_in_sources: [], send_user_confirmation_email: false, diff --git a/app/models/repository.rb b/app/models/repository.rb index 2494c266cd2..bdc3b9d1c1c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -277,7 +277,7 @@ class Repository def cache_keys %i(size commit_count readme version contribution_guide changelog - license_blob license_key gitignore) + license_blob license_key gitignore koding_yml) end # Keys for data on branch/tag operations. @@ -553,6 +553,14 @@ class Repository end end + def koding_yml + return nil unless head_exists? + + cache.fetch(:koding_yml) do + file_on_head(/\A\.koding\.yml\z/) + end + end + def gitlab_ci_yml return nil unless head_exists? diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index c7fd344eea2..e0878512e62 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -388,6 +388,25 @@ .help-block If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database. + %fieldset + %legend Koding + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :koding_enabled do + = f.check_box :koding_enabled + Enable Koding + .form-group + = f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090' + .help-block + Koding has integration enabled out of the box for the + %strong gitlab + team, and you need to provide that team's URL here. Learn more in the + = succeed "." do + = link_to "Koding integration documentation", help_page_path("integration/koding") + .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml new file mode 100644 index 00000000000..111cc67336c --- /dev/null +++ b/app/views/koding/index.html.haml @@ -0,0 +1,9 @@ +.row-content-block.second-block.center + %p + = icon('circle', class: 'cgreen') + Integration is active for + = link_to koding_project_url, target: '_blank' do + #{current_application_settings.koding_url} + +.documentation.wiki + = markdown @markdown diff --git a/app/views/layouts/koding.html.haml b/app/views/layouts/koding.html.haml new file mode 100644 index 00000000000..22319bba745 --- /dev/null +++ b/app/views/layouts/koding.html.haml @@ -0,0 +1,5 @@ +- page_title "Koding" +- page_description "Koding Dashboard" +- header_title "Koding", koding_path + += render template: "layouts/application" diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 3a14751ea8e..67f558c854b 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -12,6 +12,11 @@ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do %span Activity + - if koding_enabled? + = nav_link(controller: :koding) do + = link_to koding_path, title: 'Koding' do + %span + Koding = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do = link_to dashboard_groups_path, title: 'Groups' do %span diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml new file mode 100644 index 00000000000..fdc80d44253 --- /dev/null +++ b/app/views/projects/buttons/_koding.html.haml @@ -0,0 +1,7 @@ +- if koding_enabled? && current_user && can_push_branch?(@project, @project.default_branch) + - if @repository.koding_yml + = link_to koding_project_url(@project), class: 'btn', target: '_blank' do + Run in IDE (Koding) + - else + = link_to add_koding_stack_path(@project), class: 'btn' do + Set Up Koding diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index f8025fc1dbe..9d8b4cc56be 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -16,6 +16,9 @@ - if @merge_request.open? .pull-right - if @merge_request.source_branch_exists? + - if koding_enabled? && @repository.koding_yml + = link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank' do + Run in IDE (Koding) = link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do Check out branch diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index a666d07e9eb..340e159c874 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -64,10 +64,12 @@ %li.missing = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do Set Up CI + %li.project-repo-buttons-right .project-repo-buttons.project-right-buttons - if current_user = render 'shared/members/access_request_buttons', source: @project + = render "projects/buttons/koding" .btn-group.project-repo-btn-group = render "projects/buttons/download" @@ -86,4 +88,4 @@ Archived project! Repository is read-only %div{class: "project-show-#{default_project_view}"} - = render default_project_view \ No newline at end of file + = render default_project_view diff --git a/config/routes.rb b/config/routes.rb index 66f77aee06a..e93b640fbc0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -90,6 +90,11 @@ Rails.application.routes.draw do get 'help/ui' => 'help#ui' get 'help/*path' => 'help#show', as: :help_page + # + # Koding route + # + get 'koding' => 'koding#index' + # # Global snippets # diff --git a/db/migrate/20160817133006_add_koding_to_application_settings.rb b/db/migrate/20160817133006_add_koding_to_application_settings.rb new file mode 100644 index 00000000000..915d3d78e40 --- /dev/null +++ b/db/migrate/20160817133006_add_koding_to_application_settings.rb @@ -0,0 +1,10 @@ +class AddKodingToApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :application_settings, :koding_enabled, :boolean + add_column :application_settings, :koding_url, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 748c4adc889..040573da848 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -90,6 +90,8 @@ ActiveRecord::Schema.define(version: 20160818205718) do t.string "enabled_git_access_protocol" t.boolean "domain_blacklist_enabled", default: false t.text "domain_blacklist" + t.boolean "koding_enabled" + t.string "koding_url" end create_table "audit_events", force: :cascade do |t| diff --git a/doc/integration/README.md b/doc/integration/README.md index ddbd570ac6c..70895abbcad 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -15,6 +15,7 @@ See the documentation below for details on how to configure these services. - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages - [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users - [Akismet](akismet.md) Configure Akismet to stop spam +- [Koding](koding.md) Configure Koding to use IDE integration GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. diff --git a/doc/integration/img/koding_build-in-progress.png b/doc/integration/img/koding_build-in-progress.png new file mode 100644 index 00000000000..f8cc81834c4 Binary files /dev/null and b/doc/integration/img/koding_build-in-progress.png differ diff --git a/doc/integration/img/koding_build-logs.png b/doc/integration/img/koding_build-logs.png new file mode 100644 index 00000000000..a04cd5aff99 Binary files /dev/null and b/doc/integration/img/koding_build-logs.png differ diff --git a/doc/integration/img/koding_build-success.png b/doc/integration/img/koding_build-success.png new file mode 100644 index 00000000000..2a0dd296480 Binary files /dev/null and b/doc/integration/img/koding_build-success.png differ diff --git a/doc/integration/img/koding_commit-koding.yml.png b/doc/integration/img/koding_commit-koding.yml.png new file mode 100644 index 00000000000..3e133c50327 Binary files /dev/null and b/doc/integration/img/koding_commit-koding.yml.png differ diff --git a/doc/integration/img/koding_different-stack-on-mr-try.png b/doc/integration/img/koding_different-stack-on-mr-try.png new file mode 100644 index 00000000000..fd25e32f648 Binary files /dev/null and b/doc/integration/img/koding_different-stack-on-mr-try.png differ diff --git a/doc/integration/img/koding_edit-on-ide.png b/doc/integration/img/koding_edit-on-ide.png new file mode 100644 index 00000000000..fd5aaff75f5 Binary files /dev/null and b/doc/integration/img/koding_edit-on-ide.png differ diff --git a/doc/integration/img/koding_enable-koding.png b/doc/integration/img/koding_enable-koding.png new file mode 100644 index 00000000000..c0ae0ee9918 Binary files /dev/null and b/doc/integration/img/koding_enable-koding.png differ diff --git a/doc/integration/img/koding_landing.png b/doc/integration/img/koding_landing.png new file mode 100644 index 00000000000..7c629d9b05e Binary files /dev/null and b/doc/integration/img/koding_landing.png differ diff --git a/doc/integration/img/koding_open-gitlab-from-koding.png b/doc/integration/img/koding_open-gitlab-from-koding.png new file mode 100644 index 00000000000..c958cf8f224 Binary files /dev/null and b/doc/integration/img/koding_open-gitlab-from-koding.png differ diff --git a/doc/integration/img/koding_run-in-ide.png b/doc/integration/img/koding_run-in-ide.png new file mode 100644 index 00000000000..f91ee0f74cc Binary files /dev/null and b/doc/integration/img/koding_run-in-ide.png differ diff --git a/doc/integration/img/koding_run-mr-in-ide.png b/doc/integration/img/koding_run-mr-in-ide.png new file mode 100644 index 00000000000..502817a2a46 Binary files /dev/null and b/doc/integration/img/koding_run-mr-in-ide.png differ diff --git a/doc/integration/img/koding_set-up-ide.png b/doc/integration/img/koding_set-up-ide.png new file mode 100644 index 00000000000..7f408c980b5 Binary files /dev/null and b/doc/integration/img/koding_set-up-ide.png differ diff --git a/doc/integration/img/koding_stack-import.png b/doc/integration/img/koding_stack-import.png new file mode 100644 index 00000000000..2a4e3c87fc8 Binary files /dev/null and b/doc/integration/img/koding_stack-import.png differ diff --git a/doc/integration/img/koding_start-build.png b/doc/integration/img/koding_start-build.png new file mode 100644 index 00000000000..52159440f62 Binary files /dev/null and b/doc/integration/img/koding_start-build.png differ diff --git a/doc/integration/koding-usage.md b/doc/integration/koding-usage.md new file mode 100644 index 00000000000..bb74badce66 --- /dev/null +++ b/doc/integration/koding-usage.md @@ -0,0 +1,122 @@ +# Koding & GitLab + +This document will guide you through using Koding integration on GitLab in +detail. For configuring and installing please follow [this](koding.md) guide. + +You can use Koding integration to run and develop your projects on GitLab. This +will allow you and the users to test your project without leaving the browser. +Koding handles projects as stacks which are basic recipes to define your +environment for your project. With this integration you can automatically +create a proper stack template for your projects. Currently auto-generated +stack templates are designed to work with AWS which requires a valid AWS +credential to be able to use these stacks. You can find more information about +stacks and the other providers that you can use on Koding from +[here](https://www.koding.com/docs). + + +# Enable Integration + +You can enable Koding integration by providing the running Koding instance URL +in Application Settings; + + - Open **Admin area > Settings** (`/admin/application_settings`). + +![Enable Koding](help/integration/img/koding_enable-koding.png) + +Once enabled you will see `Koding` link on your sidebar which leads you to +Koding Landing page + +![Koding Landing](help/integration/img/koding_landing.png) + +You can navigate to running Koding instance from here. For more information and +details about configuring integration please follow [this](koding.md) guide. + + +# Set up Koding on Projects + +Once it's enabled, you will see some integration buttons on Project pages, +Merge Requests etc. To get started working on a specific project you first need +to create a `.koding.yml` file under your project root. You can easily do that +by using `Set Up Koding` button which will be visible on every project's +landing page; + +![Set Up Koding](help/integration/img/koding_set-up-ide.png) + +Once you click this will open a New File page on GitLab with auto-generated +`.koding.yml` content based on your server and repository configuration. + +![Commit .koding.yml](help/integration/img/koding_commit-koding.yml.png) + + +# Run a project on Koding + +If there is `.koding.yml` exists in your project root, you will see +`Run in IDE (Koding)` button in your project landing page. You can initiate the +process from here. + +![Run on Koding](help/integration/img/koding_run-in-ide.png) + +This will open Koding defined in the settings in a new window and will start +importing the project's stack file; + +![Import Stack](help/integration/img/koding_stack-import.png) + +You should see the details of your repository imported into your Koding +instance. Once it's completed it will lead you to the Stack Editor and from +there you can start using your new stack integrated with your project on your +GitLab instance. For details about what's next you can follow +[this](https://www.koding.com/docs/creating-an-aws-stack) guide from 8. step. + +Once stack initialized you will see the `README.md` content from your project +in `Stack Build` wizard, this wizard will let you to build the stack and import +your project into it. **Once it's completed it will automatically open the +related vm instead of importing from scratch** + +![Stack Building](help/integration/img/koding_start-build.png) + +This will take time depending on the required environment. + +![Stack Building in Progress](help/integration/img/koding_build-in-progress.png) + +It usually takes ~4 min. to make it ready with a `t2.nano` instance on given +AWS region. (`t2.nano` is default vm type on auto-generated stack template +which can be manually changed) + +![Stack Building Success](help/integration/img/koding_build-success.png) + +You can check out the `Build Logs` from this success modal as well; + +![Stack Build Logs](help/integration/img/koding_build-logs.png) + +You can now `Start Coding`! + +![Edit On IDE](help/integration/img/koding_edit-on-ide.png) + + +# Try a Merge Request on IDE + +It's also possible to try a change on IDE before merging it. This flow only +enabled if the target project has `.koding.yml` in it's target branch. You +should see the alternative version of `Run in IDE (Koding)` button in merge +request pages as well; + +![Run in IDE on MR](help/integration/img/koding_run-mr-in-ide.png) + +This will again take you to Koding with proper arguments passed, which will +allow Koding to modify the stack template provided by target branch. You can +see the difference; + +![Different Branch for MR](help/integration/img/koding_different-stack-on-mr-try.png) + +The flow for the branch stack is also same with the regular project flow. + + +# Open GitLab from Koding + +Since stacks generated with import flow defined in previous steps, they have +information about the repository they are belonging to. By using this +information you can access to related GitLab page from stacks on your sidebar +on Koding. + +![Open GitLab from Koding](help/integration/img/koding_open-gitlab-from-koding.png) + diff --git a/doc/integration/koding.md b/doc/integration/koding.md new file mode 100644 index 00000000000..53450b6d048 --- /dev/null +++ b/doc/integration/koding.md @@ -0,0 +1,239 @@ +# Koding & GitLab + +This document will guide you through installing and configuring Koding with +GitLab. + +First of all, to be able to use Koding and GitLab together you will need public +access to your server. This allows you to use single sign-on from GitLab to +Koding and using vms from cloud providers like AWS. Koding has a registry for +VMs, called Kontrol and it runs on the same server as Koding itself, VMs from +cloud providers register themselves to Kontrol via the agent that we put into +provisioned VMs. This agent is called Klient and it provides Koding to access +and manage the target machine. + +Kontrol and Klient are based on another technology called +[Kite](github.com/koding/kite), that we have written at Koding. Which is a +microservice framework that allows you to develop microservices easily. + + +## Requirements + +### Hardware + +Minimum requirements are; + + - 2 cores CPU + - 3G RAM + - 10G Storage + +If you plan to use AWS to install Koding it is recommended that you use at +least a `c3.xlarge` instance. + +### Software + + - [git](https://git-scm.com) + - [docker](https://www.docker.com) + - [docker-compose](https://www.docker.com/products/docker-compose) + +Koding can run on most of the UNIX based operating systems, since it's shipped +as containerized with Docker support, it can work on any operating system that +supports Docker. + +Required services are; + + - PostgreSQL # Kontrol and Service DB provider + - MongoDB # Main DB provider the application + - Redis # In memory DB used by both application and services + - RabbitMQ # Message Queue for both application and services + +which are also provided as a Docker container by Koding. + + +## Getting Started with Development Versions + + +### Koding + +You can run `docker-compose` environment for developing koding by +executing commands in the following snippet. + +```bash +git clone https://github.com/koding/koding.git +cd koding +docker-compose up +``` + +This should start koding on `localhost:8090`. + +By default there is no team exists in Koding DB. You'll need to create a team +called `gitlab` which is the default team name for GitLab integration in the +configuration. To make things in order it's recommended to create the `gitlab` +team first thing after setting up Koding. + + +### GitLab + +To install GitLab to your environment for development purposes it's recommended +to use GitLab Development Kit which you can get it from +[here](https://gitlab.com/gitlab-org/gitlab-development-kit). + +After all those steps, gitlab should be running on `localhost:3000` + + +## Integration + +Integration includes following components; + + - Single Sign On with OAuth from GitLab to Koding + - System Hook integration for handling GitLab events on Koding + (`project_created`, `user_joined` etc.) + - Service endpoints for importing/executing stacks from GitLab to Koding + (`Run/Try on IDE (Koding)` buttons on GitLab Projects, Issues, MRs) + +As it's pointed out before, you will need public access to this machine that +you've installed Koding and GitLab on. Better to use a domain but a static IP +is also fine. + +For IP based installation you can use [xip.io](https://xip.io) service which is +free and provides DNS resolution to IP based requests like following; + + - 127.0.0.1.xip.io -> resolves to 127.0.0.1 + - foo.bar.baz.127.0.0.1.xip.io -> resolves to 127.0.0.1 + - and so on... + +As Koding needs subdomains for team names; `foo.127.0.0.1.xip.io` requests for +a running koding instance on `127.0.0.1` server will be handled as `foo` team +requests. + + +### GitLab Side + +You need to enable Koding integration from Settings under Admin Area. To do +that login with an Admin account and do followings; + + - open [http://127.0.0.1:3000/admin/application_settings](http://127.0.0.1:3000/admin/application_settings) + - scroll to bottom of the page until Koding section + - check `Enable Koding` checkbox + - provide GitLab team page for running Koding instance as `Koding URL`* + +* For `Koding URL` you need to provide the gitlab integration enabled team on +your Koding installation. Team called `gitlab` has integration on Koding out +of the box, so if you didn't change anything your team on Koding should be +`gitlab`. + +So, if your Koding is running on `http://1.2.3.4.xip.io:8090` your URL needs +to be `http://gitlab.1.2.3.4.xip.io:8090`. You need to provide the same host +with your Koding installation here. + + +#### Registering Koding for OAuth integration + +We need `Application ID` and `Secret` to enable login to Koding via GitLab +feature and to do that you need to register running Koding as a new application +to your running GitLab application. Follow +[these](http://docs.gitlab.com/ce/integration/oauth_provider.html) steps to +enable this integration. + +Redirect URI should be `http://gitlab.127.0.0.1:8090/-/oauth/gitlab/callback` +which again you need to _replace `127.0.0.1` with your instance public IP._ + +Take a copy of `Application ID` and `Secret` that is generated by the GitLab +application, we will need those on _Koding Part_ of this guide. + + +#### Registering system hooks to Koding (optional) + +Koding can take actions based on the events generated by GitLab application. +This feature is still in progress and only following events are processed by +Koding at the moment; + + - user_create + - user_destroy + +All system events are handled but not implemented on Koding side. + +To enable this feature you need to provide a `URL` and a `Secret Token` to your +GitLab application. Open your admin area on your GitLab app from +[http://127.0.0.1:3000/admin/hooks](http://127.0.0.1:3000/admin/hooks) +and provide `URL` as `http://gitlab.127.0.0.1:8090/-/api/gitlab` which is the +endpoint to handle GitLab events on Koding side. Provide a `Secret Token` and +keep a copy of it, we will need it on _Koding Part_ of this guide. + +_(replace `127.0.0.1` with your instance public IP)_ + + +### Koding Part + +If you followed the steps in GitLab part we should have followings to enable +Koding part integrations; + + - `Application ID` and `Secret` for OAuth integration + - `Secret Token` for system hook integration + - Public address of running GitLab instance + + +#### Start Koding with GitLab URL + +Now we need to configure Koding with all this information to get things ready. +If it's already running please stop koding first. + +##### From command-line + +Replace followings with the ones you got from GitLab part of this guide; + +```bash +cd koding +docker-compose run \ + --service-ports backend \ + /opt/koding/scripts/bootstrap-container build \ + --host=**YOUR_IP**.xip.io \ + --gitlabHost=**GITLAB_IP** \ + --gitlabPort=**GITLAB_PORT** \ + --gitlabToken=**SECRET_TOKEN** \ + --gitlabAppId=**APPLICATION_ID** \ + --gitlabAppSecret=**SECRET** +``` + +##### By updating configuration + +Alternatively you can update `gitlab` section on +`config/credentials.default.coffee` like following; + +``` +gitlab = + host: '**GITLAB_IP**' + port: '**GITLAB_PORT**' + applicationId: '**APPLICATION_ID**' + applicationSecret: '**SECRET**' + team: 'gitlab' + redirectUri: '' + systemHookToken: '**SECRET_TOKEN**' + hooksEnabled: yes +``` + +and start by only providing the `host`; + +```bash +cd koding +docker-compose run \ + --service-ports backend \ + /opt/koding/scripts/bootstrap-container build \ + --host=**YOUR_IP**.xip.io \ +``` + +#### Enable Single Sign On + +Once you restarted your Koding and logged in with your username and password +you need to activate oauth authentication for your user. To do that + + - Navigate to Dashboard on Koding from; + `http://gitlab.**YOUR_IP**.xip.io:8090/Home/my-account` + - Scroll down to Integrations section + - Click on toggle to turn On integration in GitLab integration section + +This will redirect you to your GitLab instance and will ask your permission ( +if you are not logged in to GitLab at this point you will be redirected after +login) once you accept you will be redirected to your Koding instance. + +From now on you can login by using `SIGN IN WITH GITLAB` button on your Login +screen in your Koding instance. diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 735331df66c..27acd817e51 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -30,6 +30,7 @@ module Gitlab signup_enabled: Settings.gitlab['signup_enabled'], signin_enabled: Settings.gitlab['signin_enabled'], gravatar_enabled: Settings.gravatar['enabled'], + koding_enabled: false, sign_in_text: nil, after_sign_up_text: nil, help_page_text: nil, diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb index 788581a26cb..40f773956d1 100644 --- a/spec/features/security/dashboard_access_spec.rb +++ b/spec/features/security/dashboard_access_spec.rb @@ -43,6 +43,20 @@ describe "Dashboard access", feature: true do it { is_expected.to be_allowed_for :visitor } end + describe "GET /koding" do + subject { koding_path } + + context 'with Koding enabled' do + before do + stub_application_setting(koding_enabled?: true) + end + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end + end + describe "GET /projects/new" do it { expect(new_project_path).to be_allowed_for :admin } it { expect(new_project_path).to be_allowed_for :user } diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 1d4df9197f6..d65648dd0b2 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -116,12 +116,19 @@ describe HelpController, "routing" do expect(get(path)).to route_to('help#show', path: 'workflow/protected_branches/protected_branches1', format: 'png') - + path = '/help/ui' expect(get(path)).to route_to('help#ui') end end +# koding GET /koding(.:format) koding#index +describe KodingController, "routing" do + it "to #index" do + expect(get("/koding")).to route_to('koding#index') + end +end + # profile_account GET /profile/account(.:format) profile#account # profile_history GET /profile/history(.:format) profile#history # profile_password PUT /profile/password(.:format) profile#password_update -- cgit v1.2.1 From 37bf35f0bcba28e271789542fb8c81a6c77236b6 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 26 Jul 2016 18:21:20 -0300 Subject: Todos sorting dropdown --- CHANGELOG | 1 + app/controllers/dashboard/todos_controller.rb | 1 + app/finders/todos_finder.rb | 6 ++- app/models/concerns/issuable.rb | 19 ++------ app/models/concerns/sortable.rb | 14 ++++++ app/models/todo.rb | 19 ++++++++ app/views/dashboard/todos/index.html.haml | 19 ++++++++ spec/features/todos/todos_sorting_spec.rb | 67 +++++++++++++++++++++++++ spec/finders/todos_finder_spec.rb | 70 +++++++++++++++++++++++++++ 9 files changed, 200 insertions(+), 16 deletions(-) create mode 100644 spec/features/todos/todos_sorting_spec.rb create mode 100644 spec/finders/todos_finder_spec.rb diff --git a/CHANGELOG b/CHANGELOG index a92c85873c4..df2301ed326 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.11.0 (unreleased) - Show member roles to all users on members page - Project.visible_to_user is instrumented again - Fix awardable button mutuality loading spinners (ClemMakesApps) + - Sort todos by date and priority - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Optimize maximum user access level lookup in loading of notes - Add "No one can push" as an option for protected branches. !5081 diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 1243bb96d4d..9828d39869b 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -2,6 +2,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController before_action :find_todos, only: [:index, :destroy_all] def index + @sort = params[:sort] @todos = @todos.page(params[:page]) end diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 4fe0070552e..37e919e6cf7 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -33,7 +33,7 @@ class TodosFinder # the project IDs yielded by the todos query thus far items = by_project(items) - items.reorder(id: :desc) + sort(items) end private @@ -106,6 +106,10 @@ class TodosFinder params[:type] end + def sort(items) + params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc) + end + def by_action(items) if action? items = items.where(action: to_action_id) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index cbae1cd439b..afb5ce37c06 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -131,7 +131,10 @@ module Issuable end def order_labels_priority(excluded_labels: []) - select("#{table_name}.*, (#{highest_label_priority(excluded_labels).to_sql}) AS highest_priority"). + condition_field = "#{table_name}.id" + highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql + + select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). group(arel_table[:id]). reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')) end @@ -159,20 +162,6 @@ module Issuable grouping_columns end - - private - - def highest_label_priority(excluded_labels) - query = Label.select(Label.arel_table[:priority].minimum). - joins(:label_links). - where(label_links: { target_type: name }). - where("label_links.target_id = #{table_name}.id"). - reorder(nil) - - query.where.not(title: excluded_labels) if excluded_labels.present? - - query - end end def today? diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 8b47b9e0abd..1ebecd86af9 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -35,5 +35,19 @@ module Sortable all end end + + private + + def highest_label_priority(object_types, condition_field, excluded_labels: []) + query = Label.select(Label.arel_table[:priority].minimum). + joins(:label_links). + where(label_links: { target_type: object_types }). + where("label_links.target_id = #{condition_field}"). + reorder(nil) + + query.where.not(title: excluded_labels) if excluded_labels.present? + + query + end end end diff --git a/app/models/todo.rb b/app/models/todo.rb index 8d7a5965aa1..6ae9956ade5 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -1,4 +1,6 @@ class Todo < ActiveRecord::Base + include Sortable + ASSIGNED = 1 MENTIONED = 2 BUILD_FAILED = 3 @@ -41,6 +43,23 @@ class Todo < ActiveRecord::Base after_save :keep_around_commit + class << self + def sort(method) + method == "priority" ? order_by_labels_priority : order_by(method) + end + + # Order by priority depending on which issue/merge request the Todo belongs to + # Todos with highest priority first then oldest todos + # Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue" + def order_by_labels_priority + highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.target_id").to_sql + + select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). + order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')). + order('todos.created_at') + end + end + def build_failed? action == BUILD_FAILED end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 4e340b6ec16..d320d3bcc1e 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -43,6 +43,25 @@ class: 'select2 trigger-submit', include_blank: true, data: {placeholder: 'Action'}) + .pull-right + .dropdown.inline.prepend-left-10 + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort + %li + = link_to todos_filter_path(sort: sort_value_priority) do + = sort_title_priority + = link_to todos_filter_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to todos_filter_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + + .prepend-top-default - if @todos.any? .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } diff --git a/spec/features/todos/todos_sorting_spec.rb b/spec/features/todos/todos_sorting_spec.rb new file mode 100644 index 00000000000..e74a51acede --- /dev/null +++ b/spec/features/todos/todos_sorting_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe "Dashboard > User sorts todos", feature: true do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + + let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) } + let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) } + let(:label_3) { create(:label, title: 'label_3', project: project, priority: 3) } + + let(:issue_1) { create(:issue, title: 'issue_1', project: project) } + let(:issue_2) { create(:issue, title: 'issue_2', project: project) } + let(:issue_3) { create(:issue, title: 'issue_3', project: project) } + let(:issue_4) { create(:issue, title: 'issue_4', project: project) } + + let!(:merge_request_1) { create(:merge_request, source_project: project, title: "merge_request_1") } + + before do + create(:todo, user: user, project: project, target: issue_4, created_at: 5.hours.ago) + create(:todo, user: user, project: project, target: issue_2, created_at: 4.hours.ago) + create(:todo, user: user, project: project, target: issue_3, created_at: 3.hours.ago) + create(:todo, user: user, project: project, target: issue_1, created_at: 2.hours.ago) + create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago) + + merge_request_1.labels << label_1 + issue_3.labels << label_1 + issue_2.labels << label_3 + issue_1.labels << label_2 + + project.team << [user, :developer] + login_as(user) + visit dashboard_todos_path + end + + it "sorts with oldest created todos first" do + click_link "Last created" + + results_list = page.find('.todos-list') + expect(results_list.all('p')[0]).to have_content("merge_request_1") + expect(results_list.all('p')[1]).to have_content("issue_1") + expect(results_list.all('p')[2]).to have_content("issue_3") + expect(results_list.all('p')[3]).to have_content("issue_2") + expect(results_list.all('p')[4]).to have_content("issue_4") + end + + it "sorts with newest created todos first" do + click_link "Oldest created" + + results_list = page.find('.todos-list') + expect(results_list.all('p')[0]).to have_content("issue_4") + expect(results_list.all('p')[1]).to have_content("issue_2") + expect(results_list.all('p')[2]).to have_content("issue_3") + expect(results_list.all('p')[3]).to have_content("issue_1") + expect(results_list.all('p')[4]).to have_content("merge_request_1") + end + + it "sorts by priority" do + click_link "Priority" + + results_list = page.find('.todos-list') + expect(results_list.all('p')[0]).to have_content("issue_3") + expect(results_list.all('p')[1]).to have_content("merge_request_1") + expect(results_list.all('p')[2]).to have_content("issue_1") + expect(results_list.all('p')[3]).to have_content("issue_2") + expect(results_list.all('p')[4]).to have_content("issue_4") + end +end diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb new file mode 100644 index 00000000000..f7e7e733cf7 --- /dev/null +++ b/spec/finders/todos_finder_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +describe TodosFinder do + describe '#execute' do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:finder) { described_class } + + before { project.team << [user, :developer] } + + describe '#sort' do + context 'by date' do + let!(:todo1) { create(:todo, user: user, project: project) } + let!(:todo2) { create(:todo, user: user, project: project) } + let!(:todo3) { create(:todo, user: user, project: project) } + + it 'sorts with oldest created first' do + todos = finder.new(user, { sort: 'id_asc' }).execute + + expect(todos.first).to eq(todo1) + expect(todos.second).to eq(todo2) + expect(todos.third).to eq(todo3) + end + + it 'sorts with newest created first' do + todos = finder.new(user, { sort: 'id_desc' }).execute + + expect(todos.first).to eq(todo3) + expect(todos.second).to eq(todo2) + expect(todos.third).to eq(todo1) + end + end + + it "sorts by priority" do + label_1 = create(:label, title: 'label_1', project: project, priority: 1) + label_2 = create(:label, title: 'label_2', project: project, priority: 2) + label_3 = create(:label, title: 'label_3', project: project, priority: 3) + + issue_1 = create(:issue, title: 'issue_1', project: project) + issue_2 = create(:issue, title: 'issue_2', project: project) + issue_3 = create(:issue, title: 'issue_3', project: project) + issue_4 = create(:issue, title: 'issue_4', project: project) + merge_request_1 = create(:merge_request, source_project: project) + + merge_request_1.labels << label_1 + + # Covers the case where Todo has more than one label + issue_3.labels << label_1 + issue_3.labels << label_3 + + issue_2.labels << label_3 + issue_1.labels << label_2 + + todo_1 = create(:todo, user: user, project: project, target: issue_4) + todo_2 = create(:todo, user: user, project: project, target: issue_2) + todo_3 = create(:todo, user: user, project: project, target: issue_3, created_at: 2.hours.ago) + todo_4 = create(:todo, user: user, project: project, target: issue_1) + todo_5 = create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago) + + todos = finder.new(user, { sort: 'priority' }).execute + + expect(todos.first).to eq(todo_3) + expect(todos.second).to eq(todo_5) + expect(todos.third).to eq(todo_4) + expect(todos.fourth).to eq(todo_2) + expect(todos.fifth).to eq(todo_1) + end + end + end +end -- cgit v1.2.1 From d6e027d9ca4aef4f2aaca8b3dc75e337d83c85dd Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 19 Aug 2016 16:46:51 -0500 Subject: Update CHANGELOG for 8.10.7, 8.9.8, and 8.8.9 [ci skip] --- CHANGELOG | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 84cce36a910..6d691fd2652 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -164,6 +164,10 @@ v 8.11.0 (unreleased) - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.7 + - Upgrade Hamlit to 2.6.1. !5873 + - Upgrade Doorkeeper to 4.2.0. !5881 + v 8.10.6 - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Restore "Largest repository" sort option on Admin > Projects page. !5797 @@ -387,6 +391,9 @@ v 8.10.0 - Fix migration corrupting import data for old version upgrades - Show tooltip on GitLab export link in new project page +v 8.9.8 + - Upgrade Doorkeeper to 4.2.0. !5881 + v 8.9.7 - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Require administrator privileges to perform a project import. @@ -656,6 +663,9 @@ v 8.9.0 - Add tooltip to pin/unpin navbar - Add new sub nav style to Wiki and Graphs sub navigation +v 8.8.9 + - Upgrade Doorkeeper to 4.2.0. !5881 + v 8.8.8 - Upgrade Rails to 4.2.7.1 for security fixes. !5781 -- cgit v1.2.1 From d741303a5992b0d259a125baa62b66ca4df209da Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 19 Aug 2016 17:22:59 -0500 Subject: Call `set_discussion_id` again in DiffNote `before_validation` because the order is important --- app/models/diff_note.rb | 3 +++ .../20160819221631_add_index_to_note_discussion_id.rb | 14 ++++++++++++++ ...note_discussion_id_because_it_was_calculated_wrongly.rb | 12 ++++++++++++ db/schema.rb | 3 ++- 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160819221631_add_index_to_note_discussion_id.rb create mode 100644 db/migrate/20160819221833_reset_diff_note_discussion_id_because_it_was_calculated_wrongly.rb diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index aa54189fea9..c8320ff87fa 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -16,6 +16,9 @@ class DiffNote < Note after_initialize :ensure_original_discussion_id before_validation :set_original_position, :update_position, on: :create before_validation :set_line_code, :set_original_discussion_id + # We need to do this again, because it's already in `Note`, but is affected by + # `update_position` and needs to run after that. + before_validation :set_discussion_id after_save :keep_around_commits class << self diff --git a/db/migrate/20160819221631_add_index_to_note_discussion_id.rb b/db/migrate/20160819221631_add_index_to_note_discussion_id.rb new file mode 100644 index 00000000000..b6e8bb18e7b --- /dev/null +++ b/db/migrate/20160819221631_add_index_to_note_discussion_id.rb @@ -0,0 +1,14 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddIndexToNoteDiscussionId < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_concurrent_index :notes, :discussion_id + end +end diff --git a/db/migrate/20160819221833_reset_diff_note_discussion_id_because_it_was_calculated_wrongly.rb b/db/migrate/20160819221833_reset_diff_note_discussion_id_because_it_was_calculated_wrongly.rb new file mode 100644 index 00000000000..0c68cf01900 --- /dev/null +++ b/db/migrate/20160819221833_reset_diff_note_discussion_id_because_it_was_calculated_wrongly.rb @@ -0,0 +1,12 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ResetDiffNoteDiscussionIdBecauseItWasCalculatedWrongly < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + execute "UPDATE notes SET discussion_id = NULL WHERE discussion_id IS NOT NULL AND type = 'DiffNote'" + end +end diff --git a/db/schema.rb b/db/schema.rb index 748c4adc889..a94d0cc97af 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160818205718) do +ActiveRecord::Schema.define(version: 20160819221833) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -700,6 +700,7 @@ ActiveRecord::Schema.define(version: 20160818205718) do add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree + add_index "notes", ["discussion_id"], name: "index_notes_on_discussion_id", using: :btree add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"} add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree -- cgit v1.2.1 From cf8efb462ea2aa909e9cb7e1118c160db1486949 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 19 Aug 2016 18:52:21 -0500 Subject: Use typeof instead of Underscore method --- app/assets/javascripts/gl_dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index b7f27085b4f..3f5c8359c22 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -470,7 +470,7 @@ } else { if (!selected) { value = this.options.id ? this.options.id(data) : data.id; - fieldName = _.isFunction(this.options.fieldName) ? this.options.fieldName() : this.options.fieldName; + fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName; field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); if (field.length) { @@ -545,7 +545,7 @@ selectedObject = this.renderedData[selectedIndex]; } } - fieldName = _.isFunction(this.options.fieldName) ? this.options.fieldName(selectedObject) : this.options.fieldName; + fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); -- cgit v1.2.1 From 368569f51e1f2c8e406d392339e77f4b3ed08a7f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 19 Aug 2016 19:42:10 -0500 Subject: Add element container to dropdowns --- .../protected_branches/_create_protected_branch.html.haml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index d4c6fa24768..95d8743f546 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -22,16 +22,18 @@ %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } Allowed to merge: .col-md-10 - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-merge wide', - data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) + .js-allowed-to-merge-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-merge wide', + data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) .form-group %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } Allowed to push: .col-md-10 - = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-push wide', - data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) + .js-allowed-to-push-container + = dropdown_tag('Select', + options: { toggle_class: 'js-allowed-to-push wide', + data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) .panel-footer = f.submit 'Protect', class: 'btn-create btn', disabled: true -- cgit v1.2.1 From 97dddf39cd98d49f02b9adae3b7aeb3b28c434a6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 19 Aug 2016 19:07:02 -0500 Subject: =?UTF-8?q?Disable=20=E2=80=9Cissue=20by=20email=E2=80=9D=20featur?= =?UTF-8?q?e=20until=20it=20uses=20a=20different=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 1 - app/models/project.rb | 5 ++++- lib/gitlab/email/handler.rb | 3 ++- spec/features/issues_spec.rb | 2 +- spec/lib/gitlab/email/handler/create_issue_handler_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f8c82e1ba97..dbe52982711 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -88,7 +88,6 @@ v 8.11.0 (unreleased) - Add archived badge to project list !5798 - Add simple identifier to public SSH keys (muteor) - Admin page now references docs instead of a specific file !5600 (AnAverageHuman) - - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - Fix filter input alignment (ClemMakesApps) - Include old revision in merge request update hooks (Ben Boeckel) - Add build event color in HipChat messages (David Eisner) diff --git a/app/models/project.rb b/app/models/project.rb index f9c48a546e6..1855760e694 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -611,7 +611,10 @@ class Project < ActiveRecord::Base end def new_issue_address(author) - if Gitlab::IncomingEmail.enabled? && author + # This feature is disabled for the time being. + return nil + + if Gitlab::IncomingEmail.enabled? && author # rubocop:disable Lint/UnreachableCode Gitlab::IncomingEmail.reply_address( "#{path_with_namespace}+#{author.authentication_token}") end diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb index bd3267e2a80..5cf9d5ebe28 100644 --- a/lib/gitlab/email/handler.rb +++ b/lib/gitlab/email/handler.rb @@ -4,7 +4,8 @@ require 'gitlab/email/handler/create_issue_handler' module Gitlab module Email module Handler - HANDLERS = [CreateNoteHandler, CreateIssueHandler] + # The `CreateIssueHandler` feature is disabled for the time being. + HANDLERS = [CreateNoteHandler] def self.for(mail, mail_key) HANDLERS.find do |klass| diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index cb445e22af0..2e595959f04 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -525,7 +525,7 @@ describe 'Issues', feature: true do end end - describe 'new issue by email' do + xdescribe 'new issue by email' do shared_examples 'show the email in the modal' do before do stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index e1153154778..a5cc7b02936 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require_relative '../email_shared_blocks' -describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do +xdescribe Gitlab::Email::Handler::CreateIssueHandler, lib: true do include_context :email_shared_context it_behaves_like :email_shared_examples diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d1f3a815290..9a3660012f9 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -247,7 +247,7 @@ describe Project, models: true do end end - describe "#new_issue_address" do + xdescribe "#new_issue_address" do let(:project) { create(:empty_project, path: "somewhere") } let(:user) { create(:user) } -- cgit v1.2.1 From 65c25993b08c465e92d0ea24c28d59f06239d63d Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Sat, 20 Aug 2016 10:57:10 +0200 Subject: Update gitlab-shell to v3.4.0 --- GITLAB_SHELL_VERSION | 2 +- doc/update/8.10-to-8.11.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 619b5376684..18091983f59 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.3.3 +3.4.0 diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index 84c624cbcb7..253fff50bdd 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-11-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.3.3 +sudo -u git -H git checkout v3.4.0 ``` ### 5. Update gitlab-workhorse -- cgit v1.2.1 From 41d89533e61f6009b5d800afc00c184e2807eafd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 19 Aug 2016 22:22:52 -0700 Subject: Fix assorted rspec failures due to stale, cached user permissions RequestStore is disabled in tests, but the Ability class was caching user permissions based on the user and project ID of previous test runs. Revise code to use RequestStore only if it is active. --- app/models/ability.rb | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 07f703f205d..b82632ccc0b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -166,38 +166,44 @@ class Ability end def project_abilities(user, project) - rules = [] key = "/user/#{user.id}/project/#{project.id}" - RequestStore.store[key] ||= begin - # Push abilities on the users team role - rules.push(*project_team_rules(project.team, user)) + if RequestStore.active? + RequestStore.store[key] ||= uncached_project_abilities(user, project) + else + uncached_project_abilities(user, project) + end + end - owner = user.admin? || - project.owner == user || - (project.group && project.group.has_owner?(user)) + def uncached_project_abilities(user, project) + rules = [] + # Push abilities on the users team role + rules.push(*project_team_rules(project.team, user)) - if owner - rules.push(*project_owner_rules) - end + owner = user.admin? || + project.owner == user || + (project.group && project.group.has_owner?(user)) - if project.public? || (project.internal? && !user.external?) - rules.push(*public_project_rules) + if owner + rules.push(*project_owner_rules) + end - # Allow to read builds for internal projects - rules << :read_build if project.public_builds? + if project.public? || (project.internal? && !user.external?) + rules.push(*public_project_rules) - unless owner || project.team.member?(user) || project_group_member?(project, user) - rules << :request_access if project.request_access_enabled - end - end + # Allow to read builds for internal projects + rules << :read_build if project.public_builds? - if project.archived? - rules -= project_archived_rules + unless owner || project.team.member?(user) || project_group_member?(project, user) + rules << :request_access if project.request_access_enabled end + end - rules - project_disabled_features_rules(project) + if project.archived? + rules -= project_archived_rules end + + rules - project_disabled_features_rules(project) end def project_team_rules(team, user) -- cgit v1.2.1 From be95e03a5633696d79260ae61bb9e8f4c6755855 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 19 Aug 2016 23:19:42 -0700 Subject: Add spec for Ability.project_abilities --- spec/models/ability_spec.rb | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 853f6943cef..fe32d367176 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -171,6 +171,69 @@ describe Ability, lib: true do end end + shared_examples_for ".project_abilities" do |enable_request_store| + before do + RequestStore.begin! if enable_request_store + end + + after do + if enable_request_store + RequestStore.end! + RequestStore.clear! + end + end + + describe '.project_abilities' do + let!(:project) { create(:empty_project, :public) } + let!(:user) { create(:user) } + + it 'returns permissions for admin user' do + admin = create(:admin) + + results = described_class.project_abilities(admin, project) + expect(results.count).to eq(90) + end + + it 'returns permissions for an owner' do + results = described_class.project_abilities(project.owner, project) + + expect(results.count).to eq(90) + end + + it 'returns permissions for a guest' do + project.team << [user, :guest] + + results = described_class.project_abilities(user, project) + + expect(results.count).to eq(22) + end + + it 'returns permissions for a developer' do + project.team << [user, :developer] + + results = described_class.project_abilities(user, project) + + expect(results.count).to eq(22) + end + + it 'returns permissions for a master' do + project.team << [user, :master] + + results = described_class.project_abilities(user, project) + + expect(results.count).to eq(22) + end + end + end + + describe '.project_abilities with RequestStore' do + it_behaves_like ".project_abilities", true + end + + describe '.project_abilities without RequestStore' do + it_behaves_like ".project_abilities", false + end + describe '.issues_readable_by_user' do context 'with an admin user' do it 'returns all given issues' do -- cgit v1.2.1 From 1954bb17eca49d375c92a4b8fa7f52fa39873a7d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 20 Aug 2016 06:33:53 -0700 Subject: Make Ability#project_abilities return unique values and fix counts --- app/models/ability.rb | 2 +- spec/models/ability_spec.rb | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index b82632ccc0b..a49dd703926 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -203,7 +203,7 @@ class Ability rules -= project_archived_rules end - rules - project_disabled_features_rules(project) + (rules - project_disabled_features_rules(project)).uniq end def project_team_rules(team, user) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index fe32d367176..aa3b2bbf471 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -191,21 +191,22 @@ describe Ability, lib: true do admin = create(:admin) results = described_class.project_abilities(admin, project) - expect(results.count).to eq(90) + + expect(results.count).to eq(68) end it 'returns permissions for an owner' do results = described_class.project_abilities(project.owner, project) - expect(results.count).to eq(90) + expect(results.count).to eq(68) end - it 'returns permissions for a guest' do - project.team << [user, :guest] + it 'returns permissions for a master' do + project.team << [user, :master] results = described_class.project_abilities(user, project) - expect(results.count).to eq(22) + expect(results.count).to eq(60) end it 'returns permissions for a developer' do @@ -213,15 +214,15 @@ describe Ability, lib: true do results = described_class.project_abilities(user, project) - expect(results.count).to eq(22) + expect(results.count).to eq(44) end - it 'returns permissions for a master' do - project.team << [user, :master] + it 'returns permissions for a guest' do + project.team << [user, :guest] results = described_class.project_abilities(user, project) - expect(results.count).to eq(22) + expect(results.count).to eq(21) end end end -- cgit v1.2.1 From 561093e708ae79793c095820d1b595567a5bb145 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 19 Aug 2016 17:05:36 -0500 Subject: Fix project namespace links --- .../projects/ci/pipelines/_pipeline.html.haml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 9a672b23341..b119f6edf14 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -1,23 +1,23 @@ - status = pipeline.status %tr.commit %td.commit-link - = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do - if defined?(status_icon_only) && status_icon_only = ci_icon_for_status(status) - else = ci_status_with_icon(status) %td .branch-commit - = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do %span ##{pipeline.id} - if pipeline.ref - unless defined?(hide_branch) && hide_branch .icon-container = pipeline.tag? ? icon('tag') : icon('code-fork') - = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name" + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" .icon-container = custom_icon("icon_commit") - = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace" + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" - if pipeline.latest? %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest - if pipeline.triggered? @@ -30,7 +30,7 @@ %p.commit-title - if commit = pipeline.commit = author_avatar(commit, size: 20) - = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message" + = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message" - else Cant find HEAD commit for this branch @@ -41,7 +41,7 @@ - status = stages_status[stage] - tooltip = "#{stage.titleize}: #{status || 'not found'}" - if status - = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do = ci_icon_for_status(status) - else .light.has-tooltip{ title: tooltip } @@ -71,7 +71,7 @@ %ul.dropdown-menu.dropdown-menu-align-right - actions.each do |build| %li - = link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do + = link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do = icon("play") %span= build.name.humanize - if artifacts.present? @@ -82,15 +82,15 @@ %ul.dropdown-menu.dropdown-menu-align-right - artifacts.each do |build| %li - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do + = link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow' do = icon("download") %span Download '#{build.name}' artifacts - - if can?(current_user, :update_pipeline, @project) + - if can?(current_user, :update_pipeline, pipeline.project) .cancel-retry-btns.inline - if pipeline.retryable? - = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do + = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do = icon("repeat") - if pipeline.cancelable? - = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do + = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do = icon("remove") -- cgit v1.2.1 From 5cb488e8a1a10432c1c5a322b2d4748cb754277f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 20 Aug 2016 09:18:06 -0700 Subject: Fix Error 500 resulting when loading network graph `discussion_id` may not be present when the SELECT call for notes does not include this attribute. Don't attempt to set the discussion ID unless the model contains the attribute. Closes #21119, #21128 --- app/models/note.rb | 2 ++ spec/models/network/graph_spec.rb | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 spec/models/network/graph_spec.rb diff --git a/app/models/note.rb b/app/models/note.rb index 3bbf5db0b70..f2656df028b 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -259,6 +259,8 @@ class Note < ActiveRecord::Base def ensure_discussion_id return unless self.persisted? + # Needed in case the SELECT statement doesn't ask for `discussion_id` + return unless self.has_attribute?(:discussion_id) return if self.discussion_id set_discussion_id diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb new file mode 100644 index 00000000000..b76513d2a3c --- /dev/null +++ b/spec/models/network/graph_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Network::Graph, models: true do + let(:project) { create(:project) } + let!(:note_on_commit) { create(:note_on_commit, project: project) } + + it '#initialize' do + graph = described_class.new(project, 'refs/heads/master', project.repository.commit, nil) + + expect(graph.notes).to eq( { note_on_commit.commit_id => 1 } ) + end +end -- cgit v1.2.1 From 06ac1c26a14be73f409138f2877c236f352bcdf9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 20 Aug 2016 20:42:53 +0200 Subject: Add tests coverage report badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fee93d5f9c3..f00dec0ab82 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # GitLab [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) ## Canonical source -- cgit v1.2.1 From acba6042835707d8cbc4d4074ea1ebc3dae690bd Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sat, 20 Aug 2016 23:02:43 -0500 Subject: Fix inputs not being found due to incorrect class in wrap element --- .../projects/protected_branches/_create_protected_branch.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index 95d8743f546..16ac75c3d37 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -22,7 +22,7 @@ %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } Allowed to merge: .col-md-10 - .js-allowed-to-merge-container + .merge_access_levels-container = dropdown_tag('Select', options: { toggle_class: 'js-allowed-to-merge wide', data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) @@ -30,7 +30,7 @@ %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } Allowed to push: .col-md-10 - .js-allowed-to-push-container + .push_access_levels-container = dropdown_tag('Select', options: { toggle_class: 'js-allowed-to-push wide', data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) -- cgit v1.2.1 From 54412dac15203b9208ca3ab56617262e7968050b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 21 Aug 2016 00:18:28 -0500 Subject: Make dropdown selectables This makes dropdown to look according the dropdowns on the edit form. This options add a tick mark next to the selected option. --- .../projects/protected_branches/_create_protected_branch.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index 95d8743f546..226f8311a90 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -25,6 +25,7 @@ .js-allowed-to-merge-container = dropdown_tag('Select', options: { toggle_class: 'js-allowed-to-merge wide', + dropdown_class: 'dropdown-menu-selectable', data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) .form-group %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } @@ -33,6 +34,7 @@ .js-allowed-to-push-container = dropdown_tag('Select', options: { toggle_class: 'js-allowed-to-push wide', + dropdown_class: 'dropdown-menu-selectable', data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) .panel-footer -- cgit v1.2.1 From 18e98a364388e53786c7fe01eaff75efa496bb4c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 21 Aug 2016 00:19:22 -0500 Subject: Do not update if one dropdown has not selected any option No need to update the branch permission if no option is marked on the dropdown --- app/assets/javascripts/protected_branch_edit.js.es6 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branch_edit.js.es6 index a59fcbfa082..40bc4adb71b 100644 --- a/app/assets/javascripts/protected_branch_edit.js.es6 +++ b/app/assets/javascripts/protected_branch_edit.js.es6 @@ -31,6 +31,9 @@ const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`); const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`); + // Do not update if one dropdown has not selected any option + if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return; + $.ajax({ type: 'POST', url: this.$wrap.data('url'), -- cgit v1.2.1 From 4a4d8608e8a09e0c55f690aeefc05f742bc67011 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 21 Aug 2016 00:20:57 -0500 Subject: Toggle dropdown disabled attribute Before this the dropdowns were not being disabled if the user unselected an option from the allowed to merge/push dropdowns. --- app/assets/javascripts/protected_branch_create.js.es6 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branch_create.js.es6 index 2efca2414dc..46beca469b9 100644 --- a/app/assets/javascripts/protected_branch_create.js.es6 +++ b/app/assets/javascripts/protected_branch_create.js.es6 @@ -47,9 +47,7 @@ const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]'); const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]'); - if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){ - this.$form.find('input[type="submit"]').removeAttr('disabled'); - } + this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length)); } } -- cgit v1.2.1 From a7d7e9a51889a276f2d057207fba8d095fa8fc8d Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 21 Aug 2016 00:21:32 -0500 Subject: Set default label if user unselects the option from dropdown --- app/assets/javascripts/protected_branch_access_dropdown.js.es6 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branch_access_dropdown.js.es6 index 2fbb088fa04..7aeb5f92514 100644 --- a/app/assets/javascripts/protected_branch_access_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branch_access_dropdown.js.es6 @@ -10,8 +10,12 @@ selectable: true, inputId: $dropdown.data('input-id'), fieldName: $dropdown.data('field-name'), - toggleLabel(item) { - return item.text; + toggleLabel(item, el) { + if (el.is('.is-active')) { + return item.text; + } else { + return 'Select'; + } }, clicked(item, $el, e) { e.preventDefault(); -- cgit v1.2.1 From f83c5d243459c06fc80a4bf0e287ed59f83e009c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 21 Aug 2016 10:59:19 +0300 Subject: Document the new behavior of protected branches --- .../img/protected_branches_devs_can_push.png | Bin 23976 -> 19312 bytes doc/user/project/img/protected_branches_list.png | Bin 16817 -> 16223 bytes doc/user/project/img/protected_branches_page.png | Bin 0 -> 17839 bytes doc/user/project/protected_branches.md | 78 ++++++++++++--------- 4 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 doc/user/project/img/protected_branches_page.png diff --git a/doc/user/project/img/protected_branches_devs_can_push.png b/doc/user/project/img/protected_branches_devs_can_push.png index 9c33db36586..812cc8767b7 100644 Binary files a/doc/user/project/img/protected_branches_devs_can_push.png and b/doc/user/project/img/protected_branches_devs_can_push.png differ diff --git a/doc/user/project/img/protected_branches_list.png b/doc/user/project/img/protected_branches_list.png index 9f070f7a208..f33f1b2bdb6 100644 Binary files a/doc/user/project/img/protected_branches_list.png and b/doc/user/project/img/protected_branches_list.png differ diff --git a/doc/user/project/img/protected_branches_page.png b/doc/user/project/img/protected_branches_page.png new file mode 100644 index 00000000000..1585dde5b29 Binary files /dev/null and b/doc/user/project/img/protected_branches_page.png differ diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index 96d9bdc1b29..330e0040998 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -5,6 +5,8 @@ idea of having read or write permission to the repository and branches. To prevent people from messing with history or pushing code without review, we've created protected branches. +## Overview + By default, a protected branch does four simple things: - it prevents its creation, if not already created, from everybody except users @@ -15,6 +17,11 @@ By default, a protected branch does four simple things: See the [Changelog](#changelog) section for changes over time. +> +>Added benefits for GitLab Enterprise Edition: +> +>- Restrict push and merge access to [certain users][ee-restrict] + ## Configuring protected branches To protect a branch, you need to have at least Master permission level. Note @@ -28,26 +35,45 @@ that the `master` branch is protected by default. 1. From the **Branch** dropdown menu, select the branch you want to protect and click **Protect**. In the screenshot below, we chose the `develop` branch. - ![Choose protected branch](img/protected_branches_choose_branch.png) + ![Protected branches page](img/protected_branches_page.png) -1. Once done, the protected branch will appear in the "Already protected" list. +1. Once done, the protected branch will appear in the "Protected branches" list. ![Protected branches list](img/protected_branches_list.png) +## Using the Allowed to merge and Allowed to push settings + +> This feature was [introduced][ce-5081] in GitLab 8.11. + +Since GitLab 8.11, we added another layer of branch protection which provides +more granular management of protected branches. The "Developers can push" +option was replaced by an "Allowed to push" setting which can be set to +allow/prohibit Masters and/or Developers to push to a protected branch. + +Using the "Allowed to push" and "Allowed to merge" settings, you can control +the actions that different roles can perform with the protected branch. +For example, you could set "Allowed to push" to "No one", and "Allowed to merge" +to "Developers + Masters", to require _everyone_ to submit a merge request for +changes going into the protected branch. This is compatible with workflows like +the [GitLab workflow](../../workflow/gitlab_flow.md). + +However, there are workflows where that is not needed, and only protecting from +force pushes and branch removal is useful. For those workflows, you can allow +everyone with write access to push to a protected branch by setting +"Allowed to push" to "Developers + Masters". + +You can set the "Allowed to push" and "Allowed to merge" options while creating +a protected branch or afterwards by selecting the option you want from the +dropdown list in the "Already protected" area. -Since GitLab 8.10, we added another layer of branch protection which provides -more granular management of protected branches. You can now choose the option -"Developers can merge" so that Developer users can merge a merge request but -not directly push. In that case, your branches are protected from direct pushes, -yet Developers don't need elevated permissions or wait for someone with a higher -permission level to press merge. +![Developers can push](img/protected_branches_devs_can_push.png) -You can set this option while creating the protected branch or after its -creation. +If you don't choose any of those options while creating a protected branch, +they are set to "Masters" by default. ## Wildcard protected branches -> [Introduced][ce-4665] in GitLab 8.10. +> This feature was [introduced][ce-4665] in GitLab 8.10. You can specify a wildcard protected branch, which will protect all branches matching the wildcard. For example: @@ -66,40 +92,22 @@ Two different wildcards can potentially match the same branch. For example, In that case, if _any_ of these protected branches have a setting like "Allowed to push", then `production-stable` will also inherit this setting. -If you click on a protected branch's name that is created using a wildcard, -you will be presented with a list of all matching branches: +If you click on a protected branch's name, you will be presented with a list of +all matching branches: ![Protected branch matches](img/protected_branches_matches.png) ## Restrict the creation of protected branches -Creating a protected branch or a list of protected branches using the wildcard -feature, not only you are restricting pushes to those branches, but also their -creation if not already created. - -## Error messages when pushing to a protected branch - -A user with insufficient permissions will be presented with an error when -creating or pushing to a branch that's prohibited, either through GitLab's UI: - -![Protected branch error GitLab UI](img/protected_branches_error_ui.png) - -or using Git from their terminal: - -```bash -remote: GitLab: You are not allowed to push code to protected branches on this project. -To https://gitlab.example.com/thedude/bowling.git - ! [remote rejected] staging-stable -> staging-stable (pre-receive hook declined) -error: failed to push some refs to 'https://gitlab.example.com/thedude/bowling.git' -``` - ## Changelog -**8.10.0** +**8.10** -- Allow specifying protected branches using wildcards [gitlab-org/gitlab-ce!5081][ce-4665] +- Allow specifying protected branches using wildcards [gitlab-org/gitlab-ce!5081][ce-5081] +- Allow creating protected branches that can't be pushed to [gitlab-org/gitlab-ce!5081][ce-4665] --- [ce-4665]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4665 "Allow specifying protected branches using wildcards" [ce-5081]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081 "Allow creating protected branches that can't be pushed to" +[ee-restrict]: http://docs.gitlab.com/ee/user/project/protected_branches.html#restricting-push-and-merge-access-to-certain-users -- cgit v1.2.1 From 884baf2659cc7e220ed3fd6ebb6a66d9e15b7e6b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 18 Aug 2016 12:54:40 +0100 Subject: Replaced all `first` with `match:` equivalents --- spec/features/merge_requests/diff_notes_spec.rb | 163 ++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 spec/features/merge_requests/diff_notes_spec.rb diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb new file mode 100644 index 00000000000..e1da6338ae4 --- /dev/null +++ b/spec/features/merge_requests/diff_notes_spec.rb @@ -0,0 +1,163 @@ +require 'spec_helper' + +feature 'Diff notes', js: true, feature: true do + include WaitForAjax + + before do + login_as :admin + @merge_request = create(:merge_request) + @project = @merge_request.source_project + end + + context 'merge request diffs' do + let(:comment_button_class) { '.add-diff-note' } + let(:notes_holder_input_class) { 'js-temp-notes-holder' } + let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' } + let(:test_note_comment) { 'this is a test note!' } + + context 'when hovering over the parallel view diff file' do + before(:each) do + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + click_link 'Side-by-side' + end + + context 'with an old line on the left and no line on the right' do + it 'should allow commenting on the left side' do + should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left') + end + + it 'should not allow commenting on the right side' do + should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right') + end + end + + context 'with no line on the left and a new line on the right' do + it 'should not allow commenting on the left side' do + should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left') + end + + it 'should allow commenting on the right side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right') + end + end + + context 'with an old line on the left and a new line on the right' do + it 'should allow commenting on the left side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left') + end + + it 'should allow commenting on the right side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right') + end + end + + context 'with an unchanged line on the left and an unchanged line on the right' do + it 'should allow commenting on the left side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left') + end + + it 'should allow commenting on the right side' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right') + end + end + + context 'with a match line' do + it 'should not allow commenting on the left side' do + should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left') + end + + it 'should not allow commenting on the right side' do + should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right') + end + end + end + + context 'when hovering over the inline view diff file' do + before do + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + click_link 'Inline' + end + + context 'with a new line' do + it 'should allow commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) + end + end + + context 'with an old line' do + it 'should allow commenting' do + should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) + end + end + + context 'with an unchanged line' do + it 'should allow commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) + end + end + + context 'with a match line' do + it 'should not allow commenting' do + should_not_allow_commenting(find('.match', match: :first)) + end + end + end + + def should_allow_commenting(line_holder, diff_side = nil) + line = get_line_components(line_holder, diff_side) + line[:content].hover + expect(line[:num]).to have_css comment_button_class + + comment_on_line(line_holder, line) + wait_for_ajax + + assert_comment_persistence(line_holder) + end + + def should_not_allow_commenting(line_holder, diff_side = nil) + line = get_line_components(line_holder, diff_side) + line[:content].hover + expect(line[:num]).not_to have_css comment_button_class + end + + def get_line_components(line_holder, diff_side = nil) + if diff_side.nil? + get_inline_line_components(line_holder) + else + get_parallel_line_components(line_holder, diff_side) + end + end + + def get_inline_line_components(line_holder) + { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) } + end + + def get_parallel_line_components(line_holder, diff_side = nil) + side_index = diff_side == 'left' ? 0 : 1 + # Wait for `.line_content` + line_holder.find('.line_content', match: :first) + # Wait for `.diff-line-num` + line_holder.find('.diff-line-num', match: :first) + { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } + end + + def comment_on_line(line_holder, line) + line[:num].find(comment_button_class).trigger 'click' + line_holder.find(:xpath, notes_holder_input_xpath) + + notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath) + expect(notes_holder_input[:class]).to include(notes_holder_input_class) + + notes_holder_input.fill_in 'note[note]', with: test_note_comment + click_button 'Comment' + end + + def assert_comment_persistence(line_holder) + expect(line_holder).to have_xpath notes_holder_input_xpath + + notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) + expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) + expect(notes_holder_saved).to have_content test_note_comment + end + end +end -- cgit v1.2.1 From 5906cca0135c0044d984417a745ba9f5e644c5e5 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 21 Aug 2016 09:32:06 +0100 Subject: Fixed timing issue with selecting parallel or inline view types --- spec/features/merge_requests/diff_notes_spec.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb index e1da6338ae4..b8f82d06e19 100644 --- a/spec/features/merge_requests/diff_notes_spec.rb +++ b/spec/features/merge_requests/diff_notes_spec.rb @@ -17,8 +17,7 @@ feature 'Diff notes', js: true, feature: true do context 'when hovering over the parallel view diff file' do before(:each) do - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) - click_link 'Side-by-side' + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel') end context 'with an old line on the left and no line on the right' do @@ -74,8 +73,7 @@ feature 'Diff notes', js: true, feature: true do context 'when hovering over the inline view diff file' do before do - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) - click_link 'Inline' + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') end context 'with a new line' do @@ -109,7 +107,6 @@ feature 'Diff notes', js: true, feature: true do expect(line[:num]).to have_css comment_button_class comment_on_line(line_holder, line) - wait_for_ajax assert_comment_persistence(line_holder) end @@ -150,6 +147,7 @@ feature 'Diff notes', js: true, feature: true do notes_holder_input.fill_in 'note[note]', with: test_note_comment click_button 'Comment' + wait_for_ajax end def assert_comment_persistence(line_holder) -- cgit v1.2.1 From a816281fcb41a428bedd44997cf58cbc52e2d111 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 21 Aug 2016 22:15:05 -0500 Subject: Remove tab stop from issuable form added by description templates --- app/views/shared/issuable/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 544ed6203aa..7b8f95a86ee 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -42,7 +42,7 @@ - if can_add_template?(issuable) %p.help-block Add - = link_to "description templates", help_page_path('user/project/description_templates') + = link_to "description templates", help_page_path('user/project/description_templates'), tabindex: -1 to help your contributors communicate effectively! .form-group.detail-page-description -- cgit v1.2.1 From eb355dec8768ef128795309c6c9ffa296a6eee22 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 22 Aug 2016 14:18:58 +0300 Subject: Restore diff_sha_refs method Signed-off-by: Dmitriy Zaporozhets --- app/models/diff_note.rb | 6 +++++- app/models/merge_request.rb | 31 ++++++++++++++++--------------- spec/models/merge_request_spec.rb | 6 +++--- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 0c23c1c1934..c8320ff87fa 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -118,7 +118,11 @@ class DiffNote < Note end def noteable_diff_refs - noteable.diff_refs + if noteable.respond_to?(:diff_sha_refs) + noteable.diff_sha_refs + else + noteable.diff_refs + end end def set_original_position diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 14b785e6bd4..615e550cf05 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -265,28 +265,29 @@ class MergeRequest < ActiveRecord::Base @source_branch_sha || source_branch_head.try(:sha) end - def branch_merge_base_sha - branch_merge_base_commit.try(:sha) + def diff_refs + return unless diff_start_commit || diff_base_commit + + Gitlab::Diff::DiffRefs.new( + base_sha: diff_base_sha, + start_sha: diff_start_sha, + head_sha: diff_head_sha + ) end - def diff_refs + # Return diff_refs instance trying to not touch the git repository + def diff_sha_refs if merge_request_diff && merge_request_diff.diff_refs_by_sha? merge_request_diff.diff_refs else - start_sha = target_branch_sha - head_sha = source_branch_sha - base_sha = branch_merge_base_sha - - if start_sha || base_sha - Gitlab::Diff::DiffRefs.new( - base_sha: base_sha, - start_sha: start_sha, - head_sha: head_sha - ) - end + diff_refs end end + def branch_merge_base_sha + branch_merge_base_commit.try(:sha) + end + def validate_branches if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" @@ -748,7 +749,7 @@ class MergeRequest < ActiveRecord::Base end def has_complete_diff_refs? - diff_refs && diff_refs.complete? + diff_sha_refs && diff_sha_refs.complete? end def update_diff_notes_positions(old_diff_refs:, new_diff_refs:) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 573c8c6c9ce..18d178be49c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -782,7 +782,7 @@ describe MergeRequest, models: true do end end - describe "#diff_refs" do + describe "#diff_sha_refs" do context "with diffs" do subject { create(:merge_request, :with_diffs) } @@ -791,7 +791,7 @@ describe MergeRequest, models: true do expect_any_instance_of(Repository).not_to receive(:commit) - subject.diff_refs + subject.diff_sha_refs end it "returns expected diff_refs" do @@ -801,7 +801,7 @@ describe MergeRequest, models: true do head_sha: subject.merge_request_diff.head_commit_sha ) - expect(subject.diff_refs).to eq(expected_diff_refs) + expect(subject.diff_sha_refs).to eq(expected_diff_refs) end end end -- cgit v1.2.1 From bac55c346aea519eab30bf00ffa24553b7349891 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 22 Aug 2016 12:56:25 +0200 Subject: Bump SimpleCov merge timeout to 365 days --- spec/simplecov_env.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb index 6f8f7109e14..b507d38f472 100644 --- a/spec/simplecov_env.rb +++ b/spec/simplecov_env.rb @@ -1,4 +1,5 @@ require 'simplecov' +require 'active_support/core_ext/numeric/time' module SimpleCovEnv extend self @@ -48,7 +49,7 @@ module SimpleCovEnv add_group 'Uploaders', 'app/uploaders' add_group 'Validators', 'app/validators' - merge_timeout 7200 + merge_timeout 365.days end end end -- cgit v1.2.1 From 13a9f454ff0f8fedf6f1bd584beeb05957a395f7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 15:14:36 +0300 Subject: Add changelog items to protected branches docs --- doc/user/project/protected_branches.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index 330e0040998..53a6fdfa820 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -97,17 +97,20 @@ all matching branches: ![Protected branch matches](img/protected_branches_matches.png) -## Restrict the creation of protected branches - ## Changelog +**8.11** + +- Allow creating protected branches that can't be pushed to [gitlab-org/gitlab-ce!5081][ce-5081] + **8.10** -- Allow specifying protected branches using wildcards [gitlab-org/gitlab-ce!5081][ce-5081] -- Allow creating protected branches that can't be pushed to [gitlab-org/gitlab-ce!5081][ce-4665] +- Allow developers to merge into a protected branch without having push access [gitlab-org/gitlab-ce!4892][ce-4892] +- Allow specifying protected branches using wildcards [gitlab-org/gitlab-ce!4665][ce-4665] --- [ce-4665]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4665 "Allow specifying protected branches using wildcards" +[ce-4892]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4892 "Allow developers to merge into a protected branch without having push access" [ce-5081]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081 "Allow creating protected branches that can't be pushed to" [ee-restrict]: http://docs.gitlab.com/ee/user/project/protected_branches.html#restricting-push-and-merge-access-to-certain-users -- cgit v1.2.1 From 16f484890c9a9489786a0eb138bec701d9d45dc8 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 15:19:01 +0300 Subject: Reword GitLab EE ad --- doc/user/project/protected_branches.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index 53a6fdfa820..6e0f2dcc436 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -18,7 +18,7 @@ By default, a protected branch does four simple things: See the [Changelog](#changelog) section for changes over time. > ->Added benefits for GitLab Enterprise Edition: +>Additional functionality for GitLab Enterprise Edition: > >- Restrict push and merge access to [certain users][ee-restrict] -- cgit v1.2.1 From e466aeb87ee620f83320f0c9718386c38be56a74 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 15:22:05 +0300 Subject: Conform to doc styleguide on new features --- doc/user/project/protected_branches.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index 6e0f2dcc436..f7a686d2ccf 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -43,7 +43,7 @@ that the `master` branch is protected by default. ## Using the Allowed to merge and Allowed to push settings -> This feature was [introduced][ce-5081] in GitLab 8.11. +> [Introduced][ce-5081] in GitLab 8.11. Since GitLab 8.11, we added another layer of branch protection which provides more granular management of protected branches. The "Developers can push" @@ -73,7 +73,7 @@ they are set to "Masters" by default. ## Wildcard protected branches -> This feature was [introduced][ce-4665] in GitLab 8.10. +> [Introduced][ce-4665] in GitLab 8.10. You can specify a wildcard protected branch, which will protect all branches matching the wildcard. For example: -- cgit v1.2.1 From dde44b83ddeae75a5c14810d0571ee5c7ab0a26d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 22 Aug 2016 15:30:23 +0300 Subject: Fix merge request diff api Signed-off-by: Dmitriy Zaporozhets --- lib/api/entities.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3f050a8fd81..3241cb7ca56 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -246,7 +246,7 @@ module API expose :commits, using: Entities::RepoCommit expose :diffs, using: Entities::RepoDiff do |compare, _| - compare.diffs(all_diffs: true).to_a + compare.raw_diffs(all_diffs: true).to_a end end -- cgit v1.2.1 From e4e03d946e1c830973db776a570fc89f66917ff3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 19 Aug 2016 17:21:00 +0200 Subject: Added performance guidelines for new MRs These guidelines cover the performance requirement for newly submitted merge requests. These guidelines are put in to place to prevent merge requests from negatively impacting GitLab performance as much as possible. --- CONTRIBUTING.md | 2 + doc/development/README.md | 2 + .../merge_request_performance_guidelines.md | 171 +++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 doc/development/merge_request_performance_guidelines.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbc8e15bebf..4f10f90a82d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -340,6 +340,8 @@ request is as follows: migrations on a fresh database before the MR is reviewed. If the review leads to large changes in the MR, do this again once the review is complete. 1. For more complex migrations, write tests. +1. Merge requests **must** adhere to the [merge request performance + guidelines](doc/development/merge_request_performance_guidelines.md). The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. This is the best time to submit an MR and get diff --git a/doc/development/README.md b/doc/development/README.md index 57f37da6f80..58c00f618fa 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -18,6 +18,8 @@ ## Process - [Code review guidelines](code_review.md) for reviewing code and having code reviewed. +- [Merge request performance guidelines](merge_request_performance_guidelines.md) + for ensuring merge requests do not negatively impact GitLab performance ## Backend howtos diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md new file mode 100644 index 00000000000..0363bf8c1d5 --- /dev/null +++ b/doc/development/merge_request_performance_guidelines.md @@ -0,0 +1,171 @@ +# Merge Request Performance Guidelines + +To ensure a merge request does not negatively impact performance of GitLab +_every_ merge request **must** adhere to the guidelines outlined in this +document. There are no exceptions to this rule unless specifically discussed +with and agreed upon by merge request endbosses and performance specialists. + +To measure the impact of a merge request you can use +[Sherlock](profiling.md#sherlock). It's also highly recommended that you read +the following guides: + +* [Performance Guidelines](performance.md) +* [What requires downtime?](what_requires_downtime.md) + +## Impact Analysis + +**Summary:** think about the impact your merge request may have on performance +and those maintaining a GitLab setup. + +Any change submitted can have an impact not only on the application itself but +also those maintaining it and those keeping it up and running (e.g. production +engineers). As a result you should think carefully about the impact of your +merge request on not only the application but also on the people keeping it up +and running. + +Can the queries used potentially take down any critical services and result in +engineers being woken up in the night? Can a malicious user abuse the code to +take down a GitLab instance? Will my changes simply make loading a certain page +slower? Will execution time grow exponentially given enough load or data in the +database? + +These are all questions one should ask themselves before submitting a merge +request. It may sometimes be difficult to assess the impact, in which case you +should ask a performance specialist to review your code. See the "Reviewing" +section below for more information. + +## Performance Review + +**Summary:** ask performance specialists to review your code if you're not sure +about the impact. + +Sometimes it's hard to assess the impact of a merge request. In this case you +should ask one of the merge request (mini) endbosses to review your changes. You +can find a list of these endbosses at . An +endboss in turn can request a performance specialist to review the changes. + +## Query Counts + +**Summary:** a merge request **should not** increase the number of executed SQL +queries unless absolutely necessary. + +The number of queries executed by the code modified or added by a merge request +must not increase unless absolutely necessary. When building features it's +entirely possible you will need some extra queries, but you should try to keep +this at a minimum. + +As an example, say you introduce a feature that updates a number of database +rows with the same value. It may be very tempting (and easy) to write this using +the following pseudo code: + +```ruby +objects_to_update.each do |object| + object.some_field = some_value + object.save +end +``` + +This will end up running one query for every object to update. This code can +easily overload a database given enough rows to update or many instances of this +code running in parallel. This particular problem is known as the +["N+1 query problem"](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations). + +In this particular case the workaround is fairly easy: + +```ruby +objects_to_update.update_all(some_field: some_value) +``` + +This uses ActiveRecord's `update_all` method to update all rows in a single +query. This in turn makes it much harder for this code to overload a database. + +## Executing Queries in Loops + +**Summary:** SQL queries **must not** be executed in a loop unless absolutely +necessary. + +Executing SQL queries in a loop can result in many queries being executed +depending on the number of iterations in a loop. This may work fine for a +development environment with little data, but in a production environment this +can quickly spiral out of control. + +There are some cases where this may be needed. If this is the case this should +be clearly mentioned in the merge request description. + +## Eager Loading + +**Summary:** always eager load associations when retrieving more than one row. + +When retrieving multiple database records for which you need to use any +associations you **must** eager load these associations. For example, if you're +retrieving a list of blog posts and you want to display their authors you +**must** eager load the author associations. + +In other words, instead of this: + +```ruby +Post.all.each do |post| + puts post.author.name +end +``` + +You should use this: + +```ruby +Post.all.includes(:author).each do |post| + puts post.author.name +end +``` + +## Memory Usage + +**Summary:** merge requests **must not** increase memory usage unless absolutely +necessary. + +A merge request must not increase the memory usage of GitLab by more than the +absolute bare minimum required by the code. This means that if you have to parse +some large document (e.g. an HTML document) it's best to parse it as a stream +whenever possible, instead of loading the entire input into memory. Sometimes +this isn't possible, in that case this should be stated explicitly in the merge +request. + +## Lazy Rendering of UI Elements + +**Summary:** only render UI elements when they're actually needed. + +Certain UI elements may not always be needed. For example, when hovering over a +diff line there's a small icon displayed that can be used to create a new +comment. Instead of always rendering these kind of elements they should only be +rendered when actually needed. This ensures we don't spend time generating +Haml/HTML when it's not going to be used. + +## Instrumenting New Code + +**Summary:** always add instrumentation for new classes, modules, and methods. + +Newly added classes, modules, and methods must be instrumented. This ensures +we can track the performance of this code over time. + +For more information see [Instrumentation](instrumentation.md). This guide +describes how to add instrumentation and where to add it. + +## Use of Caching + +**Summary:** cache data in memory or in Redis when it's needed multiple times in +a transaction or has to be kept around for a certain time period. + +Sometimes certain bits of data have to be re-used in different places during a +transaction. In these cases this data should be cached in memory to remove the +need for running complex operations to fetch the data. You should use Redis if +data should be cached for a certain time period instead of the duration of the +transaction. + +For example, say you process multiple snippets of text containiner username +mentions (e.g. `Hello @alice` and `How are you doing @alice?`). By caching the +user objects for every username we can remove the need for running the same +query for every mention of `@alice`. + +Caching data per transaction can be done using +[RequestStore](https://github.com/steveklabnik/request_store). Caching data in +Redis can be done using [Rails' caching +system](http://guides.rubyonrails.org/caching_with_rails.html). -- cgit v1.2.1 From d7a9dcc1b14d6abc723b232dbedd025469511cb7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 22 Aug 2016 15:44:48 +0300 Subject: Fix merge request diff cache service spec Signed-off-by: Dmitriy Zaporozhets --- spec/services/merge_requests/merge_request_diff_cache_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb index c4b87468275..807f89e80b7 100644 --- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb +++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb @@ -6,7 +6,7 @@ describe MergeRequests::MergeRequestDiffCacheService do describe '#execute' do it 'retrieves the diff files to cache the highlighted result' do merge_request = create(:merge_request) - cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequest.default_options] + cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options] expect(Rails.cache).to receive(:read).with(cache_key).and_return({}) expect(Rails.cache).to receive(:write).with(cache_key, anything) -- cgit v1.2.1 From c70037752c7d3d7ccf52a19fc8d637ec441bfa25 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 15:53:09 +0300 Subject: Change location of slash commands docs --- doc/user/project/slash_commands.md | 30 ++++++++++++++++++++++++++++++ doc/workflow/README.md | 2 +- doc/workflow/slash_commands.md | 30 ------------------------------ 3 files changed, 31 insertions(+), 31 deletions(-) create mode 100644 doc/user/project/slash_commands.md delete mode 100644 doc/workflow/slash_commands.md diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md new file mode 100644 index 00000000000..91d69d4e77e --- /dev/null +++ b/doc/user/project/slash_commands.md @@ -0,0 +1,30 @@ +# GitLab slash commands + +Slash commands are textual shortcuts for common actions on issues or merge +requests that are usually done by clicking buttons or dropdowns in GitLab's UI. +You can enter these commands while creating a new issue or merge request, and +in comments. Each command should be on a separate line in order to be properly +detected and executed. The commands are removed from the issue, merge request or +comment body before it is saved and will not be visible to anyone else. + +Below is a list of all of the available commands and descriptions about what they +do. + +| Command | Action | +|:---------------------------|:-------------| +| `/close` | Close the issue or merge request | +| `/reopen` | Reopen the issue or merge request | +| `/title ` | Change title | +| `/assign @username` | Assign | +| `/unassign` | Remove assignee | +| `/milestone %milestone` | Set milestone | +| `/remove_milestone` | Remove milestone | +| `/label ~foo ~"bar baz"` | Add label(s) | +| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) | +| `/relabel ~foo ~"bar baz"` | Replace all label(s) | +| `/todo` | Add a todo | +| `/done` | Mark todo as done | +| `/subscribe` | Subscribe | +| `/unsubscribe` | Unsubscribe | +| `/due ` | Set due date | +| `/remove_due_date` | Remove due date | diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 1653d95e722..4a7e17039cd 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -7,7 +7,6 @@ - [GitLab Flow](gitlab_flow.md) - [Groups](groups.md) - [Keyboard shortcuts](shortcuts.md) -- [Slash commands](slash_commands.md) - [File finder](file_finder.md) - [Labels](../user/project/labels.md) - [Notification emails](notifications.md) @@ -15,6 +14,7 @@ - [Project forking workflow](forking_workflow.md) - [Project users](add-user/add-user.md) - [Protected branches](../user/project/protected_branches.md) +- [Slash commands](../user/project/slash_commands.md) - [Sharing a project with a group](share_with_group.md) - [Share projects with other groups](share_projects_with_other_groups.md) - [Web Editor](web_editor.md) diff --git a/doc/workflow/slash_commands.md b/doc/workflow/slash_commands.md deleted file mode 100644 index 91d69d4e77e..00000000000 --- a/doc/workflow/slash_commands.md +++ /dev/null @@ -1,30 +0,0 @@ -# GitLab slash commands - -Slash commands are textual shortcuts for common actions on issues or merge -requests that are usually done by clicking buttons or dropdowns in GitLab's UI. -You can enter these commands while creating a new issue or merge request, and -in comments. Each command should be on a separate line in order to be properly -detected and executed. The commands are removed from the issue, merge request or -comment body before it is saved and will not be visible to anyone else. - -Below is a list of all of the available commands and descriptions about what they -do. - -| Command | Action | -|:---------------------------|:-------------| -| `/close` | Close the issue or merge request | -| `/reopen` | Reopen the issue or merge request | -| `/title ` | Change title | -| `/assign @username` | Assign | -| `/unassign` | Remove assignee | -| `/milestone %milestone` | Set milestone | -| `/remove_milestone` | Remove milestone | -| `/label ~foo ~"bar baz"` | Add label(s) | -| `/unlabel ~foo ~"bar baz"` | Remove all or specific label(s) | -| `/relabel ~foo ~"bar baz"` | Replace all label(s) | -| `/todo` | Add a todo | -| `/done` | Mark todo as done | -| `/subscribe` | Subscribe | -| `/unsubscribe` | Unsubscribe | -| `/due ` | Set due date | -| `/remove_due_date` | Remove due date | -- cgit v1.2.1 From 2955ca51e306452590131a361f9f0ab293e47114 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 15:56:43 +0300 Subject: Fix docs not rendering the pipe symbol --- doc/user/project/slash_commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md index 91d69d4e77e..11e1574f772 100644 --- a/doc/user/project/slash_commands.md +++ b/doc/user/project/slash_commands.md @@ -26,5 +26,5 @@ do. | `/done` | Mark todo as done | | `/subscribe` | Subscribe | | `/unsubscribe` | Unsubscribe | -| `/due ` | Set due date | +| `/due ` | Set due date | | `/remove_due_date` | Remove due date | -- cgit v1.2.1 From 9020d8d19edf344d18cddce7d209448bcdbdabdc Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 16:02:24 +0300 Subject: Change location of slash commands in notes widget --- app/views/projects/notes/_hints.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml index cf6e14648cc..f4212350c2d 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -5,7 +5,7 @@ = link_to 'Markdown', help_page_path('markdown/markdown'), target: '_blank', tabindex: -1 - if supports_slash_commands and - = link_to 'slash commands', help_page_path('workflow/slash_commands'), target: '_blank', tabindex: -1 + = link_to 'slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1 are - else is -- cgit v1.2.1 From 7a34794a98e498d69cfeac74c20d15f167e7c070 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 8 Aug 2016 17:26:32 +0300 Subject: Documentation for Issue Boards --- doc/user/project/issue_board.md | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 doc/user/project/issue_board.md diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md new file mode 100644 index 00000000000..5890c48013f --- /dev/null +++ b/doc/user/project/issue_board.md @@ -0,0 +1,45 @@ +# Issue board + +> [Introduced][ce-5554] in GitLab 8.11. + +The GitLab Issue Board is a software project management tool used to plan, +organize, and visualize a workflow for a feature or product release. + +It allows you to create lists, which are based on the [labels] that already +exist in the issue tracker. Issues can be seen as cards and they can easily be +moved between the lists, as to create workflows. + +The starting point is two lists: **Backlog** and **Done**. Under the **Backlog** +list, all the issues that are not assigned to a list will appear. Drag a card +to the **Done** list and the relevant issue will be closed. + +## Creating workflows + +By adding new lists, you can create workflows. For instance you can create a +list based on the label of `Design` and one for `Backend`. A designer can start +working on an issue by dragging it to `Design`. That way, everyone knows, this +issue is now being worked on by the designers. Then, once they’re done, all +they have to do is drag it over to the next list, Backend, where a Backend +developer can eventually pick it up. Once they’re done, they move it to closed, +to close the issue. + +As lists in Issue Boards are based on labels, it works out of the box with your +existing issues. So if you've already labeled things with `Backend` and `Frontend`, +the issue will appear in the lists as you create them. In addition, this means +you can easily move something between lists by changing a label. + +If you move an issue from one list to the next, it removes the label from the +list it comes from and adds the label from the list it moves towards. + +## Pro-tips + +- Issue boards can have between 2-10 lists which display as columns. +- Creating a new list is easy! Simply click the **Create new list** button in + the upper right corner and select the labels from your GitLab issue tracker + to populate the list. +- If you need to create a list based on a label that doesn't exist yet, just + click **Create new list** and then "Create new**. +- All issues can be dragged and dropped into lists. + +[ce-5554]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5554 +[labels]: ./labels.md -- cgit v1.2.1 From aef158a74dd4bc8c5a40afd8976419dd6052fcd0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 16 Aug 2016 13:40:16 +0300 Subject: Great refactor of Issue Board docs --- doc/user/project/img/issue_board.png | Bin 0 -> 200614 bytes doc/user/project/issue_board.md | 98 +++++++++++++++++++++++++++++++---- doc/workflow/README.md | 1 + 3 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 doc/user/project/img/issue_board.png diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png new file mode 100644 index 00000000000..accd0900cbb Binary files /dev/null and b/doc/user/project/img/issue_board.png differ diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 5890c48013f..29c823a68f3 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -4,15 +4,96 @@ The GitLab Issue Board is a software project management tool used to plan, organize, and visualize a workflow for a feature or product release. +It can be seen like a light version of a [Kanban] or a [Scrum] board. -It allows you to create lists, which are based on the [labels] that already -exist in the issue tracker. Issues can be seen as cards and they can easily be +## Overview + +The Issue Board builds on GitLab's existing issue tracking functionality and +leverages the power of [labels] by utilizing them as lists of the scrum board. + +With the Issue Board you can have a different view of your issues while also +maintaining the same filtering and sorting abilities you see across the +issue tracker. + +There are three types of lists, of which two are default: + +- **Backlog** (default): shows all issues that do not fall in one of the other + lists. Always appears on the very left. +- **Done** (default): shows all closed issues. Always appears on the very right. +- Label list: a list based on a label. It shows all issues with that label. + +![GitLab Issue Board](img/issue_board.png) + +--- + +Below is a table of the definitions used for GitLab's Issue Board. + +| Term | Definition | +| ---- | ----------- | +| **Issue Board** | It can have up to 10 lists with each list consisting of issues represented by cards. | +| **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. You can create up to 10 lists per Issue Board. | +| **Card** | Every card represents an issue. The information you can see on a card consists of the issue number, the issue title and the labels associated with it. You can drag cards around from one lists to another. Issues are [ordered by priority](labels.md#prioritize-labels). | + + +## Functionality + +The Issue Board consists of lists appearing as columns. Each list is named after +and based on the labels that already exist in your issue tracker. + +It allows you to create lists, which are based on the labels that already +exist in your issue tracker. Issues can be seen as cards and they can easily be moved between the lists, as to create workflows. +Every list contains all issues that are associated with the corresponding label. + +The issues inside each list are sorted by priority. + +- Issue boards can have between 2-10 lists which display as columns. + +The first time you navigate to your Issue Board, you will be presented with the +two special lists ('Backlog' and 'Done') and a welcoming message that The starting point is two lists: **Backlog** and **Done**. Under the **Backlog** list, all the issues that are not assigned to a list will appear. Drag a card to the **Done** list and the relevant issue will be closed. +Here's a list of actions you can take in an Issue Board: + +1. Create a new issue list +1. Delete an issue list +1. Drag issues between lists +1. Drag and reorder the lists themselves +1. Change issue labels on-the-fly while dragging issues between lists +1. Close an issue if you drag it to the 'Done' list +1. Create a new list from a non-existing label by creating the label on-the-fly +1. Populate lists with issues automatically + +Issues > Labels + +Board > Lists > Cards + +Moving an issue between lists removes the label from the list it came from (exception: backlog) and adds the label of the list it goes to: + +When moving from the backlog, just add the new label. The backlog is not a label. + +When moving to Done, remove the label of the list it came from and close the issue. + +If an issue exists in multiple label lists, just show it in multiple lists: + + +## First time using the Issue Board + +When default lists are created, they are empty because the labels associated to them did not exist up until that moment, which means the system has no way of populating them automatically. It'll be the users' job to add individual issues to them. + +## Adding a new list + +Add a new list by clicking on the button. In a modal you will find a label dropdown, where you can also create new labels (like in the sidebar): + +The new list should be inserted at the end of the lists, before Done. + +## Moving lists + +You should be able to drag the label lists around by dragging them on the top. + ## Creating workflows By adding new lists, you can create workflows. For instance you can create a @@ -31,15 +112,12 @@ you can easily move something between lists by changing a label. If you move an issue from one list to the next, it removes the label from the list it comes from and adds the label from the list it moves towards. -## Pro-tips +## Filtering issues -- Issue boards can have between 2-10 lists which display as columns. -- Creating a new list is easy! Simply click the **Create new list** button in - the upper right corner and select the labels from your GitLab issue tracker - to populate the list. -- If you need to create a list based on a label that doesn't exist yet, just - click **Create new list** and then "Create new**. -- All issues can be dragged and dropped into lists. +You should be able to use the filters on top, as seen in the mockup and similar to the issue list. +Every issue contains metadata. [ce-5554]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5554 [labels]: ./labels.md +[scrum]: https://en.wikipedia.org/wiki/Scrum_(software_development) +[kanban]: https://en.wikipedia.org/wiki/Kanban_(development) diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 1653d95e722..8979600735a 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -6,6 +6,7 @@ - [Feature branch workflow](workflow.md) - [GitLab Flow](gitlab_flow.md) - [Groups](groups.md) +- [Issue Board](../user/project/issue_board.md) - [Keyboard shortcuts](shortcuts.md) - [Slash commands](slash_commands.md) - [File finder](file_finder.md) -- cgit v1.2.1 From 641e50368a17475404056e835e510305d178dc89 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 16 Aug 2016 19:30:03 +0300 Subject: Clean up Issue Board doc a bit --- doc/user/project/issue_board.md | 62 +++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 29c823a68f3..75ac14167fd 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -34,59 +34,47 @@ Below is a table of the definitions used for GitLab's Issue Board. | **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. You can create up to 10 lists per Issue Board. | | **Card** | Every card represents an issue. The information you can see on a card consists of the issue number, the issue title and the labels associated with it. You can drag cards around from one lists to another. Issues are [ordered by priority](labels.md#prioritize-labels). | - ## Functionality -The Issue Board consists of lists appearing as columns. Each list is named after -and based on the labels that already exist in your issue tracker. - -It allows you to create lists, which are based on the labels that already -exist in your issue tracker. Issues can be seen as cards and they can easily be -moved between the lists, as to create workflows. - -Every list contains all issues that are associated with the corresponding label. - -The issues inside each list are sorted by priority. - -- Issue boards can have between 2-10 lists which display as columns. +The Issue Board consists of lists appearing as columns. Each list you add is +named after and based on the labels that already exist in your issue tracker. +Issues can be seen as cards and they can easily be moved between the lists, as +to create workflows. The issues inside each list are sorted by priority. The first time you navigate to your Issue Board, you will be presented with the -two special lists ('Backlog' and 'Done') and a welcoming message that -The starting point is two lists: **Backlog** and **Done**. Under the **Backlog** -list, all the issues that are not assigned to a list will appear. Drag a card +two special lists (**Backlog** and **Done**) and a welcoming message that +The starting point is two lists: **Backlog** and **Done**. The **Backlog** +list shows all issues that do not fall in one of the other lists. Drag a card to the **Done** list and the relevant issue will be closed. Here's a list of actions you can take in an Issue Board: -1. Create a new issue list +1. Add a new issue list 1. Delete an issue list 1. Drag issues between lists 1. Drag and reorder the lists themselves 1. Change issue labels on-the-fly while dragging issues between lists -1. Close an issue if you drag it to the 'Done' list -1. Create a new list from a non-existing label by creating the label on-the-fly +1. Close an issue if you drag it to the **Done** list +1. Add a new list from a non-existing label by creating the label on-the-fly 1. Populate lists with issues automatically -Issues > Labels - -Board > Lists > Cards - -Moving an issue between lists removes the label from the list it came from (exception: backlog) and adds the label of the list it goes to: - -When moving from the backlog, just add the new label. The backlog is not a label. +Moving an issue between lists removes the label from the list it came from +and adds the label of the list it goes to. When moving to Done, remove the label of the list it came from and close the issue. -If an issue exists in multiple label lists, just show it in multiple lists: - +An issue can exist in multiple lists if it has more than one labels. ## First time using the Issue Board -When default lists are created, they are empty because the labels associated to them did not exist up until that moment, which means the system has no way of populating them automatically. It'll be the users' job to add individual issues to them. +When default lists are created, they are empty because the labels associated to +them did not exist up until that moment, which means the system has no way of +populating them automatically. It'll be the users' job to add individual issues to them. ## Adding a new list -Add a new list by clicking on the button. In a modal you will find a label dropdown, where you can also create new labels (like in the sidebar): +Add a new list by clicking on the button. In a modal you will find a label +dropdown, where you can also create new labels (like in the sidebar). The new list should be inserted at the end of the lists, before Done. @@ -97,15 +85,15 @@ You should be able to drag the label lists around by dragging them on the top. ## Creating workflows By adding new lists, you can create workflows. For instance you can create a -list based on the label of `Design` and one for `Backend`. A designer can start -working on an issue by dragging it to `Design`. That way, everyone knows, this -issue is now being worked on by the designers. Then, once they’re done, all -they have to do is drag it over to the next list, Backend, where a Backend -developer can eventually pick it up. Once they’re done, they move it to closed, -to close the issue. +list based on the label of 'Frontend' and one for 'Backend'. A designer can start +working on an issue by dragging it from **Backlog** to 'Frontend'. That way, everyone +knows, this issue is now being worked on by the designers. Then, once they’re +done, all they have to do is drag it over to the next list, 'Backend', where a +backend developer can eventually pick it up. Once they’re done, they move it to +**Done**, to close the issue. As lists in Issue Boards are based on labels, it works out of the box with your -existing issues. So if you've already labeled things with `Backend` and `Frontend`, +existing issues. So if you've already labeled things with 'Backend' and 'Frontend', the issue will appear in the lists as you create them. In addition, this means you can easily move something between lists by changing a label. -- cgit v1.2.1 From 31e11c90ce2a28d7733c6596d7beefb4db60a305 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 18 Aug 2016 09:09:16 +0300 Subject: Remove 10 lists limit --- doc/user/project/issue_board.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 75ac14167fd..5f87a06504e 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -30,8 +30,8 @@ Below is a table of the definitions used for GitLab's Issue Board. | Term | Definition | | ---- | ----------- | -| **Issue Board** | It can have up to 10 lists with each list consisting of issues represented by cards. | -| **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. You can create up to 10 lists per Issue Board. | +| **Issue Board** | It can have multiple lists with each list consisting of issues represented by cards. | +| **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. | | **Card** | Every card represents an issue. The information you can see on a card consists of the issue number, the issue title and the labels associated with it. You can drag cards around from one lists to another. Issues are [ordered by priority](labels.md#prioritize-labels). | ## Functionality -- cgit v1.2.1 From 53f11f9be79c30e50d4077257945467ff75e7d03 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 18 Aug 2016 13:31:44 +0300 Subject: Complete rest of documentation for Issue Boards --- doc/user/project/img/issue_board_add_list.png | Bin 0 -> 22046 bytes .../project/img/issue_board_welcome_message.png | Bin 0 -> 92257 bytes doc/user/project/issue_board.md | 136 +++++++++++++-------- 3 files changed, 87 insertions(+), 49 deletions(-) create mode 100644 doc/user/project/img/issue_board_add_list.png create mode 100644 doc/user/project/img/issue_board_welcome_message.png diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png new file mode 100644 index 00000000000..e5538763dee Binary files /dev/null and b/doc/user/project/img/issue_board_add_list.png differ diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png new file mode 100644 index 00000000000..ebb9fbe8246 Binary files /dev/null and b/doc/user/project/img/issue_board_welcome_message.png differ diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 5f87a06504e..a0edc062f99 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -15,6 +15,14 @@ With the Issue Board you can have a different view of your issues while also maintaining the same filtering and sorting abilities you see across the issue tracker. +Below is a table of the definitions used for GitLab's Issue Board. + +| How we call it | What it means | +| -------------- | ------------- | +| **Issue Board** | It represents a different view for your issues. It can have multiple lists with each list consisting of issues represented by cards. | +| **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. | +| **Card** | Every card represents an issue and it is shown under the list for which it has a label for. The information you can see on a card consists of the issue number, the issue title, the assignee and the labels associated with it. You can drag cards around from one list to another. Issues inside lists are [ordered by priority](labels.md#prioritize-labels). | + There are three types of lists, of which two are default: - **Backlog** (default): shows all issues that do not fall in one of the other @@ -26,86 +34,116 @@ There are three types of lists, of which two are default: --- -Below is a table of the definitions used for GitLab's Issue Board. +In short, here's a list of actions you can take in an Issue Board: -| Term | Definition | -| ---- | ----------- | -| **Issue Board** | It can have multiple lists with each list consisting of issues represented by cards. | -| **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. | -| **Card** | Every card represents an issue. The information you can see on a card consists of the issue number, the issue title and the labels associated with it. You can drag cards around from one lists to another. Issues are [ordered by priority](labels.md#prioritize-labels). | +- [Add a new list](#adding-a-new-list). +- [Remove an existing list](#removing-a-list). +- Drag issues between lists. +- Drag and reorder the lists themselves. +- Change issue labels on-the-fly while dragging issues between lists. +- Close an issue if you drag it to the **Done** list. +- Add a new list from a non-existing label by creating the label on-the-fly. +- [Filter issues](#filtering-issues) that appear across your Issue Board. -## Functionality +If you are not able to perform one or more of the things above, make sure you +have the right [permissions](#permissions). -The Issue Board consists of lists appearing as columns. Each list you add is -named after and based on the labels that already exist in your issue tracker. -Issues can be seen as cards and they can easily be moved between the lists, as -to create workflows. The issues inside each list are sorted by priority. +## First time using the Issue Board The first time you navigate to your Issue Board, you will be presented with the -two special lists (**Backlog** and **Done**) and a welcoming message that -The starting point is two lists: **Backlog** and **Done**. The **Backlog** -list shows all issues that do not fall in one of the other lists. Drag a card -to the **Done** list and the relevant issue will be closed. +two special lists (**Backlog** and **Done**) and a welcoming message that gives +you two options. You can either create a predefined set of labels and add their +corresponding lists to the Issue Board or opt-out and use your own lists. + +![Issue Board welcome message](img/issue_board_welcome_message.png) -Here's a list of actions you can take in an Issue Board: +If you choose to use and add the default lists, they will appear as empty +because the labels associated to them will not exist up until that moment, +which means the system has no way of populating them automatically. It'll be +the users' job to add individual issues to them. -1. Add a new issue list -1. Delete an issue list -1. Drag issues between lists -1. Drag and reorder the lists themselves -1. Change issue labels on-the-fly while dragging issues between lists -1. Close an issue if you drag it to the **Done** list -1. Add a new list from a non-existing label by creating the label on-the-fly -1. Populate lists with issues automatically +## Adding a new list -Moving an issue between lists removes the label from the list it came from -and adds the label of the list it goes to. +Add a new list by clicking on the **Add new list** button at the upper right +corner of the Issue Board. -When moving to Done, remove the label of the list it came from and close the issue. +![Issue Board welcome message](img/issue_board_add_list.png) -An issue can exist in multiple lists if it has more than one labels. +Simply choose the label to create the list from. The new list will be inserted +at the end of the lists, before **Done**. Moving and reordering lists is as +easy as dragging them around. -## First time using the Issue Board +To add a list for a label that doesn't exist yet, simply create the label by +choosing **Create new label**. The label will be created on-the-fly and it will +be immediately added to the dropdown. You can now choose it to add a list. -When default lists are created, they are empty because the labels associated to -them did not exist up until that moment, which means the system has no way of -populating them automatically. It'll be the users' job to add individual issues to them. +## Removing a list -## Adding a new list +To remove a list from the Issue Board use the small trash icon that is present +in the list's heading. A confirmation dialog will appear for you to confirm. -Add a new list by clicking on the button. In a modal you will find a label -dropdown, where you can also create new labels (like in the sidebar). +Removing a list doesn't have any effect in issues and labels, it's just the +list view that is removed. -The new list should be inserted at the end of the lists, before Done. +## Filtering issues -## Moving lists +You should be able to use the filters on top of your Issue Board to show only +the results you want. This is similar to the filtering used in the issue tracker +since the metadata from the issues and labels are re-used in the Issue Board. -You should be able to drag the label lists around by dragging them on the top. +You can filter by author, assignee, milestone and label. ## Creating workflows -By adding new lists, you can create workflows. For instance you can create a +By adding new lists, you can create workflows. As lists in Issue Boards are +based on labels, it works out of the box with your existing issues. So if you've +already labeled things with 'Backend' and 'Frontend', the issue will appear in +the lists as you create them. In addition, this means you can easily move +something between lists by changing a label. + +A typical workflow of using the Issue Board would be: + +1. You have [created labels] so that you can easily categorize your issues. +1. You have a bunch of issues (ideally labeled). +1. You visit the Issue Board and start [adding lists](#adding-a-new-list) as to + create a workflow. +1. You move issues around in lists so that your team knows who should be working + on what issue. +1. When the work by one team is done, the issue can be dragged to the next list + so someone else can pick up. +1. When the issue is finally resolved, the issue is moved to the **Done** list + and gets automatically closed. + +For instance you can add a list based on the label of 'Frontend' and one for 'Backend'. A designer can start working on an issue by dragging it from **Backlog** to 'Frontend'. That way, everyone -knows, this issue is now being worked on by the designers. Then, once they’re +knows that this issue is now being worked on by the designers. Then, once they’re done, all they have to do is drag it over to the next list, 'Backend', where a backend developer can eventually pick it up. Once they’re done, they move it to **Done**, to close the issue. -As lists in Issue Boards are based on labels, it works out of the box with your -existing issues. So if you've already labeled things with 'Backend' and 'Frontend', -the issue will appear in the lists as you create them. In addition, this means -you can easily move something between lists by changing a label. +## Permissions -If you move an issue from one list to the next, it removes the label from the -list it comes from and adds the label from the list it moves towards. +[Developers and up](../permissions.md) can use all the functionality of the +Issue Board, that is add/remove lists and drag issues around. -## Filtering issues +## Tips + +A few things to remember: -You should be able to use the filters on top, as seen in the mockup and similar to the issue list. -Every issue contains metadata. +- Moving an issue between lists removes the label from the list it came from + and adds the label from the list it goes to. +- When moving a card to **Done**, the label of the list it came from is removed + the issue gets closed. +- An issue can exist in multiple lists if it has more than one labels. +- Lists are populated with issues automatically if the issues are labeled. +- Clicking on the issue title inside a card will get you to that issue. +- Clicking on a label inside a card will quickly filter the entire Issue Board + and show only the issues from all lists that have that label. +- Issues inside lists are [ordered by priority](labels.md#prioritize-labels). [ce-5554]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5554 [labels]: ./labels.md [scrum]: https://en.wikipedia.org/wiki/Scrum_(software_development) [kanban]: https://en.wikipedia.org/wiki/Kanban_(development) +[created labels]: ./labels.md#create-new-labels -- cgit v1.2.1 From 1804f60fd5a1aab92f553b7c66b79857ef35df35 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 18 Aug 2016 22:34:17 +0300 Subject: Change images to reflect current status, add link to inline docs --- .../shared/issuable/_label_page_default.html.haml | 8 ++++- doc/user/project/img/issue_board_add_list.png | Bin 22046 -> 22391 bytes .../project/img/issue_board_welcome_message.png | Bin 92257 -> 78694 bytes doc/user/project/issue_board.md | 34 ++++++++++----------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml index a76b7baf918..18b50f9da7b 100644 --- a/app/views/shared/issuable/_label_page_default.html.haml +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -8,7 +8,13 @@ - if show_boards_content .issue-board-dropdown-content %p - Each label that exists in your issue tracker can have its own dedicated list. Select a label below to add a list to your Board and it will automatically be populated with issues that have that label. To create a list for a label that doesn't exist yet, simply create the label below. + Each label that exists in your issue tracker can have its own dedicated + list. Select a label below to add a list to your Board and it will + automatically be populated with issues that have that label. To create + a list for a label that doesn't exist yet, simply create the label below. + Learn more in the + = succeed "." do + = link_to "Issue Boards documentation", help_page_path("user/project/issue_board"), class: "underlined-link" = dropdown_filter(filter_placeholder) = dropdown_content - if @project && show_footer diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png index e5538763dee..2b8c10eaa0a 100644 Binary files a/doc/user/project/img/issue_board_add_list.png and b/doc/user/project/img/issue_board_add_list.png differ diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png index ebb9fbe8246..b757faeb230 100644 Binary files a/doc/user/project/img/issue_board_welcome_message.png and b/doc/user/project/img/issue_board_welcome_message.png differ diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index a0edc062f99..0ad21606ef6 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -36,8 +36,8 @@ There are three types of lists, of which two are default: In short, here's a list of actions you can take in an Issue Board: -- [Add a new list](#adding-a-new-list). -- [Remove an existing list](#removing-a-list). +- [Create a new list](#creating-a-new-list). +- [Delete an existing list](#deleting-a-list). - Drag issues between lists. - Drag and reorder the lists themselves. - Change issue labels on-the-fly while dragging issues between lists. @@ -62,10 +62,10 @@ because the labels associated to them will not exist up until that moment, which means the system has no way of populating them automatically. It'll be the users' job to add individual issues to them. -## Adding a new list +## Creating a new list -Add a new list by clicking on the **Add new list** button at the upper right -corner of the Issue Board. +Create a new list by clicking on the **Create new list** button at the upper +right corner of the Issue Board. ![Issue Board welcome message](img/issue_board_add_list.png) @@ -73,16 +73,16 @@ Simply choose the label to create the list from. The new list will be inserted at the end of the lists, before **Done**. Moving and reordering lists is as easy as dragging them around. -To add a list for a label that doesn't exist yet, simply create the label by +To create a list for a label that doesn't exist yet, simply create the label by choosing **Create new label**. The label will be created on-the-fly and it will -be immediately added to the dropdown. You can now choose it to add a list. +be immediately added to the dropdown. You can now choose it to create a list. -## Removing a list +## Deleting a list -To remove a list from the Issue Board use the small trash icon that is present +To delete a list from the Issue Board use the small trash icon that is present in the list's heading. A confirmation dialog will appear for you to confirm. -Removing a list doesn't have any effect in issues and labels, it's just the +Deleting a list doesn't have any effect in issues and labels, it's just the list view that is removed. ## Filtering issues @@ -114,13 +114,13 @@ A typical workflow of using the Issue Board would be: 1. When the issue is finally resolved, the issue is moved to the **Done** list and gets automatically closed. -For instance you can add a -list based on the label of 'Frontend' and one for 'Backend'. A designer can start -working on an issue by dragging it from **Backlog** to 'Frontend'. That way, everyone -knows that this issue is now being worked on by the designers. Then, once they’re -done, all they have to do is drag it over to the next list, 'Backend', where a -backend developer can eventually pick it up. Once they’re done, they move it to -**Done**, to close the issue. +For instance you can add a list based on the label of 'Frontend' and one for +'Backend'. A designer can start working on an issue by dragging it from +**Backlog** to 'Frontend'. That way, everyone knows that this issue is now being +worked on by the designers. Then, once they're done, all they have to do is +drag it over to the next list, 'Backend', where a backend developer can +eventually pick it up. Once they’re done, they move it to **Done**, to close the +issue. ## Permissions -- cgit v1.2.1 From 04d4208eed39d72ba4886c9920796d0172a20627 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 19 Aug 2016 08:30:59 +0300 Subject: Add section on searching through the Backlog list --- .../project/img/issue_board_search_backlog.png | Bin 0 -> 25948 bytes doc/user/project/issue_board.md | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 doc/user/project/img/issue_board_search_backlog.png diff --git a/doc/user/project/img/issue_board_search_backlog.png b/doc/user/project/img/issue_board_search_backlog.png new file mode 100644 index 00000000000..112ea171539 Binary files /dev/null and b/doc/user/project/img/issue_board_search_backlog.png differ diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 0ad21606ef6..edc93f6bc13 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -85,6 +85,24 @@ in the list's heading. A confirmation dialog will appear for you to confirm. Deleting a list doesn't have any effect in issues and labels, it's just the list view that is removed. +## Searching issues in the Backlog list + +The very first time you start using the Issue Board, it is very likely your +issue tracker is already populated with labels and issues. In that case, +**Backlog** will have all the issues that don't belong to another list, and +**Done** will have all the closed ones. + +For performance and visibility reasons, each list shows the first 20 issues +by default. If you have more than 20, you have to start scrolling down for the +next 20 issues to appear. This can be cumbersome if your issue tracker hosts +hundreds of issues, and for that reason it is easier to search for issues to +move from **Backlog** to another list. + +Start typing in the search bar under the **Backlog** list and the relevant +issues will appear. + +![Issue Board search Backlog](img/issue_board_search_backlog.png) + ## Filtering issues You should be able to use the filters on top of your Issue Board to show only @@ -141,6 +159,9 @@ A few things to remember: - Clicking on a label inside a card will quickly filter the entire Issue Board and show only the issues from all lists that have that label. - Issues inside lists are [ordered by priority](labels.md#prioritize-labels). +- For performance and visibility reasons, each list shows the first 20 issues + by default. If you have more than 20 issues start scrolling down and the next + 20 will appear. [ce-5554]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5554 [labels]: ./labels.md -- cgit v1.2.1 From 3a737e7ea5d4a497489e38ed1e1f14140d7c45e3 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 19 Aug 2016 08:35:39 +0300 Subject: Mention missing labels for corresponding lists --- doc/user/project/issue_board.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index edc93f6bc13..f3fd7e1ec1a 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -149,6 +149,7 @@ Issue Board, that is add/remove lists and drag issues around. A few things to remember: +- The label that corresponds to a list is hidden for issues under that list. - Moving an issue between lists removes the label from the list it came from and adds the label from the list it goes to. - When moving a card to **Done**, the label of the list it came from is removed -- cgit v1.2.1 From 3d1b24767b292af2bcadd3de728abd9a1d08cdb6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 19 Aug 2016 08:44:39 +0300 Subject: Add note about labels and system notes --- doc/user/project/img/issue_board_system_notes.png | Bin 0 -> 20637 bytes doc/user/project/issue_board.md | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 doc/user/project/img/issue_board_system_notes.png diff --git a/doc/user/project/img/issue_board_system_notes.png b/doc/user/project/img/issue_board_system_notes.png new file mode 100644 index 00000000000..b69ef034954 Binary files /dev/null and b/doc/user/project/img/issue_board_system_notes.png differ diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index f3fd7e1ec1a..c0b040c3c07 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -140,6 +140,11 @@ drag it over to the next list, 'Backend', where a backend developer can eventually pick it up. Once they’re done, they move it to **Done**, to close the issue. +This process can be seen clearly when visiting an issue since with every move +to another list the label changes and a system not is recorded. + +![Issue Board system notes](img/issue_board_system_notes.png) + ## Permissions [Developers and up](../permissions.md) can use all the functionality of the -- cgit v1.2.1 From fa72b5fa1481f49758cef45fa37c6d1d4e261e3a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 20 Aug 2016 18:21:30 +0300 Subject: Fix typos and grammar --- doc/user/project/issue_board.md | 49 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index c0b040c3c07..33db21d442b 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -17,13 +17,14 @@ issue tracker. Below is a table of the definitions used for GitLab's Issue Board. -| How we call it | What it means | +| What we call it | What it means | | -------------- | ------------- | | **Issue Board** | It represents a different view for your issues. It can have multiple lists with each list consisting of issues represented by cards. | | **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. | -| **Card** | Every card represents an issue and it is shown under the list for which it has a label for. The information you can see on a card consists of the issue number, the issue title, the assignee and the labels associated with it. You can drag cards around from one list to another. Issues inside lists are [ordered by priority](labels.md#prioritize-labels). | +| **Card** | Every card represents an issue and it is shown under the list for which it has a label. The information you can see on a card consists of the issue number, the issue title, the assignee and the labels associated with it. You can drag cards around from one list to another. Issues inside lists are [ordered by priority](labels.md#prioritize-labels). | -There are three types of lists, of which two are default: +There are three types of lists, the ones you create based on your labels, and +two default: - **Backlog** (default): shows all issues that do not fall in one of the other lists. Always appears on the very left. @@ -42,7 +43,8 @@ In short, here's a list of actions you can take in an Issue Board: - Drag and reorder the lists themselves. - Change issue labels on-the-fly while dragging issues between lists. - Close an issue if you drag it to the **Done** list. -- Add a new list from a non-existing label by creating the label on-the-fly. +- Create a new list from a non-existing label by [creating the label on-the-fly](#creating-a-new-list) + within the Issue Board. - [Filter issues](#filtering-issues) that appear across your Issue Board. If you are not able to perform one or more of the things above, make sure you @@ -51,16 +53,17 @@ have the right [permissions](#permissions). ## First time using the Issue Board The first time you navigate to your Issue Board, you will be presented with the -two special lists (**Backlog** and **Done**) and a welcoming message that gives -you two options. You can either create a predefined set of labels and add their -corresponding lists to the Issue Board or opt-out and use your own lists. +two default lists (**Backlog** and **Done**) and a welcoming message that gives +you two options. You can either create a predefined set of labels and create +their corresponding lists to the Issue Board or opt-out and use your own lists. ![Issue Board welcome message](img/issue_board_welcome_message.png) -If you choose to use and add the default lists, they will appear as empty +If you choose to use and create the predefined lists, they will appear as empty because the labels associated to them will not exist up until that moment, -which means the system has no way of populating them automatically. It'll be -the users' job to add individual issues to them. +which means the system has no way of populating them automatically. That's of +course if the predefined labels don't already exist. If any of them does exist, +the list will be created and filled with the issues that have that label. ## Creating a new list @@ -73,7 +76,7 @@ Simply choose the label to create the list from. The new list will be inserted at the end of the lists, before **Done**. Moving and reordering lists is as easy as dragging them around. -To create a list for a label that doesn't exist yet, simply create the label by +To create a list for a label that doesn't yet exist, simply create the label by choosing **Create new label**. The label will be created on-the-fly and it will be immediately added to the dropdown. You can now choose it to create a list. @@ -83,7 +86,7 @@ To delete a list from the Issue Board use the small trash icon that is present in the list's heading. A confirmation dialog will appear for you to confirm. Deleting a list doesn't have any effect in issues and labels, it's just the -list view that is removed. +list view that is removed. You can always add it back later if you need. ## Searching issues in the Backlog list @@ -113,7 +116,7 @@ You can filter by author, assignee, milestone and label. ## Creating workflows -By adding new lists, you can create workflows. As lists in Issue Boards are +By reordering your lists, you can create workflows. As lists in Issue Boards are based on labels, it works out of the box with your existing issues. So if you've already labeled things with 'Backend' and 'Frontend', the issue will appear in the lists as you create them. In addition, this means you can easily move @@ -121,9 +124,10 @@ something between lists by changing a label. A typical workflow of using the Issue Board would be: -1. You have [created labels] so that you can easily categorize your issues. +1. You have [created][create-labels] and [prioritized][label-priority] labels + so that you can easily categorize your issues. 1. You have a bunch of issues (ideally labeled). -1. You visit the Issue Board and start [adding lists](#adding-a-new-list) as to +1. You visit the Issue Board and start [creating lists](#creating-a-new-list) to create a workflow. 1. You move issues around in lists so that your team knows who should be working on what issue. @@ -132,7 +136,7 @@ A typical workflow of using the Issue Board would be: 1. When the issue is finally resolved, the issue is moved to the **Done** list and gets automatically closed. -For instance you can add a list based on the label of 'Frontend' and one for +For instance you can create a list based on the label of 'Frontend' and one for 'Backend'. A designer can start working on an issue by dragging it from **Backlog** to 'Frontend'. That way, everyone knows that this issue is now being worked on by the designers. Then, once they're done, all they have to do is @@ -148,7 +152,7 @@ to another list the label changes and a system not is recorded. ## Permissions [Developers and up](../permissions.md) can use all the functionality of the -Issue Board, that is add/remove lists and drag issues around. +Issue Board, that is create/delete lists and drag issues around. ## Tips @@ -158,13 +162,13 @@ A few things to remember: - Moving an issue between lists removes the label from the list it came from and adds the label from the list it goes to. - When moving a card to **Done**, the label of the list it came from is removed - the issue gets closed. -- An issue can exist in multiple lists if it has more than one labels. + and the issue gets closed. +- An issue can exist in multiple lists if it has more than one label. - Lists are populated with issues automatically if the issues are labeled. -- Clicking on the issue title inside a card will get you to that issue. +- Clicking on the issue title inside a card will take you to that issue. - Clicking on a label inside a card will quickly filter the entire Issue Board and show only the issues from all lists that have that label. -- Issues inside lists are [ordered by priority](labels.md#prioritize-labels). +- Issues inside lists are [ordered by priority][label-priority]. - For performance and visibility reasons, each list shows the first 20 issues by default. If you have more than 20 issues start scrolling down and the next 20 will appear. @@ -173,4 +177,5 @@ A few things to remember: [labels]: ./labels.md [scrum]: https://en.wikipedia.org/wiki/Scrum_(software_development) [kanban]: https://en.wikipedia.org/wiki/Kanban_(development) -[created labels]: ./labels.md#create-new-labels +[create-labels]: ./labels.md#create-new-labels +[label-priority]: ./labels.md#prioritize-labels -- cgit v1.2.1 From 31584d8e5518da596cfc0b963e7a403a461e098f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 20 Aug 2016 18:41:57 +0300 Subject: Add links to landing page and youtube video --- doc/user/project/issue_board.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 33db21d442b..9df9ed9c9da 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -6,6 +6,11 @@ The GitLab Issue Board is a software project management tool used to plan, organize, and visualize a workflow for a feature or product release. It can be seen like a light version of a [Kanban] or a [Scrum] board. +Other interesting links: + +- [GitLab Issue Board landing page on about.gitlab.com][landing] +- [YouTube video introduction to Issue Boards][youtube] + ## Overview The Issue Board builds on GitLab's existing issue tracking functionality and @@ -179,3 +184,5 @@ A few things to remember: [kanban]: https://en.wikipedia.org/wiki/Kanban_(development) [create-labels]: ./labels.md#create-new-labels [label-priority]: ./labels.md#prioritize-labels +[landing]: https://about.gitlab.com/solutions/issueboard +[youtube]: https://www.youtube.com/watch?v=UWsJ8tkHAa8 -- cgit v1.2.1 From 6ba9050a7db5585cd4ffc9cdfe95fc64446613ac Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 15:03:26 +0300 Subject: Remove link to docs from new list text --- app/views/shared/issuable/_label_page_default.html.haml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml index 18b50f9da7b..c0dc63be2bf 100644 --- a/app/views/shared/issuable/_label_page_default.html.haml +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -12,9 +12,6 @@ list. Select a label below to add a list to your Board and it will automatically be populated with issues that have that label. To create a list for a label that doesn't exist yet, simply create the label below. - Learn more in the - = succeed "." do - = link_to "Issue Boards documentation", help_page_path("user/project/issue_board"), class: "underlined-link" = dropdown_filter(filter_placeholder) = dropdown_content - if @project && show_footer -- cgit v1.2.1 From 915dda1421d8227c0ba395bfdb04ff915724c4ac Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 17:09:15 +0300 Subject: Split Koding admin/user docs --- app/controllers/koding_controller.rb | 2 +- .../admin/application_settings/_form.html.haml | 2 +- doc/README.md | 2 + doc/administration/integration/koding.md | 242 +++++++++++++++++++++ doc/integration/img/koding_build-in-progress.png | Bin 70949 -> 0 bytes doc/integration/img/koding_build-logs.png | Bin 263623 -> 0 bytes doc/integration/img/koding_build-success.png | Bin 304666 -> 0 bytes doc/integration/img/koding_commit-koding.yml.png | Bin 302703 -> 0 bytes .../img/koding_different-stack-on-mr-try.png | Bin 333649 -> 0 bytes doc/integration/img/koding_edit-on-ide.png | Bin 330880 -> 0 bytes doc/integration/img/koding_enable-koding.png | Bin 73499 -> 0 bytes doc/integration/img/koding_landing.png | Bin 268455 -> 0 bytes .../img/koding_open-gitlab-from-koding.png | Bin 32559 -> 0 bytes doc/integration/img/koding_run-in-ide.png | Bin 65465 -> 0 bytes doc/integration/img/koding_run-mr-in-ide.png | Bin 339759 -> 0 bytes doc/integration/img/koding_set-up-ide.png | Bin 207481 -> 0 bytes doc/integration/img/koding_stack-import.png | Bin 500352 -> 0 bytes doc/integration/img/koding_start-build.png | Bin 105253 -> 0 bytes doc/integration/koding-usage.md | 122 ----------- doc/integration/koding.md | 239 -------------------- doc/user/project/img/koding_build-in-progress.png | Bin 0 -> 70949 bytes doc/user/project/img/koding_build-logs.png | Bin 0 -> 263623 bytes doc/user/project/img/koding_build-success.png | Bin 0 -> 304666 bytes doc/user/project/img/koding_commit-koding.yml.png | Bin 0 -> 302703 bytes .../img/koding_different-stack-on-mr-try.png | Bin 0 -> 333649 bytes doc/user/project/img/koding_edit-on-ide.png | Bin 0 -> 330880 bytes doc/user/project/img/koding_enable-koding.png | Bin 0 -> 73499 bytes doc/user/project/img/koding_landing.png | Bin 0 -> 268455 bytes .../project/img/koding_open-gitlab-from-koding.png | Bin 0 -> 32559 bytes doc/user/project/img/koding_run-in-ide.png | Bin 0 -> 65465 bytes doc/user/project/img/koding_run-mr-in-ide.png | Bin 0 -> 339759 bytes doc/user/project/img/koding_set-up-ide.png | Bin 0 -> 207481 bytes doc/user/project/img/koding_stack-import.png | Bin 0 -> 500352 bytes doc/user/project/img/koding_start-build.png | Bin 0 -> 105253 bytes doc/user/project/koding.md | 128 +++++++++++ 35 files changed, 374 insertions(+), 363 deletions(-) create mode 100644 doc/administration/integration/koding.md delete mode 100644 doc/integration/img/koding_build-in-progress.png delete mode 100644 doc/integration/img/koding_build-logs.png delete mode 100644 doc/integration/img/koding_build-success.png delete mode 100644 doc/integration/img/koding_commit-koding.yml.png delete mode 100644 doc/integration/img/koding_different-stack-on-mr-try.png delete mode 100644 doc/integration/img/koding_edit-on-ide.png delete mode 100644 doc/integration/img/koding_enable-koding.png delete mode 100644 doc/integration/img/koding_landing.png delete mode 100644 doc/integration/img/koding_open-gitlab-from-koding.png delete mode 100644 doc/integration/img/koding_run-in-ide.png delete mode 100644 doc/integration/img/koding_run-mr-in-ide.png delete mode 100644 doc/integration/img/koding_set-up-ide.png delete mode 100644 doc/integration/img/koding_stack-import.png delete mode 100644 doc/integration/img/koding_start-build.png delete mode 100644 doc/integration/koding-usage.md delete mode 100644 doc/integration/koding.md create mode 100644 doc/user/project/img/koding_build-in-progress.png create mode 100644 doc/user/project/img/koding_build-logs.png create mode 100644 doc/user/project/img/koding_build-success.png create mode 100644 doc/user/project/img/koding_commit-koding.yml.png create mode 100644 doc/user/project/img/koding_different-stack-on-mr-try.png create mode 100644 doc/user/project/img/koding_edit-on-ide.png create mode 100644 doc/user/project/img/koding_enable-koding.png create mode 100644 doc/user/project/img/koding_landing.png create mode 100644 doc/user/project/img/koding_open-gitlab-from-koding.png create mode 100644 doc/user/project/img/koding_run-in-ide.png create mode 100644 doc/user/project/img/koding_run-mr-in-ide.png create mode 100644 doc/user/project/img/koding_set-up-ide.png create mode 100644 doc/user/project/img/koding_stack-import.png create mode 100644 doc/user/project/img/koding_start-build.png create mode 100644 doc/user/project/koding.md diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb index bb89f3090f9..f3759b4c0ea 100644 --- a/app/controllers/koding_controller.rb +++ b/app/controllers/koding_controller.rb @@ -3,7 +3,7 @@ class KodingController < ApplicationController layout 'koding' def index - path = File.join(Rails.root, 'doc/integration/koding-usage.md') + path = File.join(Rails.root, 'doc/user/project/koding.md') @markdown = File.read(path) end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index e0878512e62..d929364fc96 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -405,7 +405,7 @@ %strong gitlab team, and you need to provide that team's URL here. Learn more in the = succeed "." do - = link_to "Koding integration documentation", help_page_path("integration/koding") + = link_to "Koding administration documentation", help_page_path("administration/integration/koding") .form-actions diff --git a/doc/README.md b/doc/README.md index fc51ea911b9..195b7c7af62 100644 --- a/doc/README.md +++ b/doc/README.md @@ -18,6 +18,7 @@ - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. +- [Koding](user/project/koding.md) Learn how to use Koding, the online IDE. ## Administrator documentation @@ -29,6 +30,7 @@ - [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, Twitter. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. +- [Koding](administration/integration/koding.md) Set up Koding to use with GitLab. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Log system](administration/logs.md) Log system. - [Environment Variables](administration/environment_variables.md) to configure GitLab. diff --git a/doc/administration/integration/koding.md b/doc/administration/integration/koding.md new file mode 100644 index 00000000000..a2c358af095 --- /dev/null +++ b/doc/administration/integration/koding.md @@ -0,0 +1,242 @@ +# Koding & GitLab + +> [Introduced][ce-5909] in GitLab 8.11. + +This document will guide you through installing and configuring Koding with +GitLab. + +First of all, to be able to use Koding and GitLab together you will need public +access to your server. This allows you to use single sign-on from GitLab to +Koding and using vms from cloud providers like AWS. Koding has a registry for +VMs, called Kontrol and it runs on the same server as Koding itself, VMs from +cloud providers register themselves to Kontrol via the agent that we put into +provisioned VMs. This agent is called Klient and it provides Koding to access +and manage the target machine. + +Kontrol and Klient are based on another technology called +[Kite](https://github.com/koding/kite), that we have written at Koding. Which is a +microservice framework that allows you to develop microservices easily. + +## Requirements + +### Hardware + +Minimum requirements are; + + - 2 cores CPU + - 3G RAM + - 10G Storage + +If you plan to use AWS to install Koding it is recommended that you use at +least a `c3.xlarge` instance. + +### Software + + - [Git](https://git-scm.com) + - [Docker](https://www.docker.com) + - [docker-compose](https://www.docker.com/products/docker-compose) + +Koding can run on most of the UNIX based operating systems, since it's shipped +as containerized with Docker support, it can work on any operating system that +supports Docker. + +Required services are: + +- **PostgreSQL** - Kontrol and Service DB provider +- **MongoDB** - Main DB provider the application +- **Redis** - In memory DB used by both application and services +- **RabbitMQ** - Message Queue for both application and services + +which are also provided as a Docker container by Koding. + + +## Getting Started with Development Versions + + +### Koding + +You can run `docker-compose` environment for developing koding by +executing commands in the following snippet. + +```bash +git clone https://github.com/koding/koding.git +cd koding +docker-compose up +``` + +This should start koding on `localhost:8090`. + +By default there is no team exists in Koding DB. You'll need to create a team +called `gitlab` which is the default team name for GitLab integration in the +configuration. To make things in order it's recommended to create the `gitlab` +team first thing after setting up Koding. + + +### GitLab + +To install GitLab to your environment for development purposes it's recommended +to use GitLab Development Kit which you can get it from +[here](https://gitlab.com/gitlab-org/gitlab-development-kit). + +After all those steps, gitlab should be running on `localhost:3000` + + +## Integration + +Integration includes following components; + + - Single Sign On with OAuth from GitLab to Koding + - System Hook integration for handling GitLab events on Koding + (`project_created`, `user_joined` etc.) + - Service endpoints for importing/executing stacks from GitLab to Koding + (`Run/Try on IDE (Koding)` buttons on GitLab Projects, Issues, MRs) + +As it's pointed out before, you will need public access to this machine that +you've installed Koding and GitLab on. Better to use a domain but a static IP +is also fine. + +For IP based installation you can use [xip.io](https://xip.io) service which is +free and provides DNS resolution to IP based requests like following; + + - 127.0.0.1.xip.io -> resolves to 127.0.0.1 + - foo.bar.baz.127.0.0.1.xip.io -> resolves to 127.0.0.1 + - and so on... + +As Koding needs subdomains for team names; `foo.127.0.0.1.xip.io` requests for +a running koding instance on `127.0.0.1` server will be handled as `foo` team +requests. + + +### GitLab Side + +You need to enable Koding integration from Settings under Admin Area. To do +that login with an Admin account and do followings; + + - open [http://127.0.0.1:3000/admin/application_settings](http://127.0.0.1:3000/admin/application_settings) + - scroll to bottom of the page until Koding section + - check `Enable Koding` checkbox + - provide GitLab team page for running Koding instance as `Koding URL`* + +* For `Koding URL` you need to provide the gitlab integration enabled team on +your Koding installation. Team called `gitlab` has integration on Koding out +of the box, so if you didn't change anything your team on Koding should be +`gitlab`. + +So, if your Koding is running on `http://1.2.3.4.xip.io:8090` your URL needs +to be `http://gitlab.1.2.3.4.xip.io:8090`. You need to provide the same host +with your Koding installation here. + + +#### Registering Koding for OAuth integration + +We need `Application ID` and `Secret` to enable login to Koding via GitLab +feature and to do that you need to register running Koding as a new application +to your running GitLab application. Follow +[these](http://docs.gitlab.com/ce/integration/oauth_provider.html) steps to +enable this integration. + +Redirect URI should be `http://gitlab.127.0.0.1:8090/-/oauth/gitlab/callback` +which again you need to _replace `127.0.0.1` with your instance public IP._ + +Take a copy of `Application ID` and `Secret` that is generated by the GitLab +application, we will need those on _Koding Part_ of this guide. + + +#### Registering system hooks to Koding (optional) + +Koding can take actions based on the events generated by GitLab application. +This feature is still in progress and only following events are processed by +Koding at the moment; + + - user_create + - user_destroy + +All system events are handled but not implemented on Koding side. + +To enable this feature you need to provide a `URL` and a `Secret Token` to your +GitLab application. Open your admin area on your GitLab app from +[http://127.0.0.1:3000/admin/hooks](http://127.0.0.1:3000/admin/hooks) +and provide `URL` as `http://gitlab.127.0.0.1:8090/-/api/gitlab` which is the +endpoint to handle GitLab events on Koding side. Provide a `Secret Token` and +keep a copy of it, we will need it on _Koding Part_ of this guide. + +_(replace `127.0.0.1` with your instance public IP)_ + + +### Koding Part + +If you followed the steps in GitLab part we should have followings to enable +Koding part integrations; + + - `Application ID` and `Secret` for OAuth integration + - `Secret Token` for system hook integration + - Public address of running GitLab instance + + +#### Start Koding with GitLab URL + +Now we need to configure Koding with all this information to get things ready. +If it's already running please stop koding first. + +##### From command-line + +Replace followings with the ones you got from GitLab part of this guide; + +```bash +cd koding +docker-compose run \ + --service-ports backend \ + /opt/koding/scripts/bootstrap-container build \ + --host=**YOUR_IP**.xip.io \ + --gitlabHost=**GITLAB_IP** \ + --gitlabPort=**GITLAB_PORT** \ + --gitlabToken=**SECRET_TOKEN** \ + --gitlabAppId=**APPLICATION_ID** \ + --gitlabAppSecret=**SECRET** +``` + +##### By updating configuration + +Alternatively you can update `gitlab` section on +`config/credentials.default.coffee` like following; + +``` +gitlab = + host: '**GITLAB_IP**' + port: '**GITLAB_PORT**' + applicationId: '**APPLICATION_ID**' + applicationSecret: '**SECRET**' + team: 'gitlab' + redirectUri: '' + systemHookToken: '**SECRET_TOKEN**' + hooksEnabled: yes +``` + +and start by only providing the `host`; + +```bash +cd koding +docker-compose run \ + --service-ports backend \ + /opt/koding/scripts/bootstrap-container build \ + --host=**YOUR_IP**.xip.io \ +``` + +#### Enable Single Sign On + +Once you restarted your Koding and logged in with your username and password +you need to activate oauth authentication for your user. To do that + + - Navigate to Dashboard on Koding from; + `http://gitlab.**YOUR_IP**.xip.io:8090/Home/my-account` + - Scroll down to Integrations section + - Click on toggle to turn On integration in GitLab integration section + +This will redirect you to your GitLab instance and will ask your permission ( +if you are not logged in to GitLab at this point you will be redirected after +login) once you accept you will be redirected to your Koding instance. + +From now on you can login by using `SIGN IN WITH GITLAB` button on your Login +screen in your Koding instance. + +[ce-5909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5909 diff --git a/doc/integration/img/koding_build-in-progress.png b/doc/integration/img/koding_build-in-progress.png deleted file mode 100644 index f8cc81834c4..00000000000 Binary files a/doc/integration/img/koding_build-in-progress.png and /dev/null differ diff --git a/doc/integration/img/koding_build-logs.png b/doc/integration/img/koding_build-logs.png deleted file mode 100644 index a04cd5aff99..00000000000 Binary files a/doc/integration/img/koding_build-logs.png and /dev/null differ diff --git a/doc/integration/img/koding_build-success.png b/doc/integration/img/koding_build-success.png deleted file mode 100644 index 2a0dd296480..00000000000 Binary files a/doc/integration/img/koding_build-success.png and /dev/null differ diff --git a/doc/integration/img/koding_commit-koding.yml.png b/doc/integration/img/koding_commit-koding.yml.png deleted file mode 100644 index 3e133c50327..00000000000 Binary files a/doc/integration/img/koding_commit-koding.yml.png and /dev/null differ diff --git a/doc/integration/img/koding_different-stack-on-mr-try.png b/doc/integration/img/koding_different-stack-on-mr-try.png deleted file mode 100644 index fd25e32f648..00000000000 Binary files a/doc/integration/img/koding_different-stack-on-mr-try.png and /dev/null differ diff --git a/doc/integration/img/koding_edit-on-ide.png b/doc/integration/img/koding_edit-on-ide.png deleted file mode 100644 index fd5aaff75f5..00000000000 Binary files a/doc/integration/img/koding_edit-on-ide.png and /dev/null differ diff --git a/doc/integration/img/koding_enable-koding.png b/doc/integration/img/koding_enable-koding.png deleted file mode 100644 index c0ae0ee9918..00000000000 Binary files a/doc/integration/img/koding_enable-koding.png and /dev/null differ diff --git a/doc/integration/img/koding_landing.png b/doc/integration/img/koding_landing.png deleted file mode 100644 index 7c629d9b05e..00000000000 Binary files a/doc/integration/img/koding_landing.png and /dev/null differ diff --git a/doc/integration/img/koding_open-gitlab-from-koding.png b/doc/integration/img/koding_open-gitlab-from-koding.png deleted file mode 100644 index c958cf8f224..00000000000 Binary files a/doc/integration/img/koding_open-gitlab-from-koding.png and /dev/null differ diff --git a/doc/integration/img/koding_run-in-ide.png b/doc/integration/img/koding_run-in-ide.png deleted file mode 100644 index f91ee0f74cc..00000000000 Binary files a/doc/integration/img/koding_run-in-ide.png and /dev/null differ diff --git a/doc/integration/img/koding_run-mr-in-ide.png b/doc/integration/img/koding_run-mr-in-ide.png deleted file mode 100644 index 502817a2a46..00000000000 Binary files a/doc/integration/img/koding_run-mr-in-ide.png and /dev/null differ diff --git a/doc/integration/img/koding_set-up-ide.png b/doc/integration/img/koding_set-up-ide.png deleted file mode 100644 index 7f408c980b5..00000000000 Binary files a/doc/integration/img/koding_set-up-ide.png and /dev/null differ diff --git a/doc/integration/img/koding_stack-import.png b/doc/integration/img/koding_stack-import.png deleted file mode 100644 index 2a4e3c87fc8..00000000000 Binary files a/doc/integration/img/koding_stack-import.png and /dev/null differ diff --git a/doc/integration/img/koding_start-build.png b/doc/integration/img/koding_start-build.png deleted file mode 100644 index 52159440f62..00000000000 Binary files a/doc/integration/img/koding_start-build.png and /dev/null differ diff --git a/doc/integration/koding-usage.md b/doc/integration/koding-usage.md deleted file mode 100644 index bb74badce66..00000000000 --- a/doc/integration/koding-usage.md +++ /dev/null @@ -1,122 +0,0 @@ -# Koding & GitLab - -This document will guide you through using Koding integration on GitLab in -detail. For configuring and installing please follow [this](koding.md) guide. - -You can use Koding integration to run and develop your projects on GitLab. This -will allow you and the users to test your project without leaving the browser. -Koding handles projects as stacks which are basic recipes to define your -environment for your project. With this integration you can automatically -create a proper stack template for your projects. Currently auto-generated -stack templates are designed to work with AWS which requires a valid AWS -credential to be able to use these stacks. You can find more information about -stacks and the other providers that you can use on Koding from -[here](https://www.koding.com/docs). - - -# Enable Integration - -You can enable Koding integration by providing the running Koding instance URL -in Application Settings; - - - Open **Admin area > Settings** (`/admin/application_settings`). - -![Enable Koding](help/integration/img/koding_enable-koding.png) - -Once enabled you will see `Koding` link on your sidebar which leads you to -Koding Landing page - -![Koding Landing](help/integration/img/koding_landing.png) - -You can navigate to running Koding instance from here. For more information and -details about configuring integration please follow [this](koding.md) guide. - - -# Set up Koding on Projects - -Once it's enabled, you will see some integration buttons on Project pages, -Merge Requests etc. To get started working on a specific project you first need -to create a `.koding.yml` file under your project root. You can easily do that -by using `Set Up Koding` button which will be visible on every project's -landing page; - -![Set Up Koding](help/integration/img/koding_set-up-ide.png) - -Once you click this will open a New File page on GitLab with auto-generated -`.koding.yml` content based on your server and repository configuration. - -![Commit .koding.yml](help/integration/img/koding_commit-koding.yml.png) - - -# Run a project on Koding - -If there is `.koding.yml` exists in your project root, you will see -`Run in IDE (Koding)` button in your project landing page. You can initiate the -process from here. - -![Run on Koding](help/integration/img/koding_run-in-ide.png) - -This will open Koding defined in the settings in a new window and will start -importing the project's stack file; - -![Import Stack](help/integration/img/koding_stack-import.png) - -You should see the details of your repository imported into your Koding -instance. Once it's completed it will lead you to the Stack Editor and from -there you can start using your new stack integrated with your project on your -GitLab instance. For details about what's next you can follow -[this](https://www.koding.com/docs/creating-an-aws-stack) guide from 8. step. - -Once stack initialized you will see the `README.md` content from your project -in `Stack Build` wizard, this wizard will let you to build the stack and import -your project into it. **Once it's completed it will automatically open the -related vm instead of importing from scratch** - -![Stack Building](help/integration/img/koding_start-build.png) - -This will take time depending on the required environment. - -![Stack Building in Progress](help/integration/img/koding_build-in-progress.png) - -It usually takes ~4 min. to make it ready with a `t2.nano` instance on given -AWS region. (`t2.nano` is default vm type on auto-generated stack template -which can be manually changed) - -![Stack Building Success](help/integration/img/koding_build-success.png) - -You can check out the `Build Logs` from this success modal as well; - -![Stack Build Logs](help/integration/img/koding_build-logs.png) - -You can now `Start Coding`! - -![Edit On IDE](help/integration/img/koding_edit-on-ide.png) - - -# Try a Merge Request on IDE - -It's also possible to try a change on IDE before merging it. This flow only -enabled if the target project has `.koding.yml` in it's target branch. You -should see the alternative version of `Run in IDE (Koding)` button in merge -request pages as well; - -![Run in IDE on MR](help/integration/img/koding_run-mr-in-ide.png) - -This will again take you to Koding with proper arguments passed, which will -allow Koding to modify the stack template provided by target branch. You can -see the difference; - -![Different Branch for MR](help/integration/img/koding_different-stack-on-mr-try.png) - -The flow for the branch stack is also same with the regular project flow. - - -# Open GitLab from Koding - -Since stacks generated with import flow defined in previous steps, they have -information about the repository they are belonging to. By using this -information you can access to related GitLab page from stacks on your sidebar -on Koding. - -![Open GitLab from Koding](help/integration/img/koding_open-gitlab-from-koding.png) - diff --git a/doc/integration/koding.md b/doc/integration/koding.md deleted file mode 100644 index 53450b6d048..00000000000 --- a/doc/integration/koding.md +++ /dev/null @@ -1,239 +0,0 @@ -# Koding & GitLab - -This document will guide you through installing and configuring Koding with -GitLab. - -First of all, to be able to use Koding and GitLab together you will need public -access to your server. This allows you to use single sign-on from GitLab to -Koding and using vms from cloud providers like AWS. Koding has a registry for -VMs, called Kontrol and it runs on the same server as Koding itself, VMs from -cloud providers register themselves to Kontrol via the agent that we put into -provisioned VMs. This agent is called Klient and it provides Koding to access -and manage the target machine. - -Kontrol and Klient are based on another technology called -[Kite](github.com/koding/kite), that we have written at Koding. Which is a -microservice framework that allows you to develop microservices easily. - - -## Requirements - -### Hardware - -Minimum requirements are; - - - 2 cores CPU - - 3G RAM - - 10G Storage - -If you plan to use AWS to install Koding it is recommended that you use at -least a `c3.xlarge` instance. - -### Software - - - [git](https://git-scm.com) - - [docker](https://www.docker.com) - - [docker-compose](https://www.docker.com/products/docker-compose) - -Koding can run on most of the UNIX based operating systems, since it's shipped -as containerized with Docker support, it can work on any operating system that -supports Docker. - -Required services are; - - - PostgreSQL # Kontrol and Service DB provider - - MongoDB # Main DB provider the application - - Redis # In memory DB used by both application and services - - RabbitMQ # Message Queue for both application and services - -which are also provided as a Docker container by Koding. - - -## Getting Started with Development Versions - - -### Koding - -You can run `docker-compose` environment for developing koding by -executing commands in the following snippet. - -```bash -git clone https://github.com/koding/koding.git -cd koding -docker-compose up -``` - -This should start koding on `localhost:8090`. - -By default there is no team exists in Koding DB. You'll need to create a team -called `gitlab` which is the default team name for GitLab integration in the -configuration. To make things in order it's recommended to create the `gitlab` -team first thing after setting up Koding. - - -### GitLab - -To install GitLab to your environment for development purposes it's recommended -to use GitLab Development Kit which you can get it from -[here](https://gitlab.com/gitlab-org/gitlab-development-kit). - -After all those steps, gitlab should be running on `localhost:3000` - - -## Integration - -Integration includes following components; - - - Single Sign On with OAuth from GitLab to Koding - - System Hook integration for handling GitLab events on Koding - (`project_created`, `user_joined` etc.) - - Service endpoints for importing/executing stacks from GitLab to Koding - (`Run/Try on IDE (Koding)` buttons on GitLab Projects, Issues, MRs) - -As it's pointed out before, you will need public access to this machine that -you've installed Koding and GitLab on. Better to use a domain but a static IP -is also fine. - -For IP based installation you can use [xip.io](https://xip.io) service which is -free and provides DNS resolution to IP based requests like following; - - - 127.0.0.1.xip.io -> resolves to 127.0.0.1 - - foo.bar.baz.127.0.0.1.xip.io -> resolves to 127.0.0.1 - - and so on... - -As Koding needs subdomains for team names; `foo.127.0.0.1.xip.io` requests for -a running koding instance on `127.0.0.1` server will be handled as `foo` team -requests. - - -### GitLab Side - -You need to enable Koding integration from Settings under Admin Area. To do -that login with an Admin account and do followings; - - - open [http://127.0.0.1:3000/admin/application_settings](http://127.0.0.1:3000/admin/application_settings) - - scroll to bottom of the page until Koding section - - check `Enable Koding` checkbox - - provide GitLab team page for running Koding instance as `Koding URL`* - -* For `Koding URL` you need to provide the gitlab integration enabled team on -your Koding installation. Team called `gitlab` has integration on Koding out -of the box, so if you didn't change anything your team on Koding should be -`gitlab`. - -So, if your Koding is running on `http://1.2.3.4.xip.io:8090` your URL needs -to be `http://gitlab.1.2.3.4.xip.io:8090`. You need to provide the same host -with your Koding installation here. - - -#### Registering Koding for OAuth integration - -We need `Application ID` and `Secret` to enable login to Koding via GitLab -feature and to do that you need to register running Koding as a new application -to your running GitLab application. Follow -[these](http://docs.gitlab.com/ce/integration/oauth_provider.html) steps to -enable this integration. - -Redirect URI should be `http://gitlab.127.0.0.1:8090/-/oauth/gitlab/callback` -which again you need to _replace `127.0.0.1` with your instance public IP._ - -Take a copy of `Application ID` and `Secret` that is generated by the GitLab -application, we will need those on _Koding Part_ of this guide. - - -#### Registering system hooks to Koding (optional) - -Koding can take actions based on the events generated by GitLab application. -This feature is still in progress and only following events are processed by -Koding at the moment; - - - user_create - - user_destroy - -All system events are handled but not implemented on Koding side. - -To enable this feature you need to provide a `URL` and a `Secret Token` to your -GitLab application. Open your admin area on your GitLab app from -[http://127.0.0.1:3000/admin/hooks](http://127.0.0.1:3000/admin/hooks) -and provide `URL` as `http://gitlab.127.0.0.1:8090/-/api/gitlab` which is the -endpoint to handle GitLab events on Koding side. Provide a `Secret Token` and -keep a copy of it, we will need it on _Koding Part_ of this guide. - -_(replace `127.0.0.1` with your instance public IP)_ - - -### Koding Part - -If you followed the steps in GitLab part we should have followings to enable -Koding part integrations; - - - `Application ID` and `Secret` for OAuth integration - - `Secret Token` for system hook integration - - Public address of running GitLab instance - - -#### Start Koding with GitLab URL - -Now we need to configure Koding with all this information to get things ready. -If it's already running please stop koding first. - -##### From command-line - -Replace followings with the ones you got from GitLab part of this guide; - -```bash -cd koding -docker-compose run \ - --service-ports backend \ - /opt/koding/scripts/bootstrap-container build \ - --host=**YOUR_IP**.xip.io \ - --gitlabHost=**GITLAB_IP** \ - --gitlabPort=**GITLAB_PORT** \ - --gitlabToken=**SECRET_TOKEN** \ - --gitlabAppId=**APPLICATION_ID** \ - --gitlabAppSecret=**SECRET** -``` - -##### By updating configuration - -Alternatively you can update `gitlab` section on -`config/credentials.default.coffee` like following; - -``` -gitlab = - host: '**GITLAB_IP**' - port: '**GITLAB_PORT**' - applicationId: '**APPLICATION_ID**' - applicationSecret: '**SECRET**' - team: 'gitlab' - redirectUri: '' - systemHookToken: '**SECRET_TOKEN**' - hooksEnabled: yes -``` - -and start by only providing the `host`; - -```bash -cd koding -docker-compose run \ - --service-ports backend \ - /opt/koding/scripts/bootstrap-container build \ - --host=**YOUR_IP**.xip.io \ -``` - -#### Enable Single Sign On - -Once you restarted your Koding and logged in with your username and password -you need to activate oauth authentication for your user. To do that - - - Navigate to Dashboard on Koding from; - `http://gitlab.**YOUR_IP**.xip.io:8090/Home/my-account` - - Scroll down to Integrations section - - Click on toggle to turn On integration in GitLab integration section - -This will redirect you to your GitLab instance and will ask your permission ( -if you are not logged in to GitLab at this point you will be redirected after -login) once you accept you will be redirected to your Koding instance. - -From now on you can login by using `SIGN IN WITH GITLAB` button on your Login -screen in your Koding instance. diff --git a/doc/user/project/img/koding_build-in-progress.png b/doc/user/project/img/koding_build-in-progress.png new file mode 100644 index 00000000000..f8cc81834c4 Binary files /dev/null and b/doc/user/project/img/koding_build-in-progress.png differ diff --git a/doc/user/project/img/koding_build-logs.png b/doc/user/project/img/koding_build-logs.png new file mode 100644 index 00000000000..a04cd5aff99 Binary files /dev/null and b/doc/user/project/img/koding_build-logs.png differ diff --git a/doc/user/project/img/koding_build-success.png b/doc/user/project/img/koding_build-success.png new file mode 100644 index 00000000000..2a0dd296480 Binary files /dev/null and b/doc/user/project/img/koding_build-success.png differ diff --git a/doc/user/project/img/koding_commit-koding.yml.png b/doc/user/project/img/koding_commit-koding.yml.png new file mode 100644 index 00000000000..3e133c50327 Binary files /dev/null and b/doc/user/project/img/koding_commit-koding.yml.png differ diff --git a/doc/user/project/img/koding_different-stack-on-mr-try.png b/doc/user/project/img/koding_different-stack-on-mr-try.png new file mode 100644 index 00000000000..fd25e32f648 Binary files /dev/null and b/doc/user/project/img/koding_different-stack-on-mr-try.png differ diff --git a/doc/user/project/img/koding_edit-on-ide.png b/doc/user/project/img/koding_edit-on-ide.png new file mode 100644 index 00000000000..fd5aaff75f5 Binary files /dev/null and b/doc/user/project/img/koding_edit-on-ide.png differ diff --git a/doc/user/project/img/koding_enable-koding.png b/doc/user/project/img/koding_enable-koding.png new file mode 100644 index 00000000000..c0ae0ee9918 Binary files /dev/null and b/doc/user/project/img/koding_enable-koding.png differ diff --git a/doc/user/project/img/koding_landing.png b/doc/user/project/img/koding_landing.png new file mode 100644 index 00000000000..7c629d9b05e Binary files /dev/null and b/doc/user/project/img/koding_landing.png differ diff --git a/doc/user/project/img/koding_open-gitlab-from-koding.png b/doc/user/project/img/koding_open-gitlab-from-koding.png new file mode 100644 index 00000000000..c958cf8f224 Binary files /dev/null and b/doc/user/project/img/koding_open-gitlab-from-koding.png differ diff --git a/doc/user/project/img/koding_run-in-ide.png b/doc/user/project/img/koding_run-in-ide.png new file mode 100644 index 00000000000..f91ee0f74cc Binary files /dev/null and b/doc/user/project/img/koding_run-in-ide.png differ diff --git a/doc/user/project/img/koding_run-mr-in-ide.png b/doc/user/project/img/koding_run-mr-in-ide.png new file mode 100644 index 00000000000..502817a2a46 Binary files /dev/null and b/doc/user/project/img/koding_run-mr-in-ide.png differ diff --git a/doc/user/project/img/koding_set-up-ide.png b/doc/user/project/img/koding_set-up-ide.png new file mode 100644 index 00000000000..7f408c980b5 Binary files /dev/null and b/doc/user/project/img/koding_set-up-ide.png differ diff --git a/doc/user/project/img/koding_stack-import.png b/doc/user/project/img/koding_stack-import.png new file mode 100644 index 00000000000..2a4e3c87fc8 Binary files /dev/null and b/doc/user/project/img/koding_stack-import.png differ diff --git a/doc/user/project/img/koding_start-build.png b/doc/user/project/img/koding_start-build.png new file mode 100644 index 00000000000..52159440f62 Binary files /dev/null and b/doc/user/project/img/koding_start-build.png differ diff --git a/doc/user/project/koding.md b/doc/user/project/koding.md new file mode 100644 index 00000000000..e54587fab68 --- /dev/null +++ b/doc/user/project/koding.md @@ -0,0 +1,128 @@ +# Koding & GitLab + +> [Introduced][ce-5909] in GitLab 8.11. + +This document will guide you through using Koding integration on GitLab in +detail. For configuring and installing please follow the +[administrator guide](../../administration/integration/koding.md). + +You can use Koding integration to run and develop your projects on GitLab. This +will allow you and the users to test your project without leaving the browser. +Koding handles projects as stacks which are basic recipes to define your +environment for your project. With this integration you can automatically +create a proper stack template for your projects. Currently auto-generated +stack templates are designed to work with AWS which requires a valid AWS +credential to be able to use these stacks. You can find more information about +stacks and the other providers that you can use on Koding following the +[Koding documentation][koding-docs]. + +## Enable Integration + +You can enable Koding integration by providing the running Koding instance URL +in Application Settings under **Admin area > Settings** (`/admin/application_settings`). + +![Enable Koding](img/koding_enable-koding.png) + +Once enabled you will see `Koding` link on your sidebar which leads you to +Koding Landing page. + +![Koding Landing](img/koding_landing.png) + +You can navigate to running Koding instance from here. For more information and +details about configuring the integration, please follow the +[administrator guide](../../administration/integration/koding.md). + +## Set up Koding on Projects + +Once it's enabled, you will see some integration buttons on Project pages, +Merge Requests etc. To get started working on a specific project you first need +to create a `.koding.yml` file under your project root. You can easily do that +by using `Set Up Koding` button which will be visible on every project's +landing page; + +![Set Up Koding](img/koding_set-up-ide.png) + +Once you click this will open a New File page on GitLab with auto-generated +`.koding.yml` content based on your server and repository configuration. + +![Commit .koding.yml](img/koding_commit-koding.yml.png) + + +## Run a project on Koding + +If there is `.koding.yml` exists in your project root, you will see +`Run in IDE (Koding)` button in your project landing page. You can initiate the +process from here. + +![Run on Koding](img/koding_run-in-ide.png) + +This will open Koding defined in the settings in a new window and will start +importing the project's stack file. + +![Import Stack](img/koding_stack-import.png) + +You should see the details of your repository imported into your Koding +instance. Once it's completed it will lead you to the Stack Editor and from +there you can start using your new stack integrated with your project on your +GitLab instance. For details about what's next you can follow +[this guide](https://www.koding.com/docs/creating-an-aws-stack) from step 8. + +Once stack initialized you will see the `README.md` content from your project +in `Stack Build` wizard, this wizard will let you to build the stack and import +your project into it. **Once it's completed it will automatically open the +related vm instead of importing from scratch**. + +![Stack Building](img/koding_start-build.png) + +This will take time depending on the required environment. + +![Stack Building in Progress](img/koding_build-in-progress.png) + +It usually takes ~4 min. to make it ready with a `t2.nano` instance on given +AWS region. (`t2.nano` is default vm type on auto-generated stack template +which can be manually changed). + +![Stack Building Success](img/koding_build-success.png) + +You can check out the `Build Logs` from this success modal as well. + +![Stack Build Logs](img/koding_build-logs.png) + +You can now `Start Coding`! + +![Edit On IDE](img/koding_edit-on-ide.png) + +## Try a Merge Request on IDE + +It's also possible to try a change on IDE before merging it. This flow only +enabled if the target project has `.koding.yml` in it's target branch. You +should see the alternative version of `Run in IDE (Koding)` button in merge +request pages as well; + +![Run in IDE on MR](img/koding_run-mr-in-ide.png) + +This will again take you to Koding with proper arguments passed, which will +allow Koding to modify the stack template provided by target branch. You can +see the difference; + +![Different Branch for MR](img/koding_different-stack-on-mr-try.png) + +The flow for the branch stack is also same with the regular project flow. + +## Open GitLab from Koding + +Since stacks generated with import flow defined in previous steps, they have +information about the repository they are belonging to. By using this +information you can access to related GitLab page from stacks on your sidebar +on Koding. + +![Open GitLab from Koding](img/koding_open-gitlab-from-koding.png) + +## Other links + +- [YouTube video on GitLab + Koding workflow][youtube] +- [Koding documentation][koding-docs] + +[ce-5909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5909 +[youtube]: https://youtu.be/3wei5yv_Ye8 +[koding-docs]: https://www.koding.com/docs -- cgit v1.2.1 From 49c8530ea7c6d2d23ceca3cba78d62bc45b2c6ef Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 22 Aug 2016 17:36:04 +0300 Subject: Remove documentation rendering from /koding for now --- app/views/koding/index.html.haml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml index 111cc67336c..65887aacbaf 100644 --- a/app/views/koding/index.html.haml +++ b/app/views/koding/index.html.haml @@ -4,6 +4,3 @@ Integration is active for = link_to koding_project_url, target: '_blank' do #{current_application_settings.koding_url} - -.documentation.wiki - = markdown @markdown -- cgit v1.2.1 From 7b3fd81673e595b48b4c0e5f1ca5f1ab806f8ac3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 22 Aug 2016 18:21:28 +0300 Subject: Disable comments on older merge request diff Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/merge_requests_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 438dd928853..eff959c781c 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -93,6 +93,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html { define_discussion_vars } format.json do + if @merge_request_diff != @merge_request.merge_request_diff + # Disable comments if browsing older version of the diff + @diff_notes_disabled = true + end + @diffs = @merge_request_diff.diffs(diff_options) render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } -- cgit v1.2.1 From 8f9a7ca854ffda26c5ce9aed2aec10bf155d0463 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 1 Aug 2016 18:34:17 +0300 Subject: Revert the revert of Optimistic Locking --- CHANGELOG | 2 + app/controllers/projects/issues_controller.rb | 6 ++- .../projects/merge_requests_controller.rb | 5 +- app/models/concerns/issuable.rb | 6 +++ app/views/shared/issuable/_form.html.haml | 9 ++++ config/initializers/ar_monkey_patch.rb | 57 ++++++++++++++++++++++ db/migrate/20160707104333_add_lock_to_issuables.rb | 18 +++++++ db/schema.rb | 32 ++++++------ features/project/merge_requests.feature | 2 +- spec/features/issues_spec.rb | 11 +++++ spec/features/merge_requests/edit_mr_spec.rb | 11 +++++ 11 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 config/initializers/ar_monkey_patch.rb create mode 100644 db/migrate/20160707104333_add_lock_to_issuables.rb diff --git a/CHANGELOG b/CHANGELOG index c6a4fe5f5b8..0ea728cf89b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. +v 8.12.0 (unreleased) + - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) v 8.11.0 (unreleased) - Use test coverage value from the latest successful pipeline in badge. !5862 diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 639cf4c0ef2..7b0189150f8 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -125,6 +125,10 @@ class Projects::IssuesController < Projects::ApplicationController render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }) end end + + rescue ActiveRecord::StaleObjectError + @conflict = true + render :edit end def referenced_merge_requests @@ -230,7 +234,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue_params params.require(:issue).permit( :title, :assignee_id, :position, :description, :confidential, - :milestone_id, :due_date, :state_event, :task_num, label_ids: [] + :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [] ) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d3fe441c4d2..6a8c7166b39 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -258,6 +258,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController else render "edit" end + rescue ActiveRecord::StaleObjectError + @conflict = true + render :edit end def remove_wip @@ -493,7 +496,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :state_event, :description, :task_num, :force_remove_source_branch, - label_ids: [] + :lock_version, label_ids: [] ) end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index afb5ce37c06..8e11d4f57cf 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -87,6 +87,12 @@ module Issuable User.find(assignee_id_was).update_cache_counts if assignee_id_was assignee.update_cache_counts if assignee end + + # We want to use optimistic lock for cases when only title or description are involved + # http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html + def locking_enabled? + title_changed? || description_changed? + end end module ClassMethods diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 544ed6203aa..d8cfa1fca72 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -1,5 +1,12 @@ = form_errors(issuable) +- if @conflict + .alert.alert-danger + Someone edited the #{issuable.class.model_name.human.downcase} the same time you did. + Please check out + = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank" + and make sure your changes will not unintentionally remove theirs + .form-group = f.label :title, class: 'control-label' @@ -172,3 +179,5 @@ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' + += f.hidden_field :lock_version diff --git a/config/initializers/ar_monkey_patch.rb b/config/initializers/ar_monkey_patch.rb new file mode 100644 index 00000000000..0da584626ee --- /dev/null +++ b/config/initializers/ar_monkey_patch.rb @@ -0,0 +1,57 @@ +# rubocop:disable Lint/RescueException + +# This patch fixes https://github.com/rails/rails/issues/26024 +# TODO: Remove it when it's no longer necessary + +module ActiveRecord + module Locking + module Optimistic + # We overwrite this method because we don't want to have default value + # for newly created records + def _create_record(attribute_names = self.attribute_names, *) # :nodoc: + super + end + + def _update_record(attribute_names = self.attribute_names) #:nodoc: + return super unless locking_enabled? + return 0 if attribute_names.empty? + + lock_col = self.class.locking_column + + previous_lock_value = send(lock_col).to_i + + # This line is added as a patch + previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0 + + increment_lock + + attribute_names += [lock_col] + attribute_names.uniq! + + begin + relation = self.class.unscoped + + affected_rows = relation.where( + self.class.primary_key => id, + lock_col => previous_lock_value, + ).update_all( + attributes_for_update(attribute_names).map do |name| + [name, _read_attribute(name)] + end.to_h + ) + + unless affected_rows == 1 + raise ActiveRecord::StaleObjectError.new(self, "update") + end + + affected_rows + + # If something went wrong, revert the version. + rescue Exception + send(lock_col + '=', previous_lock_value) + raise + end + end + end + end +end diff --git a/db/migrate/20160707104333_add_lock_to_issuables.rb b/db/migrate/20160707104333_add_lock_to_issuables.rb new file mode 100644 index 00000000000..54866d02cbc --- /dev/null +++ b/db/migrate/20160707104333_add_lock_to_issuables.rb @@ -0,0 +1,18 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddLockToIssuables < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + add_column :issues, :lock_version, :integer + add_column :merge_requests, :lock_version, :integer + end + + def down + remove_column :issues, :lock_version + remove_column :merge_requests, :lock_version + end +end diff --git a/db/schema.rb b/db/schema.rb index 6cbc766831b..25cfb5de2fa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -84,8 +84,8 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.string "health_check_access_token" t.boolean "send_user_confirmation_email", default: false t.integer "container_registry_token_expire_delay", default: 5 - t.boolean "user_default_external", default: false, null: false t.text "after_sign_up_text" + t.boolean "user_default_external", default: false, null: false t.string "repository_storage", default: "default" t.string "enabled_git_access_protocol" t.boolean "domain_blacklist_enabled", default: false @@ -175,8 +175,8 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.string "environment" t.datetime "artifacts_expire_at" + t.string "environment" t.integer "artifacts_size" t.string "when" t.text "yaml_variables" @@ -471,6 +471,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.datetime "deleted_at" t.date "due_date" t.integer "moved_to_id" + t.integer "lock_version" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -613,12 +614,13 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.datetime "locked_at" t.integer "updated_by_id" t.string "merge_error" + t.text "merge_params" t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" t.string "in_progress_merge_commit_sha" - t.text "merge_params" + t.integer "lock_version" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -772,10 +774,10 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.integer "user_id", null: false t.string "token", null: false t.string "name", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.boolean "revoked", default: false t.datetime "expires_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree @@ -838,8 +840,8 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" t.string "repository_storage", default: "default", null: false - t.boolean "has_external_wiki" t.boolean "request_access_enabled", default: true, null: false + t.boolean "has_external_wiki" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree @@ -960,8 +962,8 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.string "noteable_type" t.string "title" t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "submitted_as_ham", default: false, null: false end @@ -1032,13 +1034,13 @@ ActiveRecord::Schema.define(version: 20160819221833) do add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree create_table "user_agent_details", force: :cascade do |t| - t.string "user_agent", null: false - t.string "ip_address", null: false - t.integer "subject_id", null: false - t.string "subject_type", null: false + t.string "user_agent", null: false + t.string "ip_address", null: false + t.integer "subject_id", null: false + t.string "subject_type", null: false t.boolean "submitted", default: false, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| @@ -1154,4 +1156,4 @@ ActiveRecord::Schema.define(version: 20160819221833) do add_foreign_key "protected_branch_merge_access_levels", "protected_branches" add_foreign_key "protected_branch_push_access_levels", "protected_branches" add_foreign_key "u2f_registrations", "users" -end \ No newline at end of file +end diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 6bac6011467..967f2edb243 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -89,7 +89,7 @@ Feature: Project Merge Requests Then The list should be sorted by "Oldest updated" @javascript - Scenario: Visiting Merge Requests from a differente Project after sorting + Scenario: Visiting Merge Requests from a different Project after sorting Given I visit project "Shop" merge requests page And I sort the list by "Oldest updated" And I visit dashboard merge requests page diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 2e595959f04..d744d0eb6af 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -122,6 +122,17 @@ describe 'Issues', feature: true do expect(page).to have_content date.to_s(:medium) end end + + it 'warns about version conflict' do + issue.update(title: "New title") + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + + click_button 'Save changes' + + expect(page).to have_content 'Someone edited the issue the same time you did' + end end end diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb index 4109e78f560..c77e719c5df 100644 --- a/spec/features/merge_requests/edit_mr_spec.rb +++ b/spec/features/merge_requests/edit_mr_spec.rb @@ -17,5 +17,16 @@ feature 'Edit Merge Request', feature: true do it 'has class js-quick-submit in form' do expect(page).to have_selector('.js-quick-submit') end + + it 'warns about version conflict' do + merge_request.update(title: "New title") + + fill_in 'merge_request_title', with: 'bug 345' + fill_in 'merge_request_description', with: 'bug description' + + click_button 'Save changes' + + expect(page).to have_content 'Someone edited the merge request the same time you did' + end end end -- cgit v1.2.1 From 21ddda75ac739a89565a2334fdfd77ed104d2fbe Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 22 Aug 2016 10:00:01 -0600 Subject: Add Ruby 2.3 upgrade notes. --- doc/update/8.10-to-8.11.md | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index 253fff50bdd..d423d6131b2 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -20,7 +20,31 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` -### 3. Get latest code +### 3. Update Ruby + +If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +``bash +mkdir /tmp/ruby && cd /tmp/ruby +curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz +echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum -c - && tar xzf ruby-2.3.1.tar.gz +cd ruby-2.3.1 +./configure --disable-install-rdoc +make +sudo make install +`` + +Install Bundler: + +``bash +sudo gem install bundler --no-ri --no-rdoc +`` + +### 4. Get latest code ```bash sudo -u git -H git fetch --all @@ -41,7 +65,7 @@ For GitLab Enterprise Edition: sudo -u git -H git checkout 8-11-stable-ee ``` -### 4. Update gitlab-shell +### 5. Update gitlab-shell ```bash cd /home/git/gitlab-shell @@ -49,7 +73,7 @@ sudo -u git -H git fetch --all --tags sudo -u git -H git checkout v3.4.0 ``` -### 5. Update gitlab-workhorse +### 6. Update gitlab-workhorse Install and compile gitlab-workhorse. This requires [Go 1.5](https://golang.org/dl) which should already be on your system from @@ -62,7 +86,7 @@ sudo -u git -H git checkout v0.7.8 sudo -u git -H make ``` -### 6. Install libs, migrations, etc. +### 7. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -84,7 +108,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ``` -### 7. Update configuration files +### 8. Update configuration files #### New configuration options for `gitlab.yml` @@ -133,12 +157,12 @@ Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -### 8. Start application +### 9. Start application sudo service gitlab start sudo service nginx restart -### 9. Check application status +### 10. Check application status Check if GitLab and its environment are configured correctly: -- cgit v1.2.1 From dc743a6e1f15c9a17411d2a18e89df30871272a7 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 22 Aug 2016 10:26:23 -0600 Subject: Appease the linter. --- doc/update/8.10-to-8.11.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index d423d6131b2..1c849ad59a9 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -30,8 +30,8 @@ Download and compile Ruby: ``bash mkdir /tmp/ruby && cd /tmp/ruby -curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz -echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum -c - && tar xzf ruby-2.3.1.tar.gz +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz +echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz cd ruby-2.3.1 ./configure --disable-install-rdoc make -- cgit v1.2.1 From b36b885cfd6cc931e701812492d51986797714b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila=20Santos?= Date: Mon, 22 Aug 2016 18:05:13 +0000 Subject: Fix for update 8.10-to-8.11.md doc. --- doc/update/8.10-to-8.11.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index 1c849ad59a9..98721763566 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -28,7 +28,7 @@ You can check which version you are running with `ruby -v`. Download and compile Ruby: -``bash +```bash mkdir /tmp/ruby && cd /tmp/ruby curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz @@ -36,13 +36,13 @@ cd ruby-2.3.1 ./configure --disable-install-rdoc make sudo make install -`` +``` Install Bundler: -``bash +```bash sudo gem install bundler --no-ri --no-rdoc -`` +``` ### 4. Get latest code -- cgit v1.2.1 From 4b1a98ba9847579db32da95ed479e2da098eb155 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 22 Aug 2016 21:15:06 +0300 Subject: Fix db schema via db:migrate:reset Signed-off-by: Dmitriy Zaporozhets --- db/schema.rb | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 4947745b232..77f114ede45 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -70,11 +70,11 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.string "recaptcha_site_key" t.string "recaptcha_private_key" t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" t.integer "metrics_sample_interval", default: 15 t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" t.boolean "repository_checks_enabled", default: false @@ -467,11 +467,10 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.integer "moved_to_id" t.boolean "confidential", default: false t.datetime "deleted_at" t.date "due_date" - t.integer "lock_version", default: 0, null: false + t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -614,13 +613,12 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.datetime "locked_at" t.integer "updated_by_id" t.string "merge_error" + t.text "merge_params" t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" - t.integer "lock_version", default: 0, null: false t.string "in_progress_merge_commit_sha" - t.text "merge_params" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -915,9 +913,9 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true @@ -962,8 +960,8 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.string "noteable_type" t.string "title" t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "submitted_as_ham", default: false, null: false end @@ -1034,13 +1032,13 @@ ActiveRecord::Schema.define(version: 20160819221833) do add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree create_table "user_agent_details", force: :cascade do |t| - t.string "user_agent", null: false - t.string "ip_address", null: false - t.integer "subject_id", null: false - t.string "subject_type", null: false + t.string "user_agent", null: false + t.string "ip_address", null: false + t.integer "subject_id", null: false + t.string "subject_type", null: false t.boolean "submitted", default: false, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| @@ -1156,4 +1154,4 @@ ActiveRecord::Schema.define(version: 20160819221833) do add_foreign_key "protected_branch_merge_access_levels", "protected_branches" add_foreign_key "protected_branch_push_access_levels", "protected_branches" add_foreign_key "u2f_registrations", "users" -end \ No newline at end of file +end -- cgit v1.2.1 From 03671c30df268c787420cb163beb41d208a3c9e7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 22 Aug 2016 21:17:17 +0300 Subject: Add changelog item for merge request versions feature Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c6a4fe5f5b8..88a2acd15d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ Please view this file on the master branch, on stable branches it's out of date. +v 8.12.0 (unreleased) + - Add merge request versions !5467 + v 8.11.0 (unreleased) - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 -- cgit v1.2.1 From eff3c35410e97d4238ef93816e5164884c8b1369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Mon, 22 Aug 2016 19:57:43 +0000 Subject: Update merge_requests.md with a simpler way to check out a merge request The previous way was put under the "By modifying `.git/config` for a given repository" section. The disadvantage is that one has to modify `.git/config` for each repository that one uses. The new way was put under the "By adding a git alias" section. One has to create this git alias once, and then it will work from any repository, without having to modify `.git/config` over and over again. --- doc/workflow/merge_requests.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index d2ec56e6504..12ffa916a8f 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -15,6 +15,21 @@ Please note that you need to have builds configured to enable this feature. ## Checkout merge requests locally +### By adding a git alias + +Add the following alias to your `~/.gitconfig`: +``` +[alias] + mr = !sh -c 'git fetch origin merge-requests/$1/head:mr-$1 && git checkout mr-$1' - +``` +Now you can check out a particular merge request from any repository (it will use the `origin` remote) by: +``` +$ git mr 5 +``` +This will fetch the merge request into a local `mr-5` branch and check it out. + +### By modifying `.git/config` for a given repository + Locate the section for your GitLab remote in the `.git/config` file. It looks like this: ``` -- cgit v1.2.1 From c2d0bb9b034fb11b352ae7336c0fe8a7bd4cba48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Mon, 22 Aug 2016 20:17:23 +0000 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c6a4fe5f5b8..e5da0b45f66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -163,6 +163,7 @@ v 8.11.0 (unreleased) - Eliminate unneeded calls to Repository#blob_at when listing commits with no path - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done + - Update merge_requests.md with a simpler way to check out a merge request. !5944 v 8.10.7 - Upgrade Hamlit to 2.6.1. !5873 -- cgit v1.2.1 From d4e44e90943b842de97df21946cf19a7ffecee83 Mon Sep 17 00:00:00 2001 From: John Northrup Date: Mon, 22 Aug 2016 15:25:05 -0500 Subject: fix location of markdown help location --- app/views/projects/notes/_hints.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml index f4212350c2d..6c14f48d41b 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -2,7 +2,7 @@ .comment-toolbar.clearfix .toolbar-text Styling with - = link_to 'Markdown', help_page_path('markdown/markdown'), target: '_blank', tabindex: -1 + = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1 - if supports_slash_commands and = link_to 'slash commands', help_page_path('user/project/slash_commands'), target: '_blank', tabindex: -1 -- cgit v1.2.1 From 7e6af85490e289ffcc868390654748009ecc67fa Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 22 Aug 2016 21:36:04 -0500 Subject: Also check if Akismet is enabled, before showing the `Submit as spam` button. --- app/models/concerns/spammable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index ce54fe5d3bf..1aa97debe42 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -23,7 +23,7 @@ module Spammable def submittable_as_spam? if user_agent_detail - user_agent_detail.submittable? + user_agent_detail.submittable? && current_application_settings.akismet_enabled else false end -- cgit v1.2.1 From f633f7db66d4ff42abbe764c21f9da4e49045f91 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 23 Aug 2016 10:00:38 +0200 Subject: 8.11 is released, long live 8.12 --- CHANGELOG | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0ea728cf89b..97bd6316b55 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) -v 8.11.0 (unreleased) +v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) diff --git a/VERSION b/VERSION index 542e7824102..8a5bbc87ef9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.11.0-pre +8.12.0-pre -- cgit v1.2.1 From e665444724cd5d213d2d2d37267c3ded08912296 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 23 Aug 2016 01:06:54 -0700 Subject: Fix incorrect "stopped impersonation" log message Closes #21015 --- app/controllers/admin/impersonations_controller.rb | 2 +- spec/controllers/admin/impersonations_controller_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb index 8be35f00a77..9433da02f64 100644 --- a/app/controllers/admin/impersonations_controller.rb +++ b/app/controllers/admin/impersonations_controller.rb @@ -7,7 +7,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController warden.set_user(impersonator, scope: :user) - Gitlab::AppLogger.info("User #{original_user.username} has stopped impersonating #{impersonator.username}") + Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{original_user.username}") session[:impersonator_id] = nil diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb index d5f0b289b5b..8be662974a0 100644 --- a/spec/controllers/admin/impersonations_controller_spec.rb +++ b/spec/controllers/admin/impersonations_controller_spec.rb @@ -77,6 +77,8 @@ describe Admin::ImpersonationsController do context "when the impersonator is not blocked" do it "redirects to the impersonated user's page" do + expect(Gitlab::AppLogger).to receive(:info).with("User #{impersonator.username} has stopped impersonating #{user.username}").and_call_original + delete :destroy expect(response).to redirect_to(admin_user_path(user)) -- cgit v1.2.1 From 2c909a5a3c219cad6e6ef8c78a4984ab311d14cb Mon Sep 17 00:00:00 2001 From: Bryce Date: Tue, 23 Aug 2016 10:39:31 +0200 Subject: Check for pipelines when setting page_gutter_class. --- CHANGELOG | 1 + app/helpers/nav_helper.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0ea728cf89b..5740ba89791 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ v 8.12.0 (unreleased) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) v 8.11.0 (unreleased) + - Fix pipelines tab layout regression (brycepj) - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 6c1cc6ef072..2b0ff6c0d00 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -25,6 +25,8 @@ module NavHelper current_path?('merge_requests#commits') || current_path?('merge_requests#builds') || current_path?('merge_requests#conflicts') || + current_path?('merge_requests#pipelines') || + current_path?('issues#show') if cookies[:collapsed_gutter] == 'true' "page-gutter right-sidebar-collapsed" -- cgit v1.2.1 From 1a7c293c2ddbe960accec355a0ed399de82e0c41 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 22 Aug 2016 17:19:39 +0100 Subject: Fix file links on project page Files view --- CHANGELOG | 6 ++++-- app/controllers/projects_controller.rb | 9 ++++++++- spec/features/projects_spec.rb | 29 +++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 97bd6316b55..c361880ff18 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,11 @@ Please view this file on the master branch, on stable branches it's out of date. + v 8.12.0 (unreleased) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) +v 8.11.1 (unreleased) + - Fix file links on project page when default view is Files !5933 + v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 @@ -198,8 +202,6 @@ v 8.10.3 - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 - Fix label already exist error message in the right sidebar. -v 8.10.3 (unreleased) - v 8.10.2 - User can now search branches by name. !5144 - Page is now properly rendered after committing the first file and creating the first branch. !5399 diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index fc52cd2f367..2a6385c1029 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] before_action :assign_ref_vars, only: [:show], if: :repo_exists? - before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?] + before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] @@ -332,4 +332,11 @@ class ProjectsController < Projects::ApplicationController def get_id project.repository.root_ref end + + # ExtractsPath will set @id = project.path on the show route, but it has to be the + # branch name for the tree view to work correctly. + def assign_tree_vars + @id = get_id + tree + end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 1b14c66fe28..e00d85904d5 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -115,6 +115,35 @@ feature 'Project', feature: true do end end + describe 'tree view (default view is set to Files)' do + let(:user) { create(:user, project_view: 'files') } + let(:project) { create(:forked_project_with_submodules) } + + before do + project.team << [user, :master] + login_as user + visit namespace_project_path(project.namespace, project) + end + + it 'has working links to files' do + click_link('PROCESS.md') + + expect(page.status_code).to eq(200) + end + + it 'has working links to directories' do + click_link('encoding') + + expect(page.status_code).to eq(200) + end + + it 'has working links to submodules' do + click_link('645f6c4c') + + expect(page.status_code).to eq(200) + end + end + def remove_with_confirm(button_text, confirm_with) click_button button_text fill_in 'confirm_name_input', with: confirm_with -- cgit v1.2.1 From 4b40767c0e4899188bb86edac1b05ab9f7403d4f Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Tue, 23 Aug 2016 11:28:11 +0100 Subject: First pass for community issue templates * Include "Bug" template from the contribution guide * Include "Feature Proposal" template from the contribution guide --- .gitlab/issue_templates/Bug.md | 40 +++++++++++++++++++++++++++++ .gitlab/issue_templates/Feature Proposal.md | 6 +++++ 2 files changed, 46 insertions(+) create mode 100644 .gitlab/issue_templates/Bug.md create mode 100644 .gitlab/issue_templates/Feature Proposal.md diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md new file mode 100644 index 00000000000..139d97034a2 --- /dev/null +++ b/.gitlab/issue_templates/Bug.md @@ -0,0 +1,40 @@ +## Summary + +(Summarize your issue in one sentence - what goes wrong, what did you expect to happen) + +## Steps to reproduce + +(How one can reproduce the issue - this is very important) + +## Expected behavior + +(What you should see instead) + +## Relevant logs and/or screenshots + +(Paste any relevant logs - please use code blocks (```) to format console output, +logs, and code as it's very hard to read otherwise.) + +## Output of checks + +### Results of GitLab Application Check + +(For installations with omnibus-gitlab package run and paste the output of: +sudo gitlab-rake gitlab:check SANITIZE=true) + +(For installations from source run and paste the output of: +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true) + +(we will only investigate if the tests are passing) + +### Results of GitLab Environment Info + +(For installations with omnibus-gitlab package run and paste the output of: +sudo gitlab-rake gitlab:env:info) + +(For installations from source run and paste the output of: +sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production) + +## Possible fixes + +(If you can, link to the line of code that might be responsible for the problem) diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md new file mode 100644 index 00000000000..ea009c32296 --- /dev/null +++ b/.gitlab/issue_templates/Feature Proposal.md @@ -0,0 +1,6 @@ +## Description +Include problem, use cases, benefits, and/or goals + +## Proposal + +## Links / references -- cgit v1.2.1 From 5963263912c1633ff49ce758f71589bae8133e57 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 19 Aug 2016 09:45:58 +0100 Subject: Fixed enter key in search input not working Closes #20627 --- app/assets/javascripts/gl_dropdown.js | 28 ++++++++++++++++++---------- spec/features/search_spec.rb | 10 ++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 24abea0d30d..b1f5a6816e1 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -31,8 +31,8 @@ this.input .on('keydown', function (e) { var keyCode = e.which; - if (keyCode === 13) { - e.preventDefault(); + if (keyCode === 13 && !options.elIsInput) { + e.preventDefault() } }) .on('keyup', function(e) { @@ -46,7 +46,7 @@ } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.removeClass(HAS_VALUE_CLASS); } - if (keyCode === 13) { + if (keyCode === 13 && !options.elIsInput) { return false; } if (this.options.remote) { @@ -238,6 +238,7 @@ } if (this.options.filterable) { this.filter = new GitLabDropdownFilter(this.filterInput, { + elIsInput: $(this.el).is('input'), filterInputBlur: this.filterInputBlur, filterByText: this.options.filterByText, onFilter: this.options.onFilter, @@ -266,8 +267,12 @@ if (_this.dropdown.find('.dropdown-toggle-page').length) { selector = ".dropdown-page-one " + selector; } - $(selector, _this.dropdown).first().find('a').addClass('is-focused'); - return currentIndex = 0; + if ($(_this.el).is('input')) { + currentIndex = -1; + } else { + $(selector, _this.dropdown).first().find('a').addClass('is-focused'); + currentIndex = 0; + } } }; })(this) @@ -611,17 +616,20 @@ return this.dropdown.before($input); }; - GitLabDropdown.prototype.selectRowAtIndex = function(index) { + GitLabDropdown.prototype.selectRowAtIndex = function() { var $el, selector; - selector = SELECTABLE_CLASSES + ":eq(" + index + ") a"; + selector = ".dropdown-content .is-focused"; if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one " + selector; } $el = $(selector, this.dropdown); if ($el.length) { - $el.first().trigger('click'); var href = $el.attr('href'); - if (href && href !== '#') Turbolinks.visit(href); + if (href && href !== '#') { + Turbolinks.visit(href); + } else { + $el.first().trigger('click'); + } } }; @@ -657,7 +665,7 @@ return false; } if (currentKeyCode === 13 && currentIndex !== -1) { - return _this.selectRowAtIndex(currentIndex); + _this.selectRowAtIndex(); } }; })(this)); diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index b7a25d80fec..dcd3a2f17b0 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -71,6 +71,16 @@ describe "Search", feature: true do end describe 'Right header search field', feature: true do + it 'allows enter key to search', js: true do + visit namespace_project_path(project.namespace, project) + fill_in 'search', with: 'gitlab' + find('#search').native.send_keys(:enter) + + page.within '.title' do + expect(page).to have_content 'Search' + end + end + describe 'Search in project page' do before do visit namespace_project_path(project.namespace, project) -- cgit v1.2.1 From 86f03e175042afe551cf95c195c043fa92b58ea7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 22 Aug 2016 11:41:51 -0500 Subject: Allow passing an index to selectRowAtIndex --- app/assets/javascripts/gl_dropdown.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index b1f5a6816e1..0179b320a3b 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -616,9 +616,14 @@ return this.dropdown.before($input); }; - GitLabDropdown.prototype.selectRowAtIndex = function() { + GitLabDropdown.prototype.selectRowAtIndex = function(index) { var $el, selector; - selector = ".dropdown-content .is-focused"; + // If we pass an option index + if (typeof index !== "undefined") { + selector = SELECTABLE_CLASSES + ":eq(" + index + ") a"; + } else { + selector = ".dropdown-content .is-focused"; + } if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one " + selector; } -- cgit v1.2.1 From cc94714c05a5088db7e30b9582a187e5ee52068b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 23 Aug 2016 08:45:18 -0500 Subject: Add body as data-container on pipeline graph tooltip --- app/helpers/ci_status_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index bb285a17baf..0327b476d18 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -66,10 +66,10 @@ module CiStatusHelper Ci::Runner.shared.blank? end - def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '') + def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body') klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" title = "#{type.titleize}: #{ci_label_for_status(status)}" - data = { toggle: 'tooltip', placement: tooltip_placement } + data = { toggle: 'tooltip', placement: tooltip_placement, container: container } if path link_to ci_icon_for_status(status), path, -- cgit v1.2.1 From 7d654c0e1810f2023a1deaeddd542c5c92d20f42 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 5 Aug 2016 13:02:08 +0100 Subject: Added addtional 'renderable' validator to check 'data-note-type' attr exists --- app/assets/javascripts/files_comment_button.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index b2e49b71fec..817d38ea86e 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -39,12 +39,13 @@ FilesCommentButton.prototype.render = function(e) { var $currentTarget, buttonParentElement, lineContentElement, textFileElement; $currentTarget = $(e.currentTarget); + buttonParentElement = this.getButtonParent($currentTarget); - if (!this.shouldRender(e, buttonParentElement)) { - return; - } - textFileElement = this.getTextFileElement($currentTarget); + if (!this.validateButtonParent(buttonParentElement)) return; lineContentElement = this.getLineContent($currentTarget); + if (!this.validateLineContent(lineContentElement)) return; + + textFileElement = this.getTextFileElement($currentTarget); buttonParentElement.append(this.buildButton({ noteableType: textFileElement.attr('data-noteable-type'), noteableID: textFileElement.attr('data-noteable-id'), @@ -119,10 +120,14 @@ return newButtonParent.is(this.getButtonParent($(e.currentTarget))); }; - FilesCommentButton.prototype.shouldRender = function(e, buttonParentElement) { + FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) { return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0; }; + FilesCommentButton.prototype.validateLineContent = function(lineContentElement) { + return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== ''; + }; + return FilesCommentButton; })(); -- cgit v1.2.1 From 66a6dfbc5326418ce5b5e2c72bc8aca11890c601 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 5 Aug 2016 14:02:10 +0100 Subject: Added 'with an unfolded line should not allow commenting' scenario (line 125) --- spec/features/merge_requests/diff_notes_spec.rb | 153 ++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb index b8f82d06e19..f5bc0543661 100644 --- a/spec/features/merge_requests/diff_notes_spec.rb +++ b/spec/features/merge_requests/diff_notes_spec.rb @@ -17,6 +17,7 @@ feature 'Diff notes', js: true, feature: true do context 'when hovering over the parallel view diff file' do before(:each) do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel') end @@ -27,51 +28,111 @@ feature 'Diff notes', js: true, feature: true do it 'should not allow commenting on the right side' do should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right') +======= + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + click_link 'Side-by-side' + end + + context 'with an old line on the left and no line on the right' do + let(:line_holder) { find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..') } + + it 'should allow commenting on the left side' do + should_allow_commenting line_holder, 'left' + end + + it 'should not allow commenting on the right side' do + should_not_allow_commenting line_holder, 'right' +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with no line on the left and a new line on the right' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should not allow commenting on the left side' do should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left') end it 'should allow commenting on the right side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right') +======= + let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..') } + + it 'should not allow commenting on the left side' do + should_not_allow_commenting line_holder, 'left' + end + + it 'should allow commenting on the right side' do + should_allow_commenting line_holder, 'right' +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an old line on the left and a new line on the right' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting on the left side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left') end it 'should allow commenting on the right side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right') +======= + let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..') } + + it 'should allow commenting on the left side' do + should_allow_commenting line_holder, 'left' + end + + it 'should allow commenting on the right side' do + should_allow_commenting line_holder, 'right' +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an unchanged line on the left and an unchanged line on the right' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting on the left side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left') end it 'should allow commenting on the right side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right') +======= + let(:line_holder) { first('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]').find(:xpath, '..') } + + it 'should allow commenting on the left side' do + should_allow_commenting line_holder, 'left' + end + + it 'should allow commenting on the right side' do + should_allow_commenting line_holder, 'right' +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with a match line' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should not allow commenting on the left side' do should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left') end it 'should not allow commenting on the right side' do should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right') +======= + let(:line_holder) { first('.match').find(:xpath, '..') } + + it 'should not allow commenting on the left side' do + should_not_allow_commenting line_holder, 'left' + end + + it 'should not allow commenting on the right side' do + should_not_allow_commenting line_holder, 'right' +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end end context 'when hovering over the inline view diff file' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 before do visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') end @@ -79,29 +140,80 @@ feature 'Diff notes', js: true, feature: true do context 'with a new line' do it 'should allow commenting' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) +======= + let(:comment_button_class) { '.add-diff-note' } + + before(:each) do + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + click_link 'Inline' + end + + context 'with a new line' do + let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]') } + + it 'should allow commenting' do + should_allow_commenting line_holder +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an old line' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting' do should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) +======= + let(:line_holder) { find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]') } + + it 'should allow commenting' do + should_allow_commenting line_holder +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an unchanged line' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) +======= + let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]') } + + it 'should allow commenting' do + should_allow_commenting line_holder +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with a match line' do +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should not allow commenting' do should_not_allow_commenting(find('.match', match: :first)) +======= + let(:line_holder) { first('.match') } + + it 'should not allow commenting' do + should_not_allow_commenting line_holder + end + end + + context 'with an unfolded line' do + before(:each) do + first('.js-unfold').click + wait_for_ajax + end + + # The first `.js-unfold` unfolds upwards, therefore the first + # `.line_holder` will be an unfolded line. + let(:line_holder) { first('.line_holder[id="1"]') } + + it 'should not allow commenting' do + should_not_allow_commenting line_holder +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end end def should_allow_commenting(line_holder, diff_side = nil) +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 line = get_line_components(line_holder, diff_side) line[:content].hover expect(line[:num]).to have_css comment_button_class @@ -113,33 +225,61 @@ feature 'Diff notes', js: true, feature: true do def should_not_allow_commenting(line_holder, diff_side = nil) line = get_line_components(line_holder, diff_side) +======= + line = get_line_components line_holder, diff_side + line[:content].hover + expect(line[:num]).to have_css comment_button_class + + comment_on_line line_holder, line + wait_for_ajax + + assert_comment_persistence line_holder + end + + def should_not_allow_commenting(line_holder, diff_side = nil) + line = get_line_components line_holder, diff_side +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) line[:content].hover expect(line[:num]).not_to have_css comment_button_class end def get_line_components(line_holder, diff_side = nil) if diff_side.nil? +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 get_inline_line_components(line_holder) else get_parallel_line_components(line_holder, diff_side) +======= + get_inline_line_components line_holder + else + get_parallel_line_components line_holder, diff_side +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end def get_inline_line_components(line_holder) +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) } +======= + { content: line_holder.first('.line_content'), num: line_holder.first('.diff-line-num') } +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end def get_parallel_line_components(line_holder, diff_side = nil) side_index = diff_side == 'left' ? 0 : 1 +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 # Wait for `.line_content` line_holder.find('.line_content', match: :first) # Wait for `.diff-line-num` line_holder.find('.diff-line-num', match: :first) +======= +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } end def comment_on_line(line_holder, line) line[:num].find(comment_button_class).trigger 'click' +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 line_holder.find(:xpath, notes_holder_input_xpath) notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath) @@ -148,13 +288,26 @@ feature 'Diff notes', js: true, feature: true do notes_holder_input.fill_in 'note[note]', with: test_note_comment click_button 'Comment' wait_for_ajax +======= + expect(line_holder).to have_xpath notes_holder_input_xpath + + notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath) + expect(notes_holder_input[:class].include? notes_holder_input_class).to be true + + notes_holder_input.fill_in 'note[note]', with: test_note_comment + click_button 'Comment' +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end def assert_comment_persistence(line_holder) expect(line_holder).to have_xpath notes_holder_input_xpath notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) +<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) +======= + expect(notes_holder_saved[:class].include? notes_holder_input_class).to be false +>>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) expect(notes_holder_saved).to have_content test_note_comment end end -- cgit v1.2.1 From ed94b9b4c755c863bb90914c72cf20318fefe58a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 22 Aug 2016 14:21:18 +0100 Subject: Updated to optimized specs from !5864 --- spec/features/merge_requests/diff_notes_spec.rb | 138 ------------------------ 1 file changed, 138 deletions(-) diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb index f5bc0543661..482c280c188 100644 --- a/spec/features/merge_requests/diff_notes_spec.rb +++ b/spec/features/merge_requests/diff_notes_spec.rb @@ -17,7 +17,6 @@ feature 'Diff notes', js: true, feature: true do context 'when hovering over the parallel view diff file' do before(:each) do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel') end @@ -28,111 +27,51 @@ feature 'Diff notes', js: true, feature: true do it 'should not allow commenting on the right side' do should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right') -======= - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) - click_link 'Side-by-side' - end - - context 'with an old line on the left and no line on the right' do - let(:line_holder) { find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..') } - - it 'should allow commenting on the left side' do - should_allow_commenting line_holder, 'left' - end - - it 'should not allow commenting on the right side' do - should_not_allow_commenting line_holder, 'right' ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with no line on the left and a new line on the right' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should not allow commenting on the left side' do should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left') end it 'should allow commenting on the right side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right') -======= - let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..') } - - it 'should not allow commenting on the left side' do - should_not_allow_commenting line_holder, 'left' - end - - it 'should allow commenting on the right side' do - should_allow_commenting line_holder, 'right' ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an old line on the left and a new line on the right' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting on the left side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left') end it 'should allow commenting on the right side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right') -======= - let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..') } - - it 'should allow commenting on the left side' do - should_allow_commenting line_holder, 'left' - end - - it 'should allow commenting on the right side' do - should_allow_commenting line_holder, 'right' ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an unchanged line on the left and an unchanged line on the right' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting on the left side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left') end it 'should allow commenting on the right side' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'right') -======= - let(:line_holder) { first('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]').find(:xpath, '..') } - - it 'should allow commenting on the left side' do - should_allow_commenting line_holder, 'left' - end - - it 'should allow commenting on the right side' do - should_allow_commenting line_holder, 'right' ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with a match line' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should not allow commenting on the left side' do should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'left') end it 'should not allow commenting on the right side' do should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right') -======= - let(:line_holder) { first('.match').find(:xpath, '..') } - - it 'should not allow commenting on the left side' do - should_not_allow_commenting line_holder, 'left' - end - - it 'should not allow commenting on the right side' do - should_not_allow_commenting line_holder, 'right' ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end end context 'when hovering over the inline view diff file' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 before do visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') end @@ -140,58 +79,24 @@ feature 'Diff notes', js: true, feature: true do context 'with a new line' do it 'should allow commenting' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) -======= - let(:comment_button_class) { '.add-diff-note' } - - before(:each) do - visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) - click_link 'Inline' - end - - context 'with a new line' do - let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]') } - - it 'should allow commenting' do - should_allow_commenting line_holder ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an old line' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting' do should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) -======= - let(:line_holder) { find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]') } - - it 'should allow commenting' do - should_allow_commenting line_holder ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with an unchanged line' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should allow commenting' do should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) -======= - let(:line_holder) { find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]') } - - it 'should allow commenting' do - should_allow_commenting line_holder ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end context 'with a match line' do -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 it 'should not allow commenting' do should_not_allow_commenting(find('.match', match: :first)) -======= - let(:line_holder) { first('.match') } - - it 'should not allow commenting' do - should_not_allow_commenting line_holder end end @@ -207,13 +112,11 @@ feature 'Diff notes', js: true, feature: true do it 'should not allow commenting' do should_not_allow_commenting line_holder ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end end def should_allow_commenting(line_holder, diff_side = nil) -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 line = get_line_components(line_holder, diff_side) line[:content].hover expect(line[:num]).to have_css comment_button_class @@ -225,61 +128,33 @@ feature 'Diff notes', js: true, feature: true do def should_not_allow_commenting(line_holder, diff_side = nil) line = get_line_components(line_holder, diff_side) -======= - line = get_line_components line_holder, diff_side - line[:content].hover - expect(line[:num]).to have_css comment_button_class - - comment_on_line line_holder, line - wait_for_ajax - - assert_comment_persistence line_holder - end - - def should_not_allow_commenting(line_holder, diff_side = nil) - line = get_line_components line_holder, diff_side ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) line[:content].hover expect(line[:num]).not_to have_css comment_button_class end def get_line_components(line_holder, diff_side = nil) if diff_side.nil? -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 get_inline_line_components(line_holder) else get_parallel_line_components(line_holder, diff_side) -======= - get_inline_line_components line_holder - else - get_parallel_line_components line_holder, diff_side ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end end def get_inline_line_components(line_holder) -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 { content: line_holder.find('.line_content', match: :first), num: line_holder.find('.diff-line-num', match: :first) } -======= - { content: line_holder.first('.line_content'), num: line_holder.first('.diff-line-num') } ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end def get_parallel_line_components(line_holder, diff_side = nil) side_index = diff_side == 'left' ? 0 : 1 -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 # Wait for `.line_content` line_holder.find('.line_content', match: :first) # Wait for `.diff-line-num` line_holder.find('.diff-line-num', match: :first) -======= ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] } end def comment_on_line(line_holder, line) line[:num].find(comment_button_class).trigger 'click' -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 line_holder.find(:xpath, notes_holder_input_xpath) notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath) @@ -288,26 +163,13 @@ feature 'Diff notes', js: true, feature: true do notes_holder_input.fill_in 'note[note]', with: test_note_comment click_button 'Comment' wait_for_ajax -======= - expect(line_holder).to have_xpath notes_holder_input_xpath - - notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath) - expect(notes_holder_input[:class].include? notes_holder_input_class).to be true - - notes_holder_input.fill_in 'note[note]', with: test_note_comment - click_button 'Comment' ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) end def assert_comment_persistence(line_holder) expect(line_holder).to have_xpath notes_holder_input_xpath notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) -<<<<<<< 7d654c0e1810f2023a1deaeddd542c5c92d20f42 expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) -======= - expect(notes_holder_saved[:class].include? notes_holder_input_class).to be false ->>>>>>> Added 'with an unfolded line should not allow commenting' scenario (line 125) expect(notes_holder_saved).to have_content test_note_comment end end -- cgit v1.2.1 From 84d2cab648d06cdeb8ace31727bb16abe9ef67c1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sat, 20 Aug 2016 17:21:59 +0100 Subject: Moved builds panel to the bottom of the builds sidemenu --- app/assets/stylesheets/pages/builds.scss | 3 +- app/views/projects/builds/_sidebar.html.haml | 69 ++++++++++++++-------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index c1bb250b42d..1af4e388386 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -131,7 +131,7 @@ } .build-dropdown { - padding: 0 $gl-padding; + padding: $gl-padding 0; .dropdown-menu-toggle { margin-top: 8px; @@ -145,7 +145,6 @@ } .builds-container { - margin-top: $gl-padding; background-color: $white-light; border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 5b0b58e087b..ab66f757a55 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -1,3 +1,6 @@ +- builds = @build.pipeline.builds.latest.to_a +- statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] + %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default Build @@ -11,40 +14,6 @@ %p.build-detail-row #{@build.coverage}% - - builds = @build.pipeline.builds.latest.to_a - - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] - - if builds.size > 1 - .dropdown.build-dropdown - .build-light-text Stage - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} - %span.stage-selection More - = icon('caret-down') - %ul.dropdown-menu - - builds.map(&:stage).uniq.each do |stage| - %li - %a.stage-item= stage - - .builds-container - - statuses.each do |build_status| - - builds.select{|build| build.status == build_status}.each do |build| - .build-job{class: ('active' if build == @build), data: {stage: build.stage}} - = link_to namespace_project_build_path(@project.namespace, @project, build) do - = icon('check') - = ci_icon_for_status(build.status) - %span - - if build.name - = build.name - - else - = build.id - - - if @build.retried? - %li.active - %a - Build ##{@build.id} - · - %i.fa.fa-warning - This build was retried. - .blocks-container - if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?) .block{ class: ("block-first" if !@build.coverage) } @@ -141,3 +110,35 @@ - @build.tag_list.each do |tag| %span.label.label-primary = tag + + - if builds.size > 1 + .dropdown.build-dropdown + .title Stage + %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %span.stage-selection More + = icon('caret-down') + %ul.dropdown-menu + - builds.map(&:stage).uniq.each do |stage| + %li + %a.stage-item= stage + + .builds-container + - statuses.each do |build_status| + - builds.select{|build| build.status == build_status}.each do |build| + .build-job{class: ('active' if build == @build), data: {stage: build.stage}} + = link_to namespace_project_build_path(@project.namespace, @project, build) do + = icon('check') + = ci_icon_for_status(build.status) + %span + - if build.name + = build.name + - else + = build.id + + - if @build.retried? + %li.active + %a + Build ##{@build.id} + · + %i.fa.fa-warning + This build was retried. -- cgit v1.2.1 From 8a07b45d64f4e96fd39d51edd8b921c4515edc1f Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sat, 20 Aug 2016 17:47:58 +0100 Subject: Changed `retry` link to blue --- app/assets/stylesheets/pages/builds.scss | 7 +++++++ app/views/projects/builds/_sidebar.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 1af4e388386..87e195018b9 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -122,6 +122,13 @@ } } + .retry-link { + color: $gl-link-color; + &:hover { + text-decoration: underline; + } + } + .stage-item { cursor: pointer; diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index ab66f757a55..c35bfe703e8 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -45,7 +45,7 @@ .title Build details - if can?(current_user, :update_build, @build) && @build.retryable? - = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post + = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post - if @build.merge_request %p.build-detail-row %span.build-light-text Merge Request: -- cgit v1.2.1 From c369738e78570d4c951765728c3c5510f0d68b68 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 23 Aug 2016 15:19:36 +0100 Subject: Review changes --- app/assets/stylesheets/pages/builds.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 87e195018b9..10dfae74d96 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -107,7 +107,7 @@ } .blocks-container { - padding: $gl-padding; + padding: 0 $gl-padding; } .block { -- cgit v1.2.1 From 542be288d6bf4fbef958281e08bfded773c47561 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 10:25:17 -0400 Subject: entities: make Environment inherit EnvironmentBasic --- lib/api/entities.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 54ce2dcfa57..66b85ab1793 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -514,13 +514,12 @@ module API expose :duration end - class Environment < Grape::Entity + class EnvironmentBasic < Grape::Entity expose :id, :name, :external_url - expose :project, using: Entities::Project end - class EnvironmentBasic < Grape::Entity - expose :id, :name, :external_url + class Environment < EnvironmentBasic + expose :project, using: Entities::Project end class Deployment < Grape::Entity -- cgit v1.2.1 From 453bf1543e56638117dd73f952be7f7fda285dee Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 20 Aug 2016 09:04:37 +0000 Subject: Fix typo in gitlab_flow.md --- doc/workflow/gitlab_flow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 2b2f140f8bf..8119324bb62 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -89,7 +89,7 @@ In this case the master branch is deployed on staging. When someone wants to dep And going live with code happens by merging the pre-production branch into the production branch. This workflow where commits only flow downstream ensures that everything has been tested on all environments. If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch. -If master is good to go (it should be if you a practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches. +If master is good to go (it should be if you are practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches. If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches. An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](https://teatro.io/). -- cgit v1.2.1 From f7ddd4d09072ef3c483d8bff77da08e7b325cbc4 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 23 Aug 2016 01:19:58 -0700 Subject: Change merge_error column from string to text type In some cases, the artificial 255-character limit would cause an error in MergeWorker. Closes #20593 --- CHANGELOG | 2 ++ db/migrate/20160823081327_change_merge_error_to_text.rb | 10 ++++++++++ db/schema.rb | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20160823081327_change_merge_error_to_text.rb diff --git a/CHANGELOG b/CHANGELOG index 0ea728cf89b..ea94cf8781b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. + v 8.12.0 (unreleased) + - Change merge_error column from string to text type - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) v 8.11.0 (unreleased) diff --git a/db/migrate/20160823081327_change_merge_error_to_text.rb b/db/migrate/20160823081327_change_merge_error_to_text.rb new file mode 100644 index 00000000000..7920389cd83 --- /dev/null +++ b/db/migrate/20160823081327_change_merge_error_to_text.rb @@ -0,0 +1,10 @@ +class ChangeMergeErrorToText < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'This migration requires downtime because it alters a column from varchar(255) to text.' + + def change + change_column :merge_requests, :merge_error, :text, limit: 65535 + end +end diff --git a/db/schema.rb b/db/schema.rb index 25cfb5de2fa..802c928b2fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160819221833) do +ActiveRecord::Schema.define(version: 20160823081327) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -613,7 +613,7 @@ ActiveRecord::Schema.define(version: 20160819221833) do t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" - t.string "merge_error" + t.text "merge_error" t.text "merge_params" t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" -- cgit v1.2.1 From 2473dade6ad4844c1f936bda3338bdc8a45fbdc0 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 23 Aug 2016 18:00:15 +0100 Subject: Better first match on this MR also --- spec/features/merge_requests/diff_notes_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb index 482c280c188..a371d89f7a5 100644 --- a/spec/features/merge_requests/diff_notes_spec.rb +++ b/spec/features/merge_requests/diff_notes_spec.rb @@ -102,7 +102,7 @@ feature 'Diff notes', js: true, feature: true do context 'with an unfolded line' do before(:each) do - first('.js-unfold').click + find('.js-unfold', match: :first).click wait_for_ajax end -- cgit v1.2.1 From 4a2351185cf0430f55b69f88750d4897d9cea9d4 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 23 Aug 2016 18:04:43 +0100 Subject: Fix Spinach branches spec --- features/project/commits/branches.feature | 1 + features/steps/project/commits/branches.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature index 2c17d32154a..88fef674c0c 100644 --- a/features/project/commits/branches.feature +++ b/features/project/commits/branches.feature @@ -22,6 +22,7 @@ Feature: Project Commits Branches @javascript Scenario: I delete a branch Given I visit project branches page + And I filter for branch improve/awesome And I click branch 'improve/awesome' delete link Then I should not see branch 'improve/awesome' diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb index 4bfb7e92e99..5f9b9e0445e 100644 --- a/features/steps/project/commits/branches.rb +++ b/features/steps/project/commits/branches.rb @@ -73,6 +73,11 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps expect(page).to have_content 'Branch already exists' end + step 'I filter for branch improve/awesome' do + fill_in 'branch-search', with: 'improve/awesome' + find('#branch-search').native.send_keys(:enter) + end + step "I click branch 'improve/awesome' delete link" do page.within '.js-branch-improve\/awesome' do find('.btn-remove').click -- cgit v1.2.1 From 251ded248dcda8c7a552e52ddb2582ad961e4bc0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Lokhorst Date: Tue, 23 Aug 2016 17:33:59 +0000 Subject: Update Ruby version to 2.3 in Requirements --- doc/install/requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index a65ac8a5f79..571f1a38358 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab. ## Ruby versions -GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2 or 2.3. +GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13. You will have to use the standard MRI implementation of Ruby. We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab -- cgit v1.2.1 From d04e8f4fc09832473f7b3794d617aaba9cb204aa Mon Sep 17 00:00:00 2001 From: Sebastiaan Lokhorst Date: Tue, 23 Aug 2016 18:12:27 +0000 Subject: Update Ruby version to 2.3 in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fee93d5f9c3..33d9407a0c1 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the GitLab is a Ruby on Rails application that runs on the following software: - Ubuntu/Debian/CentOS/RHEL -- Ruby (MRI) 2.1 +- Ruby (MRI) 2.3 - Git 2.7.4+ - Redis 2.8+ - MySQL or PostgreSQL -- cgit v1.2.1 From d1ed0c76ec7e9ef4ec1ccff2756433997b3a2397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Tue, 23 Aug 2016 13:02:16 -0600 Subject: Add new lines after backticks and colons --- doc/workflow/merge_requests.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index 12ffa916a8f..adee26f0fe9 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -18,14 +18,18 @@ Please note that you need to have builds configured to enable this feature. ### By adding a git alias Add the following alias to your `~/.gitconfig`: + ``` [alias] mr = !sh -c 'git fetch origin merge-requests/$1/head:mr-$1 && git checkout mr-$1' - ``` + Now you can check out a particular merge request from any repository (it will use the `origin` remote) by: + ``` $ git mr 5 ``` + This will fetch the merge request into a local `mr-5` branch and check it out. ### By modifying `.git/config` for a given repository -- cgit v1.2.1 From a7bd1c23dd03f00d10b0ff4db655690283e2f88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C4=8Cert=C3=ADk?= Date: Tue, 23 Aug 2016 13:05:34 -0600 Subject: Modify the alias to allow specifying a remote Now the alias should be used as: git mr upstream 5 To check out a MR number 5 from the upstream remote, into a `mr-upstream-5` local branch. Also clarify that the `5` in the merge request is the number shown in GitLab. --- doc/workflow/merge_requests.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index adee26f0fe9..91fcfb02d39 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -21,16 +21,16 @@ Add the following alias to your `~/.gitconfig`: ``` [alias] - mr = !sh -c 'git fetch origin merge-requests/$1/head:mr-$1 && git checkout mr-$1' - + mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' - ``` -Now you can check out a particular merge request from any repository (it will use the `origin` remote) by: +Now you can check out a particular merge request from any repository and any remote, e.g. to check out a merge request number 5 as shown in GitLab from the `upstream` remote, do: ``` -$ git mr 5 +$ git mr upstream 5 ``` -This will fetch the merge request into a local `mr-5` branch and check it out. +This will fetch the merge request into a local `mr-upstream-5` branch and check it out. ### By modifying `.git/config` for a given repository -- cgit v1.2.1 From 009727fb80da7c3c2f4414b1e4d0a31c9e13ff68 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 22 Aug 2016 13:43:21 -0500 Subject: Remove ugly horizontal scrollbars in windows --- app/assets/stylesheets/pages/builds.scss | 2 +- app/assets/stylesheets/pages/pipelines.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index c1bb250b42d..8c33e7d9a2e 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -150,7 +150,7 @@ border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; max-height: 300px; - overflow: scroll; + overflow: auto; svg { position: relative; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6fa097e3bf1..8e7cfafe76f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -265,7 +265,7 @@ .pipeline-visualization { position: relative; - min-width: 1220px; + min-width: 1200px; ul { padding: 0; -- cgit v1.2.1 From 2f4cde01c2849168c5cb1aea3902e0af8bcaced1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 22 Aug 2016 13:51:38 -0500 Subject: Remove graph min width --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 8e7cfafe76f..7eb4e7ec676 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -265,7 +265,6 @@ .pipeline-visualization { position: relative; - min-width: 1200px; ul { padding: 0; -- cgit v1.2.1 From a60c1fc4f8e315d82fddf157b5f4790ed8409d1e Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 23 Aug 2016 20:08:18 +0100 Subject: Added unfold test to parallel and added 'diff discussion' context --- app/assets/javascripts/files_comment_button.js | 2 +- spec/features/merge_requests/diff_notes_spec.rb | 35 +++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index 817d38ea86e..3fb3b1a8b51 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -125,7 +125,7 @@ }; FilesCommentButton.prototype.validateLineContent = function(lineContentElement) { - return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== ''; + return lineContentElement.attr('data-discussion-id') && lineContentElement.attr('data-discussion-id') !== ''; }; return FilesCommentButton; diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb index a371d89f7a5..a818679a874 100644 --- a/spec/features/merge_requests/diff_notes_spec.rb +++ b/spec/features/merge_requests/diff_notes_spec.rb @@ -15,7 +15,7 @@ feature 'Diff notes', js: true, feature: true do let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' } let(:test_note_comment) { 'this is a test note!' } - context 'when hovering over the parallel view diff file' do + context 'when hovering over a parallel view diff file' do before(:each) do visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'parallel') end @@ -69,9 +69,28 @@ feature 'Diff notes', js: true, feature: true do should_not_allow_commenting(find('.match', match: :first).find(:xpath, '..'), 'right') end end + + context 'with an unfolded line' do + before(:each) do + find('.js-unfold', match: :first).click + wait_for_ajax + end + + # The first `.js-unfold` unfolds upwards, therefore the first + # `.line_holder` will be an unfolded line. + let(:line_holder) { first('.line_holder[id="1"]') } + + it 'should not allow commenting on the left side' do + should_not_allow_commenting(line_holder, 'left') + end + + it 'should not allow commenting on the right side' do + should_not_allow_commenting(line_holder, 'right') + end + end end - context 'when hovering over the inline view diff file' do + context 'when hovering over an inline view diff file' do before do visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') end @@ -114,6 +133,18 @@ feature 'Diff notes', js: true, feature: true do should_not_allow_commenting line_holder end end + + context 'when hovering over a diff discussion' do + before do + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) + visit namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + end + + it 'should not allow commenting' do + should_not_allow_commenting(find('.line_holder', match: :first)) + end + end end def should_allow_commenting(line_holder, diff_side = nil) -- cgit v1.2.1 From 3a9091b43d06ab499968571bfa13e1c613114e4d Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 23 Aug 2016 15:02:16 -0500 Subject: Fix overflow scroll --- app/assets/stylesheets/pages/merge_requests.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index fcdaf671538..4bfdc4a2b26 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -269,7 +269,7 @@ .builds { .table-holder { - overflow-x: scroll; + overflow-x: auto; } } -- cgit v1.2.1 From 6692bca49d0c652eb50f5fcefb77f0afcce3588d Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 23 Aug 2016 21:48:42 +0100 Subject: Added CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 97bd6316b55..5fad4684e86 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) + - Added tests for diff notes v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 -- cgit v1.2.1 From 75d48ae35bcdb8bfc0a6f661de32718710082856 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 24 Aug 2016 02:18:38 +0200 Subject: Add failing test for #21334 --- spec/lib/extracts_path_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 86d04ecfa36..e10c1f5c547 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -51,6 +51,16 @@ describe ExtractsPath, lib: true do expect(@path).to eq(params[:path]) end end + + context 'subclass overrides get_id' do + it 'uses ref returned by get_id' do + allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' } + + assign_ref_vars + + expect(@id).to eq(get_id) + end + end end describe '#extract_ref' do -- cgit v1.2.1 From bc3493f9474d2557c1c30bf30a61e4cd51ece0f1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 14:40:18 +0800 Subject: Use only one before block, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142/diffs#note_14347758 --- spec/requests/projects/artifacts_controller_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 1c68ec9117b..3ba6725efc3 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -17,9 +17,7 @@ describe Projects::ArtifactsController do describe 'GET /:project/builds/artifacts/:ref_name/browse?job=name' do before do project.team << [user, :developer] - end - before do login_as(user) end -- cgit v1.2.1 From e65bc0f175c54d9df66fd4950972c0b0b08d448e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 16:02:56 +0800 Subject: Path could also have slashes! Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14347729 --- app/controllers/projects/artifacts_controller.rb | 14 +++++++++++--- app/helpers/gitlab_routing_helper.rb | 5 +++-- config/routes.rb | 5 ++--- .../requests/projects/artifacts_controller_spec.rb | 22 ++++++++++++++++++++-- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 60e432d68d8..17c6d56c8b9 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -1,4 +1,6 @@ class Projects::ArtifactsController < Projects::ApplicationController + include ExtractsPath + layout 'project' before_action :authorize_read_build! before_action :authorize_update_build!, only: [:keep] @@ -35,7 +37,8 @@ class Projects::ArtifactsController < Projects::ApplicationController end def latest_succeeded - target_path = artifacts_action_path(params[:path], project, build) + path = ref_name_and_path.last + target_path = artifacts_action_path(path, project, build) if target_path redirect_to(target_path) @@ -59,13 +62,18 @@ class Projects::ArtifactsController < Projects::ApplicationController end def build_from_ref - if params[:ref_name] - builds = project.latest_successful_builds_for(params[:ref_name]) + if params[:ref_name_and_path] + ref_name = ref_name_and_path.first + builds = project.latest_successful_builds_for(ref_name) builds.find_by(name: params[:job]) end end + def ref_name_and_path + @ref_name_and_path ||= extract_ref(params[:ref_name_and_path]) + end + def artifacts_file @artifacts_file ||= build.artifacts_file end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index cd526f17b99..a322a90cc4e 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -153,9 +153,10 @@ module GitlabRoutingHelper # Artifacts def artifacts_action_path(path, project, build) - args = [project.namespace, project, build] + action, path_params = path.split('/', 2) + args = [project.namespace, project, build, path_params] - case path + case action when 'download' download_namespace_project_build_artifacts_path(*args) when 'browse' diff --git a/config/routes.rb b/config/routes.rb index 606181ff837..879cd61a02f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -787,9 +787,8 @@ Rails.application.routes.draw do resources :artifacts, only: [] do collection do get :latest_succeeded, - path: ':ref_name/*path', - format: false, - constraints: { ref_name: /.+/ } # could have / + path: '*ref_name_and_path', + format: false end end end diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index 3ba6725efc3..e02f0eacc93 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -26,8 +26,7 @@ describe Projects::ArtifactsController do latest_succeeded_namespace_project_artifacts_path( project.namespace, project, - ref, - path, + [ref, path].join('/'), job: job) end @@ -94,6 +93,25 @@ describe Projects::ArtifactsController do it_behaves_like 'redirect to the build' end + + context 'with branch name and path containing slashes' do + before do + pipeline.update(ref: 'improve/awesome', + sha: project.commit('improve/awesome').sha) + + get path_from_ref('improve/awesome', build.name, 'file/README.md') + end + + it 'redirects' do + path = file_namespace_project_build_artifacts_path( + project.namespace, + project, + build, + 'README.md') + + expect(response).to redirect_to(path) + end + end end end end -- cgit v1.2.1 From 0762f680c653dff9ff45fe0ce72136b39e125979 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 22 Aug 2016 13:56:07 +0100 Subject: change from members using size to start using total_count in the group members page --- CHANGELOG | 1 + app/views/groups/group_members/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ef38d3e29f5..d9b8372be18 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.12.0 (unreleased) v 8.11.1 (unreleased) - Fix file links on project page when default view is Files !5933 + - Change using size to use count and caching it for number of group members v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 90f362c052b..f789796e942 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -17,7 +17,7 @@ .panel-heading %strong #{@group.name} group members - %span.badge= @members.size + %span.badge= @members.total_count .controls = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group -- cgit v1.2.1 From 31b2c1ef880349c9579f41e501de0346e59f0a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Dequ=C3=A8nes=20=28Duck=29?= Date: Mon, 22 Aug 2016 12:47:58 +0900 Subject: expose 'only_allow_merge_if_build_succeeds' project setting in the API --- CHANGELOG | 1 + doc/api/projects.md | 24 +++++++++++++++++------- lib/api/entities.rb | 1 + lib/api/projects.rb | 9 ++++++--- spec/javascripts/fixtures/projects.json | 2 +- spec/requests/api/projects_spec.rb | 28 +++++++++++++++++++++++++++- 6 files changed, 53 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ef38d3e29f5..c28daf5e6df 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Added tests for diff notes + - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) v 8.11.1 (unreleased) - Fix file links on project page when default view is Files !5933 diff --git a/doc/api/projects.md b/doc/api/projects.md index 37d97b2db44..fe3c38750d6 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -84,7 +84,8 @@ Parameters: "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false }, { "id": 6, @@ -144,7 +145,8 @@ Parameters: "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ] ``` @@ -280,7 +282,8 @@ Parameters: "group_name": "Gitlab Org", "group_access_level": 10 } - ] + ], + "only_allow_merge_if_build_succeeds": false } ``` @@ -448,6 +451,7 @@ Parameters: - `visibility_level` (optional) - `import_url` (optional) - `public_builds` (optional) +- `only_allow_merge_if_build_succeeds` (optional) ### Create project for user @@ -473,6 +477,7 @@ Parameters: - `visibility_level` (optional) - `import_url` (optional) - `public_builds` (optional) +- `only_allow_merge_if_build_succeeds` (optional) ### Edit project @@ -499,6 +504,7 @@ Parameters: - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) - `public_builds` (optional) +- `only_allow_merge_if_build_succeeds` (optional) On success, method returns 200 with the updated project. If parameters are invalid, 400 is returned. @@ -577,7 +583,8 @@ Example response: "forks_count": 0, "star_count": 1, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` @@ -643,7 +650,8 @@ Example response: "forks_count": 0, "star_count": 0, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` @@ -729,7 +737,8 @@ Example response: "star_count": 0, "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` @@ -815,7 +824,8 @@ Example response: "star_count": 0, "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "only_allow_merge_if_build_succeeds": false } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 66b85ab1793..0f6d28f1821 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -90,6 +90,7 @@ module API expose :shared_with_groups do |project, options| SharedGroup.represent(project.project_group_links.all, options) end + expose :only_allow_merge_if_build_succeeds end class Member < UserBasic diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 60cfc103afd..71efd4f33ca 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -123,7 +123,8 @@ module API :public, :visibility_level, :import_url, - :public_builds] + :public_builds, + :only_allow_merge_if_build_succeeds] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(current_user, attrs).execute if @project.saved? @@ -172,7 +173,8 @@ module API :public, :visibility_level, :import_url, - :public_builds] + :public_builds, + :only_allow_merge_if_build_succeeds] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(user, attrs).execute if @project.saved? @@ -234,7 +236,8 @@ module API :shared_runners_enabled, :public, :visibility_level, - :public_builds] + :public_builds, + :only_allow_merge_if_build_succeeds] attrs = map_public_to_visibility_level(attrs) authorize_admin_project authorize! :rename_project, user_project if attrs[:name].present? diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json index 84e8d0ba1e4..4919d77e5a4 100644 --- a/spec/javascripts/fixtures/projects.json +++ b/spec/javascripts/fixtures/projects.json @@ -1 +1 @@ -[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}] +[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":true,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":true,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}] diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 4742b3d0e37..63f2467be63 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -224,7 +224,8 @@ describe API::API, api: true do description: FFaker::Lorem.sentence, issues_enabled: false, merge_requests_enabled: false, - wiki_enabled: false + wiki_enabled: false, + only_allow_merge_if_build_succeeds: false }) post api('/projects', user), project @@ -276,6 +277,18 @@ describe API::API, api: true do expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) end + it 'sets a project as allowing merge even if build fails' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + post api('/projects', user), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + end + + it 'sets a project as allowing merge only if build succeeds' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + post api('/projects', user), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + end + context 'when a visibility level is restricted' do before do @project = attributes_for(:project, { public: true }) @@ -384,6 +397,18 @@ describe API::API, api: true do expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) end + + it 'sets a project as allowing merge even if build fails' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: false }) + post api("/projects/user/#{user.id}", admin), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_falsey + end + + it 'sets a project as allowing merge only if build succeeds' do + project = attributes_for(:project, { only_allow_merge_if_build_succeeds: true }) + post api("/projects/user/#{user.id}", admin), project + expect(json_response['only_allow_merge_if_build_succeeds']).to be_truthy + end end describe "POST /projects/:id/uploads" do @@ -444,6 +469,7 @@ describe API::API, api: true do expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) + expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) end it 'returns a project by path name' do -- cgit v1.2.1 From ddbdf4e609c70dc6ed88860b5e1e65abde69ee94 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 24 Aug 2016 02:22:30 +0200 Subject: Restore get_id in ExtractsPath --- app/controllers/projects_controller.rb | 9 +-------- lib/extracts_path.rb | 13 ++++++++++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 2a6385c1029..fc52cd2f367 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -5,7 +5,7 @@ class ProjectsController < Projects::ApplicationController before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] before_action :assign_ref_vars, only: [:show], if: :repo_exists? - before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?] + before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] @@ -332,11 +332,4 @@ class ProjectsController < Projects::ApplicationController def get_id project.repository.root_ref end - - # ExtractsPath will set @id = project.path on the show route, but it has to be the - # branch name for the tree view to work correctly. - def assign_tree_vars - @id = get_id - tree - end end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index a293fa2752f..a4558d157c0 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -94,9 +94,7 @@ module ExtractsPath @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? } @options = HashWithIndifferentAccess.new(@options) - @id = params[:id] || params[:ref] - @id += "/" + params[:path] unless params[:path].blank? - + @id = get_id @ref, @path = extract_ref(@id) @repo = @project.repository if @options[:extended_sha1].blank? @@ -118,4 +116,13 @@ module ExtractsPath def tree @tree ||= @repo.tree(@commit.id, @path) end + + private + + # overriden in subclasses, do not remove + def get_id + id = params[:id] || params[:ref] + id += "/" + params[:path] unless params[:path].blank? + id + end end -- cgit v1.2.1 From 8f197315b3ec354cb0cc0af4acbe54d6aa01d71b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 19:09:10 +0800 Subject: Aggressively merge views, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14347679 https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14347470 --- app/assets/stylesheets/framework/lists.scss | 4 --- app/views/projects/branches/_branch.html.haml | 10 +----- app/views/projects/buttons/_artifacts.html.haml | 14 -------- app/views/projects/buttons/_download.html.haml | 38 ++++++++++++++++++---- .../repositories/_download_archive.html.haml | 28 ---------------- app/views/projects/show.html.haml | 4 +-- app/views/projects/tags/_download.html.haml | 15 --------- app/views/projects/tags/_tag.html.haml | 3 +- app/views/projects/tags/show.html.haml | 3 +- app/views/projects/tree/show.html.haml | 3 +- 10 files changed, 37 insertions(+), 85 deletions(-) delete mode 100644 app/views/projects/buttons/_artifacts.html.haml delete mode 100644 app/views/projects/repositories/_download_archive.html.haml delete mode 100644 app/views/projects/tags/_download.html.haml diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index a88c7906f5d..965fcc06518 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -162,10 +162,6 @@ ul.content-list { margin-right: 0; } } - - .artifacts-btn { - margin-right: 10px; - } } // When dragging a list item diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 21ac675ffe0..c5549f86e38 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -27,15 +27,7 @@ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do Compare - - if @project.latest_successful_builds_for(branch.name).any? - .dropdown.inline.artifacts-btn - %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } - = icon('download') - %span.caret - %span.sr-only - Select Archive Format - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - = render 'projects/buttons/artifacts', project: @project, ref: branch.name + = render 'projects/buttons/download', project: @project, ref: branch.name - if can_remove_branch?(@project, branch.name) = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do diff --git a/app/views/projects/buttons/_artifacts.html.haml b/app/views/projects/buttons/_artifacts.html.haml deleted file mode 100644 index a52677ebf0a..00000000000 --- a/app/views/projects/buttons/_artifacts.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- pipeline = project.pipelines.latest_successful_for(ref) -- if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = project.pipeline_for(ref) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do - %span Download '#{job.name}' diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 5e748c44b08..73dcb9c079e 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,17 +1,41 @@ -- unless @project.empty_repo? - - if can? current_user, :download_code, @project - .dropdown.inline.btn-group +- if !project.empty_repo? && can?(current_user, :download_code, project) + %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'} + .dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') - = icon('caret-down') + %span.caret %span.sr-only Select Archive Format %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } %li.dropdown-header Source code %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), rel: 'nofollow' do + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do + %i.fa.fa-download %span Download zip %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'tar.gz'), rel: 'nofollow' do + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do + %i.fa.fa-download %span Download tar.gz - = render 'projects/buttons/artifacts', project: @project, ref: @ref + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.bz2 + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar + + - pipeline = project.pipelines.latest_successful_for(ref) + - if pipeline + - artifacts = pipeline.builds.latest.with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + - latest_pipeline = project.pipeline_for(ref) + %li + .unclickable= ci_status_for_statuseable(latest_pipeline) + %li.dropdown-header Previous Artifacts + - artifacts.each do |job| + %li + = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do + %span Download '#{job.name}' diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml deleted file mode 100644 index 4f40696a190..00000000000 --- a/app/views/projects/repositories/_download_archive.html.haml +++ /dev/null @@ -1,28 +0,0 @@ -- ref = ref || nil -- btn_class = btn_class || '' -%span.btn-group{class: btn_class} - .dropdown.inline - %button.btn{ 'data-toggle' => 'dropdown' } - = icon('download') - %span.caret - %span.sr-only - Select Archive Format - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li.dropdown-header Source code - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do - %i.fa.fa-download - %span Download zip - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.gz - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.bz2 - %li - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar - = render 'projects/buttons/artifacts', project: @project, ref: ref diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index a666d07e9eb..7130ebaa743 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -70,7 +70,7 @@ = render 'shared/members/access_request_buttons', source: @project .btn-group.project-repo-btn-group - = render "projects/buttons/download" + = render 'projects/buttons/download', project: @project, ref: @ref = render 'projects/buttons/dropdown' = render 'shared/notifications/button', notification_setting: @notification_setting @@ -86,4 +86,4 @@ Archived project! Repository is read-only %div{class: "project-show-#{default_project_view}"} - = render default_project_view \ No newline at end of file + = render default_project_view diff --git a/app/views/projects/tags/_download.html.haml b/app/views/projects/tags/_download.html.haml deleted file mode 100644 index 6985eb74ca7..00000000000 --- a/app/views/projects/tags/_download.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -.dropdown.inline - %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } - = icon('download') - %span.caret - %span.sr-only - Select Archive Format - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li.dropdown-header Source code - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do - %span Download zip - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do - %span Download tar.gz - = render 'projects/buttons/artifacts', project: project, ref: ref diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 2c11c0e5b21..a156d98bab8 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -11,8 +11,7 @@ = strip_gpg_signature(tag.message) .controls - - if can?(current_user, :download_code, @project) - = render 'projects/tags/download', ref: tag.name, project: @project + = render 'projects/buttons/download', project: @project, ref: tag.name - if can?(current_user, :push_code, @project) = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 395d7af6cbb..4dd7439b2d0 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -12,8 +12,7 @@ = icon('files-o') = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do = icon('history') - - if can? current_user, :download_code, @project - = render 'projects/tags/download', ref: @tag.name, project: @project + = render 'projects/buttons/download', project: @project, ref: @tag.name - if can?(current_user, :admin_project, @project) .pull-right = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index c68f86f1378..37d341212af 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -10,8 +10,7 @@ %div{ class: container_class } .tree-controls = render 'projects/find_file_link' - - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped' + = render 'projects/buttons/download', project: @project, ref: @ref #tree-holder.tree-holder.clearfix .nav-block -- cgit v1.2.1 From 5ddb51ba5a4c58db86645424211783af4e390166 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 24 Aug 2016 11:25:06 +0000 Subject: Update abuse_reports.js.es6 with prettier `window.gl` assignment --- app/assets/javascripts/abuse_reports.js.es6 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/abuse_reports.js.es6 b/app/assets/javascripts/abuse_reports.js.es6 index 748084b0307..2fe46b9fd06 100644 --- a/app/assets/javascripts/abuse_reports.js.es6 +++ b/app/assets/javascripts/abuse_reports.js.es6 @@ -1,4 +1,3 @@ -window.gl = window.gl || {}; ((global) => { const MAX_MESSAGE_LENGTH = 500; const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; @@ -36,4 +35,4 @@ window.gl = window.gl || {}; } global.AbuseReports = AbuseReports; -})(window.gl); +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 6953d988ab141863cba4c38c52b6d1af23c9af3e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 20:57:47 +0800 Subject: Update CHANGELOG from v8.11.0 to v8.12.0 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 549b86f76ea..33eecc2c9fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Added tests for diff notes + - Add a button to download latest successful artifacts for branches and tags !5142 v 8.11.1 (unreleased) - Fix file links on project page when default view is Files !5933 @@ -42,7 +43,6 @@ v 8.11.0 - Do not escape URI when extracting path !5878 (winniehell) - Fix filter label tooltip HTML rendering (ClemMakesApps) - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - - Add a button to download latest successful artifacts for branches and tags - Expand commit message width in repo view (ClemMakesApps) - Cache highlighted diff lines for merge requests - Pre-create all builds for a Pipeline when the new Pipeline is created !5295 -- cgit v1.2.1 From 3ae4e1ebf7826340d0a4a15e350c614b6223f06f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 24 Aug 2016 15:50:24 +0200 Subject: Use gitlab-workhorse 0.7.11 Versions 0.7.9 and 0.7.10 crash on previously valid 'authBackend' settings. --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 5b209ea2067..b4d6d12101f 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.10 +0.7.11 diff --git a/doc/install/installation.md b/doc/install/installation.md index b0ec9b4a961..d4b89fa8345 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -397,7 +397,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.10 + sudo -u git -H git checkout v0.7.11 sudo -u git -H make ### Initialize Database and Activate Advanced Features -- cgit v1.2.1 From b1add60648554d5f5ae6990b95405d2b97aac627 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Wed, 24 Aug 2016 14:56:32 +0100 Subject: Reflect template addition in the contributing guide Adjust bug template to adhere to standard bug report format * https://developer.mozilla.org/en-US/docs/Mozilla/QA/Bug_writing_guidelines * Guidelines use the following format - Summary - Steps to Reproduce - Expected Behaviour - Actual Behaviour --- .gitlab/issue_templates/Bug.md | 6 ++++- CONTRIBUTING.md | 59 +++--------------------------------------- 2 files changed, 8 insertions(+), 57 deletions(-) diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index 139d97034a2..f6f5879c21a 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -1,6 +1,6 @@ ## Summary -(Summarize your issue in one sentence - what goes wrong, what did you expect to happen) +(Summarize the bug encountered concisely) ## Steps to reproduce @@ -10,6 +10,10 @@ (What you should see instead) +## Actual behaviour + +(What actually happens) + ## Relevant logs and/or screenshots (Paste any relevant logs - please use code blocks (```) to format console output, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8093a61b4c..9f4b3d5d244 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -129,7 +129,7 @@ request that potentially fixes it. ### Feature proposals -To create a feature proposal for CE and CI, open an issue on the +To create a feature proposal for CE, open an issue on the [issue tracker of CE][ce-tracker]. For feature proposals for EE, open an issue on the @@ -144,16 +144,7 @@ code snippet right after your description in a new line: `~"feature proposal"`. Please keep feature proposals as small and simple as possible, complex ones might be edited to make them small and simple. -You are encouraged to use the template below for feature proposals. - -``` -## Description -Include problem, use cases, benefits, and/or goals - -## Proposal - -## Links / references -``` +Please submit Feature Proposals using the 'Feature Proposal' issue template provided on the issue tracker. For changes in the interface, it can be helpful to create a mockup first. If you want to create something yourself, consider opening an issue first to @@ -166,55 +157,11 @@ submitting your own, there's a good chance somebody else had the same issue or feature proposal. Show your support with an award emoji and/or join the discussion. -Please submit bugs using the following template in the issue description area. +Please submit bugs using the 'Bug' issue template provided on the issue tracker. The text in the parenthesis is there to help you with what to include. Omit it when submitting the actual issue. You can copy-paste it and then edit as you see fit. -``` -## Summary - -(Summarize your issue in one sentence - what goes wrong, what did you expect to happen) - -## Steps to reproduce - -(How one can reproduce the issue - this is very important) - -## Expected behavior - -(What you should see instead) - -## Relevant logs and/or screenshots - -(Paste any relevant logs - please use code blocks (```) to format console output, -logs, and code as it's very hard to read otherwise.) - -## Output of checks - -### Results of GitLab Application Check - -(For installations with omnibus-gitlab package run and paste the output of: -sudo gitlab-rake gitlab:check SANITIZE=true) - -(For installations from source run and paste the output of: -sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true) - -(we will only investigate if the tests are passing) - -### Results of GitLab Environment Info - -(For installations with omnibus-gitlab package run and paste the output of: -sudo gitlab-rake gitlab:env:info) - -(For installations from source run and paste the output of: -sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production) - -## Possible fixes - -(If you can, link to the line of code that might be responsible for the problem) - -``` - ### Issue weight Issue weight allows us to get an idea of the amount of work required to solve -- cgit v1.2.1 From ae4efc8d5e6a195452f0ecf550198575c63a49b9 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 13:56:21 -0400 Subject: docs: update created_at docs for creating issues via API See also #19863. Fixes #18458. --- doc/api/issues.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index a665645ad0e..ff8038c78d1 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -323,7 +323,7 @@ POST /projects/:id/issues | `assignee_id` | integer | no | The ID of a user to assign issue | | `milestone_id` | integer | no | The ID of a milestone to assign issue | | `labels` | string | no | Comma-separated label names for an issue | -| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | +| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | ```bash @@ -384,7 +384,7 @@ PUT /projects/:id/issues/:issue_id | `milestone_id` | integer | no | The ID of a milestone to assign the issue to | | `labels` | string | no | Comma-separated label names for an issue | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | -| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | +| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | ```bash -- cgit v1.2.1 From a5f3bdc5c862944f755f1e61bf965a54e0a6d95d Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 13:57:34 -0400 Subject: docs: fix table alignment --- doc/api/issues.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/issues.md b/doc/api/issues.md index ff8038c78d1..443ba50af73 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -324,7 +324,7 @@ POST /projects/:id/issues | `milestone_id` | integer | no | The ID of a milestone to assign issue | | `labels` | string | no | Comma-separated label names for an issue | | `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | -| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | +| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug @@ -385,7 +385,7 @@ PUT /projects/:id/issues/:issue_id | `labels` | string | no | Comma-separated label names for an issue | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | | `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | -| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | +| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close -- cgit v1.2.1 From 9b470aebb993d7a5745f0e9fc17b207fe46b16fe Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Thu, 11 Aug 2016 18:11:16 -0400 Subject: url_builder: handle project snippets --- lib/gitlab/url_builder.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index fe65c246101..99d0c28e749 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -22,6 +22,8 @@ module Gitlab note_url when WikiPage wiki_page_url + when ProjectSnippet + project_snippet_url(object) else raise NotImplementedError.new("No URL builder defined for #{object.class}") end -- cgit v1.2.1 From e43c4060b67c1996b917bc00afa72122d1d00004 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 2 Aug 2016 13:37:04 -0400 Subject: api: expose web_url for project entities This allows web hooks to have a URL back to entities without having to generate it themselves. --- CHANGELOG | 1 + doc/api/issues.md | 31 ++++++++++++++++++++---------- doc/api/merge_requests.md | 22 ++++++++++++++------- doc/api/project_snippets.md | 3 ++- lib/api/entities.rb | 12 ++++++++++++ spec/requests/api/issues_spec.rb | 1 + spec/requests/api/merge_requests_spec.rb | 1 + spec/requests/api/project_snippets_spec.rb | 1 + 8 files changed, 54 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ef38d3e29f5..cb3069c1ccd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Change merge_error column from string to text type + - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Added tests for diff notes diff --git a/doc/api/issues.md b/doc/api/issues.md index a665645ad0e..b5730574308 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -79,7 +79,8 @@ Example response: "labels" : [], "subscribed" : false, "user_notes_count": 1, - "due_date": "2016-07-22" + "due_date": "2016-07-22", + "web_url": "http://example.com/example/example/issues/6" } ] ``` @@ -156,7 +157,8 @@ Example response: "created_at" : "2016-01-04T15:31:46.176Z", "subscribed" : false, "user_notes_count": 1, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/1" } ] ``` @@ -235,7 +237,8 @@ Example response: "created_at" : "2016-01-04T15:31:46.176Z", "subscribed" : false, "user_notes_count": 1, - "due_date": "2016-07-22" + "due_date": "2016-07-22", + "web_url": "http://example.com/example/example/issues/1" } ] ``` @@ -299,7 +302,8 @@ Example response: "created_at" : "2016-01-04T15:31:46.176Z", "subscribed": false, "user_notes_count": 1, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/1" } ``` @@ -357,7 +361,8 @@ Example response: "milestone" : null, "subscribed" : true, "user_notes_count": 0, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/14" } ``` @@ -418,7 +423,8 @@ Example response: "milestone" : null, "subscribed" : true, "user_notes_count": 0, - "due_date": "2016-07-22" + "due_date": "2016-07-22", + "web_url": "http://example.com/example/example/issues/15" } ``` @@ -496,7 +502,8 @@ Example response: "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "web_url": "https://gitlab.example.com/u/solon.cremin" }, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/11" } ``` @@ -551,7 +558,8 @@ Example response: "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "web_url": "https://gitlab.example.com/u/solon.cremin" }, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/11" } ``` @@ -607,7 +615,8 @@ Example response: "web_url": "https://gitlab.example.com/u/orville" }, "subscribed": false, - "due_date": null + "due_date": null, + "web_url": "http://example.com/example/example/issues/12" } ``` @@ -693,7 +702,9 @@ Example response: "subscribed": true, "user_notes_count": 7, "upvotes": 0, - "downvotes": 0 + "downvotes": 0, + "due_date": null, + "web_url": "http://example.com/example/example/issues/110" }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10", "body": "Vel voluptas atque dicta mollitia adipisci qui at.", diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 3e88a758936..f275762da3e 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -70,7 +70,8 @@ Parameters: "subscribed" : false, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ] ``` @@ -136,7 +137,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -239,6 +241,7 @@ Parameters: "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1", "changes": [ { "old_path": "VERSION", @@ -321,7 +324,8 @@ Parameters: "subscribed" : true, "user_notes_count": 0, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -395,7 +399,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -496,7 +501,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -565,7 +571,8 @@ Parameters: "subscribed" : true, "user_notes_count": 1, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" } ``` @@ -886,7 +893,8 @@ Example response: "subscribed": true, "user_notes_count": 7, "should_remove_source_branch": true, - "force_remove_source_branch": false + "force_remove_source_branch": false, + "web_url": "http://example.com/example/example/merge_requests/1" }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md index a7acf37b5bc..c6685f54a9d 100644 --- a/doc/api/project_snippets.md +++ b/doc/api/project_snippets.md @@ -53,7 +53,8 @@ Parameters: }, "expires_at": null, "updated_at": "2012-06-28T10:52:04Z", - "created_at": "2012-06-28T10:52:04Z" + "created_at": "2012-06-28T10:52:04Z", + "web_url": "http://example.com/example/example/snippets/1" } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 66b85ab1793..e6bc23b97d7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -177,6 +177,10 @@ module API # TODO (rspeicher): Deprecated; remove in 9.0 expose(:expires_at) { |snippet| nil } + + expose :web_url do |snippet, options| + Gitlab::UrlBuilder.build(snippet) + end end class ProjectEntity < Grape::Entity @@ -206,6 +210,10 @@ module API expose :user_notes_count expose :upvotes, :downvotes expose :due_date + + expose :web_url do |issue, options| + Gitlab::UrlBuilder.build(issue) + end end class ExternalIssue < Grape::Entity @@ -229,6 +237,10 @@ module API expose :user_notes_count expose :should_remove_source_branch?, as: :should_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch + + expose :web_url do |merge_request, options| + Gitlab::UrlBuilder.build(merge_request) + end end class MergeRequestChanges < MergeRequest diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index a40e1a93b71..b8038fc85a1 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -61,6 +61,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(issue.title) + expect(json_response.last).to have_key('web_url') end it "adds pagination headers and keep query params" do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 617600d6173..baff872e28e 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -33,6 +33,7 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) + expect(json_response.last).to have_key('web_url') end it "returns an array of all merge_requests" do diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 42757ff21b0..01148f0a05e 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -30,6 +30,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response.size).to eq(3) expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id) + expect(json_response.last).to have_key('web_url') end it 'hides private snippets from regular user' do -- cgit v1.2.1 From f413d7ed8014601497834e5093a1892dd616875b Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 14:51:05 -0400 Subject: docs: document other project webhook fields --- doc/api/projects.md | 7 +++++++ spec/requests/api/project_hooks_spec.rb | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/doc/api/projects.md b/doc/api/projects.md index 37d97b2db44..0905bdea714 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -914,7 +914,10 @@ Parameters: "push_events": true, "issues_events": true, "merge_requests_events": true, + "tag_push_events": true, "note_events": true, + "build_events": true, + "pipeline_events": true, "enable_ssl_verification": true, "created_at": "2012-10-12T17:04:47Z" } @@ -937,6 +940,8 @@ Parameters: - `merge_requests_events` - Trigger hook on merge_requests events - `tag_push_events` - Trigger hook on push_tag events - `note_events` - Trigger hook on note events +- `build_events` - Trigger hook on build events +- `pipeline_events` - Trigger hook on pipeline events - `enable_ssl_verification` - Do SSL verification when triggering the hook ### Edit project hook @@ -957,6 +962,8 @@ Parameters: - `merge_requests_events` - Trigger hook on merge_requests events - `tag_push_events` - Trigger hook on push_tag events - `note_events` - Trigger hook on note events +- `build_events` - Trigger hook on build events +- `pipeline_events` - Trigger hook on pipeline events - `enable_ssl_verification` - Do SSL verification when triggering the hook ### Delete project hook diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 914e88c9487..4a585c78edf 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -57,6 +57,8 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) expect(json_response['tag_push_events']).to eq(hook.tag_push_events) expect(json_response['note_events']).to eq(hook.note_events) + expect(json_response['build_events']).to eq(hook.build_events) + expect(json_response['pipeline_events']).to eq(hook.pipeline_events) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) end @@ -118,6 +120,8 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events) expect(json_response['tag_push_events']).to eq(hook.tag_push_events) expect(json_response['note_events']).to eq(hook.note_events) + expect(json_response['build_events']).to eq(hook.build_events) + expect(json_response['pipeline_events']).to eq(hook.pipeline_events) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) end -- cgit v1.2.1 From 99c2f3b3c534386d1d8258fea23d3991695ff4fe Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 14:54:44 -0400 Subject: api: expose wiki_page_events project hook field in the API --- CHANGELOG | 1 + doc/api/projects.md | 3 +++ lib/api/entities.rb | 2 +- lib/api/project_hooks.rb | 2 ++ spec/factories/project_hooks.rb | 1 + spec/requests/api/project_hooks_spec.rb | 4 ++++ 6 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ef38d3e29f5..437e2d5d8fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Change merge_error column from string to text type - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) + - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Added tests for diff notes v 8.11.1 (unreleased) diff --git a/doc/api/projects.md b/doc/api/projects.md index 0905bdea714..156deca932b 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -918,6 +918,7 @@ Parameters: "note_events": true, "build_events": true, "pipeline_events": true, + "wiki_page_events": true, "enable_ssl_verification": true, "created_at": "2012-10-12T17:04:47Z" } @@ -942,6 +943,7 @@ Parameters: - `note_events` - Trigger hook on note events - `build_events` - Trigger hook on build events - `pipeline_events` - Trigger hook on pipeline events +- `wiki_page_events` - Trigger hook on wiki page events - `enable_ssl_verification` - Do SSL verification when triggering the hook ### Edit project hook @@ -964,6 +966,7 @@ Parameters: - `note_events` - Trigger hook on note events - `build_events` - Trigger hook on build events - `pipeline_events` - Trigger hook on pipeline events +- `wiki_page_events` - Trigger hook on wiki page events - `enable_ssl_verification` - Do SSL verification when triggering the hook ### Delete project hook diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 66b85ab1793..3ecdc7d448a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -49,7 +49,7 @@ module API class ProjectHook < Hook expose :project_id, :push_events expose :issues_events, :merge_requests_events, :tag_push_events - expose :note_events, :build_events, :pipeline_events + expose :note_events, :build_events, :pipeline_events, :wiki_page_events expose :enable_ssl_verification end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 3f63cd678e8..14f5be3b5f6 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -46,6 +46,7 @@ module API :note_events, :build_events, :pipeline_events, + :wiki_page_events, :enable_ssl_verification ] @hook = user_project.hooks.new(attrs) @@ -80,6 +81,7 @@ module API :note_events, :build_events, :pipeline_events, + :wiki_page_events, :enable_ssl_verification ] diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index 4fd51a23490..424ecc65759 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -14,6 +14,7 @@ FactoryGirl.define do note_events true build_events true pipeline_events true + wiki_page_events true end end end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 4a585c78edf..765dc8a8f66 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -34,6 +34,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response.first['note_events']).to eq(true) expect(json_response.first['build_events']).to eq(true) expect(json_response.first['pipeline_events']).to eq(true) + expect(json_response.first['wiki_page_events']).to eq(true) expect(json_response.first['enable_ssl_verification']).to eq(true) end end @@ -59,6 +60,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['note_events']).to eq(hook.note_events) expect(json_response['build_events']).to eq(hook.build_events) expect(json_response['pipeline_events']).to eq(hook.pipeline_events) + expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) end @@ -95,6 +97,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['note_events']).to eq(false) expect(json_response['build_events']).to eq(false) expect(json_response['pipeline_events']).to eq(false) + expect(json_response['wiki_page_events']).to eq(false) expect(json_response['enable_ssl_verification']).to eq(true) end @@ -122,6 +125,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['note_events']).to eq(hook.note_events) expect(json_response['build_events']).to eq(hook.build_events) expect(json_response['pipeline_events']).to eq(hook.pipeline_events) + expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) end -- cgit v1.2.1 From 831fc817a62d66f4ac4eac8e8cc0b76d593e6448 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 24 Aug 2016 15:51:33 +0100 Subject: Moved `.option-hidden` to exist only in `SELECTABLE_CLASSES` as `NON_SELECTABLE_CLASSES` is used to re-show previously hidden elements --- app/assets/javascripts/gl_dropdown.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 0179b320a3b..2daf1b77e13 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -117,7 +117,7 @@ } }); } else { - return elements.show(); + return elements.show().removeClass('option-hidden'); } } }; @@ -190,9 +190,9 @@ currentIndex = -1; - NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden'; + NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; - SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")"; + SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)"; CURSOR_SELECT_SCROLL_PADDING = 5 -- cgit v1.2.1 From 3c09000e18dcbf6a74ed1f749db3184e309cf081 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 10 Aug 2016 18:22:21 -0300 Subject: Does not halt the GitHub import process when an error occurs --- app/models/project.rb | 2 - app/workers/repository_import_worker.rb | 2 + lib/gitlab/github_import/importer.rb | 82 +++++++------ lib/gitlab/github_import/pull_request_formatter.rb | 4 + spec/lib/gitlab/github_import/importer_spec.rb | 129 +++++++++++++++++++++ .../github_import/pull_request_formatter_spec.rb | 11 +- 6 files changed, 194 insertions(+), 36 deletions(-) create mode 100644 spec/lib/gitlab/github_import/importer_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index 1855760e694..8cf093be4c3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -471,8 +471,6 @@ class Project < ActiveRecord::Base end def reset_cache_and_import_attrs - update(import_error: nil) - ProjectCacheWorker.perform_async(self.id) self.import_data.destroy if self.import_data diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index e6701078f71..d2ca8813ab9 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -14,6 +14,8 @@ class RepositoryImportWorker import_url: @project.import_url, path: @project.path_with_namespace) + project.update_column(:import_error, nil) + result = Projects::ImportService.new(project, current_user).execute if result[:status] == :error diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 9ddc8905bd6..86b49a5021a 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -3,12 +3,13 @@ module Gitlab class Importer include Gitlab::ShellAdapter - attr_reader :client, :project, :repo, :repo_url + attr_reader :client, :errors, :project, :repo, :repo_url def initialize(project) @project = project @repo = project.import_source @repo_url = project.import_url + @errors = [] if credentials @client = Client.new(credentials[:user]) @@ -18,8 +19,14 @@ module Gitlab end def execute - import_labels && import_milestones && import_issues && - import_pull_requests && import_wiki + import_labels + import_milestones + import_issues + import_pull_requests + import_wiki + handle_errors + + true end private @@ -28,22 +35,32 @@ module Gitlab @credentials ||= project.import_data.credentials if project.import_data end + def handle_errors + project.update_column(:import_error, errors.to_json) unless errors.empty? + end + def import_labels labels = client.labels(repo, per_page: 100) - labels.each { |raw| LabelFormatter.new(project, raw).create! } - true - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message + labels.each do |raw| + begin + LabelFormatter.new(project, raw).create! + rescue => e + errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end + end end def import_milestones milestones = client.milestones(repo, state: :all, per_page: 100) - milestones.each { |raw| MilestoneFormatter.new(project, raw).create! } - true - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message + milestones.each do |raw| + begin + MilestoneFormatter.new(project, raw).create! + rescue => e + errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end + end end def import_issues @@ -53,15 +70,15 @@ module Gitlab gh_issue = IssueFormatter.new(project, raw) if gh_issue.valid? - issue = gh_issue.create! - apply_labels(issue) - import_comments(issue) if gh_issue.has_comments? + begin + issue = gh_issue.create! + apply_labels(issue) + import_comments(issue) if gh_issue.has_comments? + rescue => e + errors << { type: :issue, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end end end - - true - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message end def import_pull_requests @@ -77,14 +94,12 @@ module Gitlab apply_labels(merge_request) import_comments(merge_request) import_comments_on_diff(merge_request) - rescue ActiveRecord::RecordInvalid => e - raise Projects::ImportService::Error, e.message + rescue => e + errors << { type: :pull_request, url: Gitlab::UrlSanitizer.sanitize(pull_request.url), errors: e.message } ensure clean_up_restored_branches(pull_request) end end - - true end def restore_source_branch(pull_request) @@ -98,7 +113,7 @@ module Gitlab def remove_branch(name) project.repository.delete_branch(name) rescue Rugged::ReferenceError - nil + errors << { type: :remove_branch, name: name } end def clean_up_restored_branches(pull_request) @@ -112,9 +127,10 @@ module Gitlab issue = client.issue(repo, issuable.iid) if issue.labels.count > 0 - label_ids = issue.labels.map do |raw| - Label.find_by(LabelFormatter.new(project, raw).attributes).try(:id) - end + label_ids = issue.labels + .map { |raw| LabelFormatter.new(project, raw).attributes } + .map { |attrs| Label.find_by(attrs).try(:id) } + .compact issuable.update_attribute(:label_ids, label_ids) end @@ -132,8 +148,12 @@ module Gitlab def create_comments(issuable, comments) comments.each do |raw| - comment = CommentFormatter.new(project, raw) - issuable.notes.create!(comment.attributes) + begin + comment = CommentFormatter.new(project, raw) + issuable.notes.create!(comment.attributes) + rescue => e + errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end end end @@ -143,16 +163,12 @@ module Gitlab gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) project.update_attribute(:wiki_enabled, true) end - - true rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, # this means that repo has wiki enabled, but have no pages. So, # we can skip the import. if e.message !~ /repository not exported/ - raise Projects::ImportService::Error, e.message - else - true + errors << { type: :wiki, errors: e.message } end end end diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index b84538a090a..04aa3664f64 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -56,6 +56,10 @@ module Gitlab end end + def url + raw_data.url + end + private def assigned? diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb new file mode 100644 index 00000000000..2c8d86ef9b6 --- /dev/null +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper' + +describe Gitlab::GithubImport::Importer, lib: true do + describe '#execute' do + context 'when an error occurs' do + let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_enabled: false) } + let(:octocat) { double(id: 123456, login: 'octocat') } + let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } + let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } + let(:repository) { double(id: 1, fork: false) } + let(:source_sha) { create(:commit, project: project).id } + let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) } + let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } + let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) } + + let(:label) do + double( + name: 'Bug', + color: 'ff0000', + url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug' + ) + end + + let(:milestone) do + double( + number: 1347, + state: 'open', + title: '1.0', + description: 'Version 1.0', + due_on: nil, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/milestones/1' + ) + end + + let(:issue1) do + double( + number: 1347, + milestone: nil, + state: 'open', + title: 'Found a bug', + body: "I'm having a problem with this.", + assignee: nil, + user: octocat, + comments: 0, + pull_request: nil, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347' + ) + end + + let(:issue2) do + double( + number: 1348, + milestone: nil, + state: 'open', + title: nil, + body: "I'm having a problem with this.", + assignee: nil, + user: octocat, + comments: 0, + pull_request: nil, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348' + ) + end + + let(:pull_request) do + double( + number: 1347, + milestone: nil, + state: 'open', + title: 'New feature', + body: 'Please pull these awesome changes', + head: source_branch, + base: target_branch, + assignee: nil, + user: octocat, + created_at: created_at, + updated_at: updated_at, + closed_at: nil, + merged_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347' + ) + end + + before do + allow(project).to receive(:import_data).and_return(double.as_null_object) + allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound) + allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label, label]) + allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone]) + allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2]) + allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request]) + allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil })) + allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error) + end + + it 'returns true' do + expect(described_class.new(project).execute).to eq true + end + + it 'does not raise an error' do + expect { described_class.new(project).execute }.not_to raise_error + end + + it 'stores error messages' do + errors = [ + { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" }, + { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" }, + { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." }, + { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" }, + { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." }, + { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" }, + { type: :wiki, errors: "Gitlab::Shell::Error" } + ] + + described_class.new(project).execute + + expect(project.import_error).to eq errors.to_json + end + end + end +end diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index aa28e360993..b667abf063d 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -27,7 +27,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do created_at: created_at, updated_at: updated_at, closed_at: nil, - merged_at: nil + merged_at: nil, + url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347' } end @@ -229,4 +230,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do end end end + + describe '#url' do + let(:raw_data) { double(base_data) } + + it 'return raw url' do + expect(pull_request.url).to eq 'https://api.github.com/repos/octocat/Hello-World/pulls/1347' + end + end end -- cgit v1.2.1 From 2986de7c8c63c0a90f750adbfd843d3b2d50e25f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 24 Aug 2016 12:14:06 -0300 Subject: Add readable error message when remote data could not be fully imported --- lib/gitlab/github_import/importer.rb | 7 ++++++- spec/lib/gitlab/github_import/importer_spec.rb | 23 +++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 86b49a5021a..02ffb43d89b 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -36,7 +36,12 @@ module Gitlab end def handle_errors - project.update_column(:import_error, errors.to_json) unless errors.empty? + return unless errors.any? + + project.update_column(:import_error, { + message: 'The remote data could not be fully imported.', + errors: errors + }.to_json) end def import_labels diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index 2c8d86ef9b6..b7c3bc4e1a7 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -110,19 +110,22 @@ describe Gitlab::GithubImport::Importer, lib: true do end it 'stores error messages' do - errors = [ - { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" }, - { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" }, - { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." }, - { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" }, - { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." }, - { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" }, - { type: :wiki, errors: "Gitlab::Shell::Error" } - ] + error = { + message: 'The remote data could not be fully imported.', + errors: [ + { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" }, + { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" }, + { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." }, + { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" }, + { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." }, + { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" }, + { type: :wiki, errors: "Gitlab::Shell::Error" } + ] + } described_class.new(project).execute - expect(project.import_error).to eq errors.to_json + expect(project.import_error).to eq error.to_json end end end -- cgit v1.2.1 From 4f13a7935f24f258fde8288671ba2447639df446 Mon Sep 17 00:00:00 2001 From: winniehell Date: Thu, 18 Aug 2016 02:00:28 +0200 Subject: Remove green outline from `New branch unavailable` button on issue page (!5858) --- CHANGELOG | 1 + app/assets/javascripts/issue.js | 3 +-- app/views/projects/issues/_new_branch.html.haml | 17 ++++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 01940f71d7b..1eaa304e81e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.11.0 - Add Issues Board !5548 - Allow resolving merge conflicts in the UI !5479 - Improve diff performance by eliminating redundant checks for text blobs + - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi) - Convert switch icon into icon font (ClemMakesApps) - API: Endpoints for enabling and disabling deploy keys diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 6838d9d8da1..e6422602ce8 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -127,7 +127,7 @@ Issue.prototype.initCanCreateBranch = function() { var $container; - $container = $('div#new-branch'); + $container = $('#new-branch'); if ($container.length === 0) { return; } @@ -139,7 +139,6 @@ if (data.can_create_branch) { $container.find('.checking').hide(); $container.find('.available').show(); - return $container.find('a').attr('disabled', false); } else { $container.find('.checking').hide(); return $container.find('.unavailable').show(); diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 24749699c6d..33556a1a2b3 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,13 +1,12 @@ - if can?(current_user, :push_code, @project) .pull-right #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)} + = link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do + = icon('spinner spin') + Checking branches = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), - method: :post, class: 'btn btn-new btn-inverted has-tooltip', title: @issue.to_branch_name, disabled: 'disabled' do - .checking - = icon('spinner spin') - Checking branches - .available.hide - New branch - .unavailable.hide - = icon('exclamation-triangle') - New branch unavailable + method: :post, class: 'btn btn-new btn-inverted btn-grouped has-tooltip available hide', title: @issue.to_branch_name do + New branch + = link_to '#', class: 'unavailable btn btn-grouped hide', disabled: 'disabled' do + = icon('exclamation-triangle') + New branch unavailable -- cgit v1.2.1 From 8e52705776dce7021ac1a150253037f085638a46 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 24 Aug 2016 13:04:26 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ef38d3e29f5..b3cb9d4b3e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.12.0 (unreleased) - Added tests for diff notes v 8.11.1 (unreleased) + - Does not halt the GitHub import process when an error occurs - Fix file links on project page when default view is Files !5933 v 8.11.0 -- cgit v1.2.1 From 60ac1167d1324b4468e3ae85000feb2bf78a24e2 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 24 Aug 2016 11:29:18 -0500 Subject: Remove max height on pipelines graph --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6fa097e3bf1..dcb3e48d686 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -254,7 +254,6 @@ width: 100%; overflow: auto; white-space: nowrap; - max-height: 500px; transition: max-height 0.3s, padding 0.3s; &.graph-collapsed { -- cgit v1.2.1 From cfe512d6e926a3f757d18e5269214c06b8f9643d Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 24 Aug 2016 13:13:26 +0200 Subject: Show "Create Merge Request" widget for push events to fork projects on the source project --- CHANGELOG | 3 +++ app/helpers/projects_helper.rb | 21 ++++++++++---------- app/models/user.rb | 4 ++-- spec/helpers/projects_helper_spec.rb | 38 ++++++++++++++++++++++++++++++++++++ spec/models/user_spec.rb | 10 ++++++++++ 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ef38d3e29f5..65796fd808c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,9 @@ v 8.12.0 (unreleased) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Added tests for diff notes +v 8.11.2 (unreleased) + - Show "Create Merge Request" widget for push events to fork projects on the source project + v 8.11.1 (unreleased) - Fix file links on project page when default view is Files !5933 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 249d18c4486..356f27f2d5d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -116,6 +116,17 @@ module ProjectsHelper license.nickname || license.name end + def last_push_event + return unless current_user + + project_ids = [@project.id] + if fork = current_user.fork_of(@project) + project_ids << fork.id + end + + current_user.recent_push(project_ids) + end + private def get_project_nav_tabs(project, current_user) @@ -351,16 +362,6 @@ module ProjectsHelper namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') end - def last_push_event - return unless current_user - - if fork = current_user.fork_of(@project) - current_user.recent_push(fork.id) - else - current_user.recent_push(@project.id) - end - end - def readme_cache_key sha = @project.commit.try(:sha) || 'nil' [@project.path_with_namespace, sha, "readme"].join('-') diff --git a/app/models/user.rb b/app/models/user.rb index 48e83ab7e56..ad3cfbc03e4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -489,10 +489,10 @@ class User < ActiveRecord::Base (personal_projects.count.to_f / projects_limit) * 100 end - def recent_push(project_id = nil) + def recent_push(project_ids = nil) # Get push events not earlier than 2 hours ago events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) - events = events.where(project_id: project_id) if project_id + events = events.where(project_id: project_ids) if project_ids # Use the latest event that has not been pushed or merged recently events.recent.find do |event| diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 604204cca0a..284b58d8d5c 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -136,4 +136,42 @@ describe ProjectsHelper do expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') end end + + describe '#last_push_event' do + let(:user) { double(:user, fork_of: nil) } + let(:project) { double(:project, id: 1) } + + before do + allow(helper).to receive(:current_user).and_return(user) + helper.instance_variable_set(:@project, project) + end + + context 'when there is no current_user' do + let(:user) { nil } + + it 'returns nil' do + expect(helper.last_push_event).to eq(nil) + end + end + + it 'returns recent push on the current project' do + event = double(:event) + expect(user).to receive(:recent_push).with([project.id]).and_return(event) + + expect(helper.last_push_event).to eq(event) + end + + context 'when current user has a fork of the current project' do + let(:fork) { double(:fork, id: 2) } + + it 'returns recent push considering fork events' do + expect(user).to receive(:fork_of).with(project).and_return(fork) + + event_on_fork = double(:event) + expect(user).to receive(:recent_push).with([project.id, fork.id]).and_return(event_on_fork) + + expect(helper.last_push_event).to eq(event_on_fork) + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 51e4780e2b1..8eb0c5033c9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -920,6 +920,16 @@ describe User, models: true do expect(subject.recent_push).to eq(nil) end + + it "includes push events on any of the provided projects" do + expect(subject.recent_push(project1)).to eq(nil) + expect(subject.recent_push(project2)).to eq(push_event) + + push_data1 = Gitlab::DataBuilder::Push.build_sample(project1, subject) + push_event1 = create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject, data: push_data1) + + expect(subject.recent_push([project1, project2])).to eq(push_event1) # Newest + end end describe '#authorized_groups' do -- cgit v1.2.1 From 170885edd6f3ea52792511586778e0dce8021cf7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 18 Aug 2016 17:06:33 -0700 Subject: Add Sentry logging to API calls Closes #21043 --- CHANGELOG | 1 + app/controllers/application_controller.rb | 23 +--------------------- app/helpers/sentry_helper.rb | 27 ++++++++++++++++++++++++++ lib/api/api.rb | 12 ++---------- lib/api/helpers.rb | 32 +++++++++++++++++++++++++++++++ lib/ci/api/api.rb | 12 ++---------- spec/requests/api/api_helpers_spec.rb | 27 ++++++++++++++++++++++++++ 7 files changed, 92 insertions(+), 42 deletions(-) create mode 100644 app/helpers/sentry_helper.rb diff --git a/CHANGELOG b/CHANGELOG index 3e6066cfae0..ea9ebf740e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) + - Add Sentry logging to API calls - Added tests for diff notes v 8.11.2 (unreleased) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 634d36a4467..70a2275592b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base include Gitlab::GonHelper include GitlabRoutingHelper include PageLayoutHelper + include SentryHelper include WorkhorseHelper before_action :authenticate_user_from_private_token! @@ -46,28 +47,6 @@ class ApplicationController < ActionController::Base protected - def sentry_context - if Rails.env.production? && current_application_settings.sentry_enabled - if current_user - Raven.user_context( - id: current_user.id, - email: current_user.email, - username: current_user.username, - ) - end - - Raven.tags_context(program: sentry_program_context) - end - end - - def sentry_program_context - if Sidekiq.server? - 'sidekiq' - else - 'rails' - end - end - # This filter handles both private tokens and personal access tokens def authenticate_user_from_private_token! token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb new file mode 100644 index 00000000000..f8cccade15b --- /dev/null +++ b/app/helpers/sentry_helper.rb @@ -0,0 +1,27 @@ +module SentryHelper + def sentry_enabled? + Rails.env.production? && current_application_settings.sentry_enabled? + end + + def sentry_context + return unless sentry_enabled? + + if current_user + Raven.user_context( + id: current_user.id, + email: current_user.email, + username: current_user.username, + ) + end + + Raven.tags_context(program: sentry_program_context) + end + + def sentry_program_context + if Sidekiq.server? + 'sidekiq' + else + 'rails' + end + end +end diff --git a/lib/api/api.rb b/lib/api/api.rb index 6b8bfbbdae6..ecbd5a6e2fa 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -18,22 +18,14 @@ module API end rescue_from :all do |exception| - # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 - # why is this not wrapped in something reusable? - trace = exception.backtrace - - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << trace.join("\n ") - - API.logger.add Logger::FATAL, message - rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500) + handle_api_exception(exception) end format :json content_type :txt, "text/plain" # Ensure the namespace is right, otherwise we might load Grape::API::Helpers + helpers ::SentryHelper helpers ::API::Helpers mount ::API::AccessRequests diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index d0469d6602d..da4b1bf9902 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -279,6 +279,24 @@ module API error!({ 'message' => message }, status) end + def handle_api_exception(exception) + if sentry_enabled? && report_exception?(exception) + define_params_for_grape_middleware + sentry_context + Raven.capture_exception(exception) + end + + # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 + trace = exception.backtrace + + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << trace.join("\n ") + + API.logger.add Logger::FATAL, message + rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500) + end + # Projects helpers def filter_projects(projects) @@ -419,5 +437,19 @@ module API Entities::Issue end end + + # The Grape Error Middleware only has access to env but no params. We workaround this by + # defining a method that returns the right value. + def define_params_for_grape_middleware + self.define_singleton_method(:params) { Rack::Request.new(env).params.symbolize_keys } + end + + # We could get a Grape or a standard Ruby exception. We should only report anything that + # is clearly an error. + def report_exception?(exception) + return true unless exception.respond_to?(:status) + + exception.status == 500 + end end end diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb index 17bb99a2ae5..a6b9beecded 100644 --- a/lib/ci/api/api.rb +++ b/lib/ci/api/api.rb @@ -9,22 +9,14 @@ module Ci end rescue_from :all do |exception| - # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 - # why is this not wrapped in something reusable? - trace = exception.backtrace - - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << trace.join("\n ") - - API.logger.add Logger::FATAL, message - rack_response({ 'message' => '500 Internal Server Error' }, 500) + handle_api_exception(exception) end content_type :txt, 'text/plain' content_type :json, 'application/json' format :json + helpers ::SentryHelper helpers ::Ci::API::Helpers helpers ::API::Helpers helpers Gitlab::CurrentSettings diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb index c65510fadec..bbdf8f03c2b 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/api_helpers_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe API::Helpers, api: true do include API::Helpers include ApiHelpers + include SentryHelper let(:user) { create(:user) } let(:admin) { create(:admin) } @@ -234,4 +235,30 @@ describe API::Helpers, api: true do expect(to_boolean(nil)).to be_nil end end + + describe '.handle_api_exception' do + before do + allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true) + allow_any_instance_of(self.class).to receive(:rack_response) + end + + it 'does not report a MethodNotAllowed exception to Sentry' do + exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' }) + allow(exception).to receive(:backtrace).and_return(caller) + + expect(Raven).not_to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + + it 'does report RuntimeError to Sentry' do + exception = RuntimeError.new('test error') + allow(exception).to receive(:backtrace).and_return(caller) + + expect_any_instance_of(self.class).to receive(:sentry_context) + expect(Raven).to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + end end -- cgit v1.2.1 From bba85773519e972d036a933b1f054b6c76050c5f Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 26 Jul 2016 16:48:51 -0500 Subject: Add two factor recovery endpoint to internal API --- CHANGELOG | 1 + doc/README.md | 1 + doc/user/account/security.md | 3 ++ doc/user/account/two_factor_authentication.md | 68 +++++++++++++++++++++++++++ lib/api/internal.rb | 25 ++++++++++ spec/requests/api/internal_spec.rb | 62 ++++++++++++++++++++++++ 6 files changed, 160 insertions(+) create mode 100644 doc/user/account/security.md create mode 100644 doc/user/account/two_factor_authentication.md diff --git a/CHANGELOG b/CHANGELOG index 8b25b94b772..346618b0160 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Add two-factor recovery endpoint to internal API !5510 - Change merge_error column from string to text type - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) diff --git a/doc/README.md b/doc/README.md index 195b7c7af62..047035dfb09 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,6 +2,7 @@ ## User documentation +- [Account Security](user/account/security.md) Securing your account via two-factor authentication, etc. - [API](api/README.md) Automate GitLab via a simple and powerful API. - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. diff --git a/doc/user/account/security.md b/doc/user/account/security.md new file mode 100644 index 00000000000..816094bf8d2 --- /dev/null +++ b/doc/user/account/security.md @@ -0,0 +1,3 @@ +# Account Security + +- [Two-Factor Authentication](two_factor_authentication.md) diff --git a/doc/user/account/two_factor_authentication.md b/doc/user/account/two_factor_authentication.md new file mode 100644 index 00000000000..881358ed94d --- /dev/null +++ b/doc/user/account/two_factor_authentication.md @@ -0,0 +1,68 @@ +# Two-Factor Authentication + +## Recovery options + +If you lose your code generation device (such as your mobile phone) and you need +to disable two-factor authentication on your account, you have several options. + +### Use a saved recovery code + +When you enabled two-factor authentication for your account, a series of +recovery codes were generated. If you saved those codes somewhere safe, you +may use one to sign in. + +First, enter your username/email and password on the GitLab sign in page. When +prompted for a two-factor code, enter one of the recovery codes you saved +previously. + +> **Note:** Once a particular recovery code has been used, it cannot be used again. + You may still use the other saved recovery codes at a later time. + +### Generate new recovery codes using SSH + +It's not uncommon for users to forget to save the recovery codes when enabling +two-factor authentication. If you have an SSH key added to your GitLab account, +you can generate a new set of recovery codes using SSH. + +Run `ssh git@gitlab.example.com 2fa_recovery_codes`. You will be prompted to +confirm that you wish to generate new codes. If you choose to continue, any +previously saved codes will be invalidated. + +```bash +$ ssh git@gitlab.example.com 2fa_recovery_codes +Are you sure you want to generate new two-factor recovery codes? +Any existing recovery codes you saved will be invalidated. (yes/no) +yes + +Your two-factor authentication recovery codes are: + +119135e5a3ebce8e +11f6v2a498810dcd +3924c7ab2089c902 +e79a3398bfe4f224 +34bd7b74adbc8861 +f061691d5107df1a +169bf32a18e63e7f +b510e7422e81c947 +20dbed24c5e74663 +df9d3b9403b9c9f0 + +During sign in, use one of the codes above when prompted for +your two-factor code. Then, visit your Profile Settings and add +a new device so you do not lose access to your account again. +``` + +Next, go to the GitLab sign in page and enter your username/email and password. +When prompted for a two-factor code, enter one of the recovery codes obtained +from the command line output. + +> **Note:** After signing in, you should immediately visit your **Profile Settings + -> Account** to set up two-factor authentication with a new device. + +### Ask a GitLab administrator to disable two-factor on your account + +If the above two methods are not possible, you may ask a GitLab global +administrator to disable two-factor authentication for your account. Please +be aware that this will temporarily leave your account in a less secure state. +You should sign in and re-enable two-factor authentication as soon as possible +after the administrator disables it. diff --git a/lib/api/internal.rb b/lib/api/internal.rb index d8e9ac406c4..5b54c11ef62 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -101,6 +101,31 @@ module API {} end end + + post '/two_factor_recovery_codes' do + status 200 + + key = Key.find(params[:key_id]) + user = key.user + + # Make sure this isn't a deploy key + unless key.type.nil? + return { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } + end + + unless user.present? + return { success: false, message: 'Could not find a user for the given key' } + end + + unless user.two_factor_enabled? + return { success: false, message: 'Two-factor authentication is not enabled for this user' } + end + + codes = user.generate_otp_backup_codes! + user.save! + + { success: true, recovery_codes: codes } + end end end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index be52f88831f..5d06abcfeb3 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -38,6 +38,68 @@ describe API::API, api: true do end end + describe 'GET /internal/two_factor_recovery_codes' do + it 'returns an error message when the key does not exist' do + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: 12345 + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Not found') + end + + it 'returns an error message when the key is a deploy key' do + deploy_key = create(:deploy_key) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: deploy_key.id + + expect(json_response['success']).to be_falsey + expect(json_response['message']).to eq('Deploy keys cannot be used to retrieve recovery codes') + end + + it 'returns an error message when the user does not exist' do + key_without_user = create(:key, user: nil) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: key_without_user.id + + expect(json_response['success']).to be_falsey + expect(json_response['message']).to eq('Could not find a user for the given key') + expect(json_response['recovery_codes']).to be_nil + end + + context 'when two-factor is enabled' do + it 'returns new recovery codes when the user exists' do + allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true) + allow_any_instance_of(User) + .to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861)) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: key.id + + expect(json_response['success']).to be_truthy + expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861)) + end + end + + context 'when two-factor is not enabled' do + it 'returns an error message' do + allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false) + + post api('/internal/two_factor_recovery_codes'), + secret_token: secret_token, + key_id: key.id + + expect(json_response['success']).to be_falsey + expect(json_response['recovery_codes']).to be_nil + end + end + end + describe "GET /internal/discover" do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) -- cgit v1.2.1 From 1bc53bc8702ea0cda02a368ba9ca0516ad1e3947 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 24 Aug 2016 19:29:25 -0500 Subject: Ensure we update the label after input is added --- app/assets/javascripts/gl_dropdown.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 0179b320a3b..521ffb405be 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -565,10 +565,6 @@ } else { field.remove(); } - if (this.options.toggleLabel) { - this.updateLabel(selectedObject, el, this); - } - return selectedObject; } else if (el.hasClass(INDETERMINATE_CLASS)) { el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); @@ -578,7 +574,6 @@ if (!field.length && fieldName) { this.addInput(fieldName, value, selectedObject); } - return selectedObject; } else { if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) { this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS); @@ -590,9 +585,6 @@ field.remove(); } el.addClass(ACTIVE_CLASS); - if (this.options.toggleLabel) { - this.updateLabel(selectedObject, el, this); - } if (value != null) { if (!field.length && fieldName) { this.addInput(fieldName, value, selectedObject); @@ -600,8 +592,14 @@ field.val(value).trigger('change'); } } - return selectedObject; } + + // Update label right after input has been added + if (this.options.toggleLabel) { + this.updateLabel(selectedObject, el, this); + } + + return selectedObject; }; GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { -- cgit v1.2.1 From 23bed91b3fb21a92b836011677cc75c884188f10 Mon Sep 17 00:00:00 2001 From: De Wet Blomerus Date: Thu, 25 Aug 2016 04:55:32 +0200 Subject: rename Statuseable to HasStatus --- app/models/ci/pipeline.rb | 2 +- app/models/commit_status.rb | 2 +- app/models/concerns/has_status.rb | 93 ++++++++++++++++++++ app/models/concerns/statuseable.rb | 93 -------------------- app/services/ci/process_pipeline_service.rb | 2 +- spec/models/concerns/has_status_spec.rb | 129 ++++++++++++++++++++++++++++ spec/models/concerns/statuseable_spec.rb | 129 ---------------------------- 7 files changed, 225 insertions(+), 225 deletions(-) create mode 100644 app/models/concerns/has_status.rb delete mode 100644 app/models/concerns/statuseable.rb create mode 100644 spec/models/concerns/has_status_spec.rb delete mode 100644 spec/models/concerns/statuseable_spec.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 087abe4cbb1..255fb33bdeb 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -1,7 +1,7 @@ module Ci class Pipeline < ActiveRecord::Base extend Ci::Model - include Statuseable + include HasStatus self.table_name = 'ci_commits' diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 84ceeac7d3e..9114af18496 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,5 +1,5 @@ class CommitStatus < ActiveRecord::Base - include Statuseable + include HasStatus include Importable self.table_name = 'ci_builds' diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb new file mode 100644 index 00000000000..f7b8352405c --- /dev/null +++ b/app/models/concerns/has_status.rb @@ -0,0 +1,93 @@ +module HasStatus + extend ActiveSupport::Concern + + AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] + STARTED_STATUSES = %w[running success failed skipped] + ACTIVE_STATUSES = %w[pending running] + COMPLETED_STATUSES = %w[success failed canceled] + + class_methods do + def status_sql + scope = all.relevant + builds = scope.select('count(*)').to_sql + success = scope.success.select('count(*)').to_sql + ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) + ignored ||= '0' + pending = scope.pending.select('count(*)').to_sql + running = scope.running.select('count(*)').to_sql + canceled = scope.canceled.select('count(*)').to_sql + skipped = scope.skipped.select('count(*)').to_sql + + deduce_status = "(CASE + WHEN (#{builds})=0 THEN NULL + WHEN (#{builds})=(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' + WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending' + WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' + WHEN (#{running})+(#{pending})>0 THEN 'running' + ELSE 'failed' + END)" + + deduce_status + end + + def status + all.pluck(self.status_sql).first + end + + def started_at + all.minimum(:started_at) + end + + def finished_at + all.maximum(:finished_at) + end + end + + included do + validates :status, inclusion: { in: AVAILABLE_STATUSES } + + state_machine :status, initial: :created do + state :created, value: 'created' + state :pending, value: 'pending' + state :running, value: 'running' + state :failed, value: 'failed' + state :success, value: 'success' + state :canceled, value: 'canceled' + state :skipped, value: 'skipped' + end + + scope :created, -> { where(status: 'created') } + scope :relevant, -> { where.not(status: 'created') } + scope :running, -> { where(status: 'running') } + scope :pending, -> { where(status: 'pending') } + scope :success, -> { where(status: 'success') } + scope :failed, -> { where(status: 'failed') } + scope :canceled, -> { where(status: 'canceled') } + scope :skipped, -> { where(status: 'skipped') } + scope :running_or_pending, -> { where(status: [:running, :pending]) } + scope :finished, -> { where(status: [:success, :failed, :canceled]) } + end + + def started? + STARTED_STATUSES.include?(status) && started_at + end + + def active? + ACTIVE_STATUSES.include?(status) + end + + def complete? + COMPLETED_STATUSES.include?(status) + end + + private + + def calculate_duration + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.now - started_at + end + end +end diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb deleted file mode 100644 index 750f937b724..00000000000 --- a/app/models/concerns/statuseable.rb +++ /dev/null @@ -1,93 +0,0 @@ -module Statuseable - extend ActiveSupport::Concern - - AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] - STARTED_STATUSES = %w[running success failed skipped] - ACTIVE_STATUSES = %w[pending running] - COMPLETED_STATUSES = %w[success failed canceled] - - class_methods do - def status_sql - scope = all.relevant - builds = scope.select('count(*)').to_sql - success = scope.success.select('count(*)').to_sql - ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) - ignored ||= '0' - pending = scope.pending.select('count(*)').to_sql - running = scope.running.select('count(*)').to_sql - canceled = scope.canceled.select('count(*)').to_sql - skipped = scope.skipped.select('count(*)').to_sql - - deduce_status = "(CASE - WHEN (#{builds})=0 THEN NULL - WHEN (#{builds})=(#{skipped}) THEN 'skipped' - WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' - WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending' - WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' - WHEN (#{running})+(#{pending})>0 THEN 'running' - ELSE 'failed' - END)" - - deduce_status - end - - def status - all.pluck(self.status_sql).first - end - - def started_at - all.minimum(:started_at) - end - - def finished_at - all.maximum(:finished_at) - end - end - - included do - validates :status, inclusion: { in: AVAILABLE_STATUSES } - - state_machine :status, initial: :created do - state :created, value: 'created' - state :pending, value: 'pending' - state :running, value: 'running' - state :failed, value: 'failed' - state :success, value: 'success' - state :canceled, value: 'canceled' - state :skipped, value: 'skipped' - end - - scope :created, -> { where(status: 'created') } - scope :relevant, -> { where.not(status: 'created') } - scope :running, -> { where(status: 'running') } - scope :pending, -> { where(status: 'pending') } - scope :success, -> { where(status: 'success') } - scope :failed, -> { where(status: 'failed') } - scope :canceled, -> { where(status: 'canceled') } - scope :skipped, -> { where(status: 'skipped') } - scope :running_or_pending, -> { where(status: [:running, :pending]) } - scope :finished, -> { where(status: [:success, :failed, :canceled]) } - end - - def started? - STARTED_STATUSES.include?(status) && started_at - end - - def active? - ACTIVE_STATUSES.include?(status) - end - - def complete? - COMPLETED_STATUSES.include?(status) - end - - private - - def calculate_duration - if started_at && finished_at - finished_at - started_at - elsif started_at - Time.now - started_at - end - end -end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 6f7610d42ba..f049ed628db 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -34,7 +34,7 @@ module Ci end def process_build(build, current_status) - return false unless Statuseable::COMPLETED_STATUSES.include?(current_status) + return false unless HasStatus::COMPLETED_STATUSES.include?(current_status) if valid_statuses_for_when(build.when).include?(current_status) build.enqueue diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb new file mode 100644 index 00000000000..e118432d098 --- /dev/null +++ b/spec/models/concerns/has_status_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper' + +describe HasStatus do + before do + @object = Object.new + @object.extend(HasStatus::ClassMethods) + end + + describe '.status' do + before do + allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses)) + end + + subject { @object.status } + + shared_examples 'build status summary' do + context 'all successful' do + let(:statuses) { Array.new(2) { create(type, status: :success) } } + it { is_expected.to eq 'success' } + end + + context 'at least one failed' do + let(:statuses) do + [create(type, status: :success), create(type, status: :failed)] + end + + it { is_expected.to eq 'failed' } + end + + context 'at least one running' do + let(:statuses) do + [create(type, status: :success), create(type, status: :running)] + end + + it { is_expected.to eq 'running' } + end + + context 'at least one pending' do + let(:statuses) do + [create(type, status: :success), create(type, status: :pending)] + end + + it { is_expected.to eq 'running' } + end + + context 'success and failed but allowed to fail' do + let(:statuses) do + [create(type, status: :success), + create(type, status: :failed, allow_failure: true)] + end + + it { is_expected.to eq 'success' } + end + + context 'one failed but allowed to fail' do + let(:statuses) { [create(type, status: :failed, allow_failure: true)] } + it { is_expected.to eq 'success' } + end + + context 'success and canceled' do + let(:statuses) do + [create(type, status: :success), create(type, status: :canceled)] + end + + it { is_expected.to eq 'canceled' } + end + + context 'one failed and one canceled' do + let(:statuses) do + [create(type, status: :failed), create(type, status: :canceled)] + end + + it { is_expected.to eq 'failed' } + end + + context 'one failed but allowed to fail and one canceled' do + let(:statuses) do + [create(type, status: :failed, allow_failure: true), + create(type, status: :canceled)] + end + + it { is_expected.to eq 'canceled' } + end + + context 'one running one canceled' do + let(:statuses) do + [create(type, status: :running), create(type, status: :canceled)] + end + + it { is_expected.to eq 'running' } + end + + context 'all canceled' do + let(:statuses) do + [create(type, status: :canceled), create(type, status: :canceled)] + end + it { is_expected.to eq 'canceled' } + end + + context 'success and canceled but allowed to fail' do + let(:statuses) do + [create(type, status: :success), + create(type, status: :canceled, allow_failure: true)] + end + + it { is_expected.to eq 'success' } + end + + context 'one finished and second running but allowed to fail' do + let(:statuses) do + [create(type, status: :success), + create(type, status: :running, allow_failure: true)] + end + + it { is_expected.to eq 'running' } + end + end + + context 'ci build statuses' do + let(:type) { :ci_build } + it_behaves_like 'build status summary' + end + + context 'generic commit statuses' do + let(:type) { :generic_commit_status } + it_behaves_like 'build status summary' + end + end +end diff --git a/spec/models/concerns/statuseable_spec.rb b/spec/models/concerns/statuseable_spec.rb deleted file mode 100644 index 8e0a2a2cbde..00000000000 --- a/spec/models/concerns/statuseable_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'spec_helper' - -describe Statuseable do - before do - @object = Object.new - @object.extend(Statuseable::ClassMethods) - end - - describe '.status' do - before do - allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses)) - end - - subject { @object.status } - - shared_examples 'build status summary' do - context 'all successful' do - let(:statuses) { Array.new(2) { create(type, status: :success) } } - it { is_expected.to eq 'success' } - end - - context 'at least one failed' do - let(:statuses) do - [create(type, status: :success), create(type, status: :failed)] - end - - it { is_expected.to eq 'failed' } - end - - context 'at least one running' do - let(:statuses) do - [create(type, status: :success), create(type, status: :running)] - end - - it { is_expected.to eq 'running' } - end - - context 'at least one pending' do - let(:statuses) do - [create(type, status: :success), create(type, status: :pending)] - end - - it { is_expected.to eq 'running' } - end - - context 'success and failed but allowed to fail' do - let(:statuses) do - [create(type, status: :success), - create(type, status: :failed, allow_failure: true)] - end - - it { is_expected.to eq 'success' } - end - - context 'one failed but allowed to fail' do - let(:statuses) { [create(type, status: :failed, allow_failure: true)] } - it { is_expected.to eq 'success' } - end - - context 'success and canceled' do - let(:statuses) do - [create(type, status: :success), create(type, status: :canceled)] - end - - it { is_expected.to eq 'canceled' } - end - - context 'one failed and one canceled' do - let(:statuses) do - [create(type, status: :failed), create(type, status: :canceled)] - end - - it { is_expected.to eq 'failed' } - end - - context 'one failed but allowed to fail and one canceled' do - let(:statuses) do - [create(type, status: :failed, allow_failure: true), - create(type, status: :canceled)] - end - - it { is_expected.to eq 'canceled' } - end - - context 'one running one canceled' do - let(:statuses) do - [create(type, status: :running), create(type, status: :canceled)] - end - - it { is_expected.to eq 'running' } - end - - context 'all canceled' do - let(:statuses) do - [create(type, status: :canceled), create(type, status: :canceled)] - end - it { is_expected.to eq 'canceled' } - end - - context 'success and canceled but allowed to fail' do - let(:statuses) do - [create(type, status: :success), - create(type, status: :canceled, allow_failure: true)] - end - - it { is_expected.to eq 'success' } - end - - context 'one finished and second running but allowed to fail' do - let(:statuses) do - [create(type, status: :success), - create(type, status: :running, allow_failure: true)] - end - - it { is_expected.to eq 'running' } - end - end - - context 'ci build statuses' do - let(:type) { :ci_build } - it_behaves_like 'build status summary' - end - - context 'generic commit statuses' do - let(:type) { :generic_commit_status } - it_behaves_like 'build status summary' - end - end -end -- cgit v1.2.1 From 33694a5a6465785c2fcd5c8217197070cbd7b316 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 2 Aug 2016 10:16:41 -0500 Subject: use gitlab global root url as canonical base url for all javascript set cookies (closes #20435) --- CHANGELOG | 1 + app/assets/javascripts/activities.js | 2 +- app/assets/javascripts/application.js | 4 ++-- app/assets/javascripts/awards_handler.js | 1 + app/assets/javascripts/project.js | 8 ++------ app/assets/javascripts/right_sidebar.js | 2 +- app/assets/javascripts/user.js | 4 +--- spec/javascripts/awards_handler_spec.js | 32 ++++++++++++++++++++++++++++++-- 8 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..245219531aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.12.0 (unreleased) - Add two-factor recovery endpoint to internal API !5510 - Change merge_error column from string to text type - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) + - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add Sentry logging to API calls diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index 1ab3c2197d8..5ea6086ab77 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -26,7 +26,7 @@ event_filters = $.cookie("event_filter"); filter = sender.attr("id").split("_")[0]; $.cookie("event_filter", (event_filters !== filter ? filter : ""), { - path: '/' + path: gon.relative_url_root || '/' }); if (event_filters !== filter) { return sender.closest('li').toggleClass("active"); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index fc354dfd677..43a679501a7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -288,7 +288,7 @@ new Aside(); if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') { $.cookie('pin_nav', 'false', { - path: '/', + path: gon.relative_url_root || '/', expires: 365 * 10 }); $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned'); @@ -313,7 +313,7 @@ $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded'); } $.cookie('pin_nav', doPinNav, { - path: '/', + path: gon.relative_url_root || '/', expires: 365 * 10 }); if ($.cookie('pin_nav') === 'true' || doPinNav) { diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index aee1c29eee3..ad12cb906e1 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -320,6 +320,7 @@ frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); frequentlyUsedEmojis.push(emoji); return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { + path: gon.relative_url_root || '/', expires: 365 }); }; diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 4e1de4dfb72..66e097c0a28 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -17,19 +17,15 @@ return $(this).parents('form').submit(); }); $('.hide-no-ssh-message').on('click', function(e) { - var path; - path = '/'; $.cookie('hide_no_ssh_message', 'false', { - path: path + path: gon.relative_url_root || '/' }); $(this).parents('.no-ssh-key-message').remove(); return e.preventDefault(); }); $('.hide-no-password-message').on('click', function(e) { - var path; - path = '/'; $.cookie('hide_no_password_message', 'false', { - path: path + path: gon.relative_url_root || '/' }); $(this).parents('.no-password-message').remove(); return e.preventDefault(); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index dc4d5113826..e3d5f413c77 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -30,7 +30,7 @@ } if (!triggered) { return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), { - path: '/' + path: gon.relative_url_root || '/' }); } }); diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index b46390ad8f4..6c4d88cf407 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -7,10 +7,8 @@ }); this.initTabs(); $('.hide-project-limit-message').on('click', function(e) { - var path; - path = '/'; $.cookie('hide_project_limit_message', 'false', { - path: path + path: gon.relative_url_root || '/' }); $(this).parents('.project-limit-message').remove(); return e.preventDefault(); diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index fa32d0d7da5..c1c12b57b53 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -11,7 +11,7 @@ /*= require ./fixtures/emoji_menu */ (function() { - var awardsHandler, lazyAssert; + var awardsHandler, lazyAssert, urlRoot; awardsHandler = null; @@ -27,6 +27,7 @@ }; gon.award_menu_url = '/emojis'; + urlRoot = gon.relative_url_root; lazyAssert = function(done, assertFn) { return setTimeout(function() { @@ -45,9 +46,14 @@ return cb(); }; })(this)); - return spyOn(jQuery, 'get').and.callFake(function(req, cb) { + spyOn(jQuery, 'get').and.callFake(function(req, cb) { return cb(window.emojiMenu); }); + spyOn(jQuery, 'cookie'); + }); + afterEach(function() { + // restore original url root value + gon.relative_url_root = urlRoot; }); describe('::showEmojiMenu', function() { it('should show emoji menu when Add emoji button clicked', function(done) { @@ -189,6 +195,28 @@ return expect($thumbsUpEmoji.data("original-title")).toBe('sam'); }); }); + describe('::addEmojiToFrequentlyUsedList', function() { + it('should set a cookie with the correct default path', function() { + gon.relative_url_root = ''; + awardsHandler.addEmojiToFrequentlyUsedList('sunglasses'); + expect(jQuery.cookie) + .toHaveBeenCalledWith('frequently_used_emojis', 'sunglasses', { + path: '/', + expires: 365 + }) + ; + }); + it('should set a cookie with the correct custom root path', function() { + gon.relative_url_root = '/gitlab/subdir'; + awardsHandler.addEmojiToFrequentlyUsedList('alien'); + expect(jQuery.cookie) + .toHaveBeenCalledWith('frequently_used_emojis', 'alien', { + path: '/gitlab/subdir', + expires: 365 + }) + ; + }); + }); describe('search', function() { return it('should filter the emoji', function() { $('.js-add-award').eq(0).click(); -- cgit v1.2.1 From 96051549672084fabec9f67b1cf03c59e8ac3370 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sun, 31 Jul 2016 20:17:19 -0500 Subject: Automatically expand hidden discussions when accessed via a permalink hash (closes #19304) --- CHANGELOG | 1 + .../javascripts/behaviors/toggler_behavior.js | 30 +++++++++++++++++----- app/assets/javascripts/lib/utils/url_utility.js | 12 ++++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..46dfe2fd355 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.12.0 (unreleased) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add Sentry logging to API calls + - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 1b7b63489ea..8ac1ba7665e 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,10 +1,26 @@ -(function() { +(function(w) { $(function() { - return $("body").on("click", ".js-toggle-button", function(e) { - $(this).find('i').toggleClass('fa fa-chevron-down').toggleClass('fa fa-chevron-up'); - $(this).closest(".js-toggle-container").find(".js-toggle-content").toggle(); - return e.preventDefault(); + $('.js-toggle-button').on('click', function(e) { + e.preventDefault(); + $(this) + .find('.fa') + .toggleClass('fa-chevron-down fa-chevron-up') + .end() + .closest('.js-toggle-container') + .find('.js-toggle-content') + .toggle() + ; }); - }); -}).call(this); + // If we're accessing a permalink, ensure it is not inside a + // closed js-toggle-container! + var hash = w.gl.utils.getLocationHash(); + var anchor = hash && document.getElementById(hash); + var container = anchor && $(anchor).closest('.js-toggle-container'); + + if (container && container.find('.js-toggle-content').is(':hidden')) { + container.find('.js-toggle-button').trigger('click'); + anchor.scrollIntoView(); + } + }); +})(window); diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index fffbfd19745..533310cc87c 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -43,7 +43,7 @@ } return newUrl; }; - return w.gl.utils.removeParamQueryString = function(url, param) { + w.gl.utils.removeParamQueryString = function(url, param) { var urlVariables, variables; url = decodeURIComponent(url); urlVariables = url.split('&'); @@ -59,6 +59,16 @@ return results; })()).join('&'); }; + w.gl.utils.getLocationHash = function(url) { + var hashIndex; + if (typeof url === 'undefined') { + // Note: We can't use window.location.hash here because it's + // not consistent across browsers - Firefox will pre-decode it + url = window.location.href; + } + hashIndex = url.indexOf('#'); + return hashIndex === -1 ? null : url.substring(hashIndex + 1); + }; })(window); }).call(this); -- cgit v1.2.1 From f8a7430b00cfe410b0274b8963f0de017ef098ee Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 24 Aug 2016 23:13:38 -0500 Subject: Fix unnecessary horizontal scroll area in pipeline visualizations --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6fa097e3bf1..7eb4e7ec676 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -265,7 +265,6 @@ .pipeline-visualization { position: relative; - min-width: 1220px; ul { padding: 0; -- cgit v1.2.1 From 7629dc9982f5559972acf9d9b9d98f78ad53e54c Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Fri, 12 Aug 2016 17:38:09 +0200 Subject: Add specs to ensure a successful return on the UI when mark as done a already done todo. --- app/services/todo_service.rb | 3 ++- spec/features/todos/todos_spec.rb | 21 +++++++++++++++++++++ spec/services/todo_service_spec.rb | 21 +++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index e0ccb654590..2aab8c736d6 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -148,7 +148,8 @@ class TodoService def mark_todos_as_done_by_ids(ids, current_user) todos = current_user.todos.where(id: ids) - marked_todos = todos.update_all(state: :done) + # Only return those that are not really on that state + marked_todos = todos.where.not(state: :done).update_all(state: :done) current_user.update_todos_count_cache marked_todos end diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 0342f4f1d97..32544f3f538 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -41,6 +41,27 @@ describe 'Dashboard Todos', feature: true do expect(page).to have_content("You're all done!") end end + + context 'todo is stale on the page' do + before do + todos = TodosFinder.new(user, state: :pending).execute + TodoService.new.mark_todos_as_done(todos, user) + end + + describe 'deleting the todo' do + before do + first('.done-todo').click + end + + it 'is removed from the list' do + expect(page).not_to have_selector('.todos-list .todo') + end + + it 'shows "All done" message' do + expect(page).to have_content("You're all done!") + end + end + end end context 'User has Todos with labels spanning multiple projects' do diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 296fd1bd5a4..cafcad3e3c0 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -496,6 +496,7 @@ describe TodoService, services: true do describe '#mark_todos_as_done' do let(:issue) { create(:issue, project: project, author: author, assignee: john_doe) } + let(:another_issue) { create(:issue, project: project, author: author, assignee: john_doe) } it 'marks a relation of todos as done' do create(:todo, :mentioned, user: john_doe, target: issue, project: project) @@ -518,6 +519,26 @@ describe TodoService, services: true do expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1) end + context 'when some of the todos are done already' do + before do + create(:todo, :mentioned, user: john_doe, target: issue, project: project) + create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) + end + + it 'returns the number of those still pending' do + TodoService.new.mark_pending_todos_as_done(issue, john_doe) + + expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(1) + end + + it 'returns 0 if all are done' do + TodoService.new.mark_pending_todos_as_done(issue, john_doe) + TodoService.new.mark_pending_todos_as_done(another_issue, john_doe) + + expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq(0) + end + end + it 'caches the number of todos of a user', :caching do create(:todo, :mentioned, user: john_doe, target: issue, project: project) todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) -- cgit v1.2.1 From 89fcdbe84c8591b72a2730658ece24f94551a1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Steinblock?= Date: Thu, 25 Aug 2016 04:56:48 +0000 Subject: removed redundant word --- doc/workflow/merge_requests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index 91fcfb02d39..334a119e522 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -53,7 +53,7 @@ It should look like this: fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/* ``` -Now you can fetch all the merge requests requests: +Now you can fetch all the merge requests: ``` $ git fetch origin -- cgit v1.2.1 From dd8afbf05d0727a061e8d7bc1bc3c1db5a666116 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 25 Aug 2016 15:04:15 +0800 Subject: Just use instance variable instead, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14400736 --- app/controllers/projects/artifacts_controller.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 17c6d56c8b9..4c63bec90e5 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -37,8 +37,7 @@ class Projects::ArtifactsController < Projects::ApplicationController end def latest_succeeded - path = ref_name_and_path.last - target_path = artifacts_action_path(path, project, build) + target_path = artifacts_action_path(@path, project, build) if target_path redirect_to(target_path) @@ -63,17 +62,13 @@ class Projects::ArtifactsController < Projects::ApplicationController def build_from_ref if params[:ref_name_and_path] - ref_name = ref_name_and_path.first + ref_name, @path = extract_ref(params[:ref_name_and_path]) builds = project.latest_successful_builds_for(ref_name) builds.find_by(name: params[:job]) end end - def ref_name_and_path - @ref_name_and_path ||= extract_ref(params[:ref_name_and_path]) - end - def artifacts_file @artifacts_file ||= build.artifacts_file end -- cgit v1.2.1 From 44eb3197a9c30503a00384b3d688b64558b80397 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 23 Aug 2016 16:37:14 +0100 Subject: Handle non-UTF-8 conflicts gracefully These can't be resolved in the UI because if they aren't in a UTF-8 compatible encoding, they can't be rendered as JSON. Even if they could, we would be implicitly changing the file encoding anyway, which seems like a bad idea. --- CHANGELOG | 3 +++ doc/user/project/merge_requests/resolve_conflicts.md | 1 + lib/gitlab/conflict/parser.rb | 9 +++++++++ spec/features/merge_requests/conflicts_spec.rb | 3 ++- spec/lib/gitlab/conflict/parser_spec.rb | 5 +++++ spec/support/test_env.rb | 3 ++- 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..05517de501a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,9 @@ v 8.12.0 (unreleased) - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) +v 8.11.3 (unreleased) + - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters + v 8.11.2 (unreleased) - Show "Create Merge Request" widget for push events to fork projects on the source project diff --git a/doc/user/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md index 44b76ffc8e6..4d7225bd820 100644 --- a/doc/user/project/merge_requests/resolve_conflicts.md +++ b/doc/user/project/merge_requests/resolve_conflicts.md @@ -26,6 +26,7 @@ this is similar to performing `git checkout feature; git merge master` locally. GitLab allows resolving conflicts in a file where all of the below are true: - The file is text, not binary +- The file is in a UTF-8 compatible encoding - The file does not already contain conflict markers - The file, with conflict markers added, is not over 200 KB in size - The file exists under the same path in both branches diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 6eccded7872..2d4d55daeeb 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -13,10 +13,19 @@ module Gitlab class UnmergeableFile < ParserError end + class UnsupportedEncoding < ParserError + end + def parse(text, our_path:, their_path:, parent_file: nil) raise UnmergeableFile if text.blank? # Typically a binary file raise UnmergeableFile if text.length > 102400 + begin + text.to_json + rescue Encoding::UndefinedConversionError + raise UnsupportedEncoding + end + line_obj_index = 0 line_old = 1 line_new = 1 diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 930c36ade2b..759edf8ec80 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -43,7 +43,8 @@ feature 'Merge request conflict resolution', js: true, feature: true do 'conflict-too-large' => 'when the conflicts contain a large file', 'conflict-binary-file' => 'when the conflicts contain a binary file', 'conflict-contains-conflict-markers' => 'when the conflicts contain a file with ambiguous conflict markers', - 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another' + 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another', + 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file', } UNRESOLVABLE_CONFLICTS.each do |source_branch, description| diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb index 65a828accde..a1d2ca1e272 100644 --- a/spec/lib/gitlab/conflict/parser_spec.rb +++ b/spec/lib/gitlab/conflict/parser_spec.rb @@ -183,6 +183,11 @@ CONFLICT expect { parse_text('a' * 102401) }. to raise_error(Gitlab::Conflict::Parser::UnmergeableFile) end + + it 'raises UnsupportedEncoding when the file contains non-UTF-8 characters' do + expect { parse_text("a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) }. + to raise_error(Gitlab::Conflict::Parser::UnsupportedEncoding) + end end end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index edbbfc3c9e5..c7a45fc4ff9 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -24,11 +24,12 @@ module TestEnv 'expand-collapse-lines' => '238e82d', 'video' => '8879059', 'crlf-diff' => '5938907', - 'conflict-start' => '14fa46b', + 'conflict-start' => '75284c7', 'conflict-resolvable' => '1450cd6', 'conflict-binary-file' => '259a6fb', 'conflict-contains-conflict-markers' => '5e0964c', 'conflict-missing-side' => 'eb227b3', + 'conflict-non-utf8' => 'd0a293c', 'conflict-too-large' => '39fa04f', } -- cgit v1.2.1 From 085e1f89f327ad4d679696fe4673d09e1840b236 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 24 Aug 2016 18:06:19 +0100 Subject: Handle unavailable system info For Linux with the grsecurity patches applied, paths in `/proc` may not be readable, so handle those cases and show a message rather than blowing up. --- CHANGELOG | 3 ++ app/controllers/admin/system_info_controller.rb | 8 ++--- app/views/admin/system_info/show.html.haml | 12 +++++-- spec/features/admin/admin_system_info_spec.rb | 47 ++++++++++++++++++++++--- 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..f43eeb34d37 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,9 @@ v 8.12.0 (unreleased) - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) +v 8.11.3 (unreleased) + - Allow system info page to handle case where info is unavailable + v 8.11.2 (unreleased) - Show "Create Merge Request" widget for push events to fork projects on the source project diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb index e4c73008826..ca04a17caa1 100644 --- a/app/controllers/admin/system_info_controller.rb +++ b/app/controllers/admin/system_info_controller.rb @@ -29,7 +29,8 @@ class Admin::SystemInfoController < Admin::ApplicationController ] def show - system_info = Vmstat.snapshot + @cpus = Vmstat.cpu rescue nil + @memory = Vmstat.memory rescue nil mounts = Sys::Filesystem.mounts @disks = [] @@ -50,10 +51,5 @@ class Admin::SystemInfoController < Admin::ApplicationController rescue Sys::Filesystem::Error end end - - @cpus = system_info.cpus.length - - @mem_used = system_info.memory.active_bytes - @mem_total = system_info.memory.total_bytes end end diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index 6956e5ab795..7f3b95699db 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -9,12 +9,20 @@ .light-well %h4 CPU .data - %h1= "#{@cpus} cores" + - if @cpus + %h1= "#{@cpus.length} cores" + - else + %i.fa.fa-warning.text-warning + Unable to collect CPU info .col-sm-4 .light-well %h4 Memory .data - %h1= "#{number_to_human_size(@mem_used)} / #{number_to_human_size(@mem_total)}" + - if @memory + %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}" + - else + %i.fa.fa-warning.text-warning + Unable to collect memory info .col-sm-4 .light-well %h4 Disks diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb index f4e5c26b519..1df972843e2 100644 --- a/spec/features/admin/admin_system_info_spec.rb +++ b/spec/features/admin/admin_system_info_spec.rb @@ -6,12 +6,49 @@ describe 'Admin System Info' do end describe 'GET /admin/system_info' do - it 'shows system info page' do - visit admin_system_info_path + let(:cpu) { double(:cpu, length: 2) } + let(:memory) { double(:memory, active_bytes: 4294967296, total_bytes: 17179869184) } - expect(page).to have_content 'CPU' - expect(page).to have_content 'Memory' - expect(page).to have_content 'Disks' + context 'when all info is available' do + before do + allow(Vmstat).to receive(:cpu).and_return(cpu) + allow(Vmstat).to receive(:memory).and_return(memory) + visit admin_system_info_path + end + + it 'shows system info page' do + expect(page).to have_content 'CPU 2 cores' + expect(page).to have_content 'Memory 4 GB / 16 GB' + expect(page).to have_content 'Disks' + end + end + + context 'when CPU info is not available' do + before do + allow(Vmstat).to receive(:cpu).and_raise(Errno::ENOENT) + allow(Vmstat).to receive(:memory).and_return(memory) + visit admin_system_info_path + end + + it 'shows system info page with no CPU info' do + expect(page).to have_content 'CPU Unable to collect CPU info' + expect(page).to have_content 'Memory 4 GB / 16 GB' + expect(page).to have_content 'Disks' + end + end + + context 'when memory info is not available' do + before do + allow(Vmstat).to receive(:cpu).and_return(cpu) + allow(Vmstat).to receive(:memory).and_raise(Errno::ENOENT) + visit admin_system_info_path + end + + it 'shows system info page with no CPU info' do + expect(page).to have_content 'CPU 2 cores' + expect(page).to have_content 'Memory Unable to collect memory info' + expect(page).to have_content 'Disks' + end end end end -- cgit v1.2.1 From 3d98494815b9cb574a82c1e8b566ac9c2fadc1da Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 25 Aug 2016 08:30:17 +0100 Subject: Update vmstat for {Open,Net}BSD support --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 68547b6fac8..194379dd687 100644 --- a/Gemfile +++ b/Gemfile @@ -349,5 +349,5 @@ gem 'paranoia', '~> 2.0' gem 'health_check', '~> 2.1.0' # System information -gem 'vmstat', '~> 2.1.1' +gem 'vmstat', '~> 2.2' gem 'sys-filesystem', '~> 1.1.6' diff --git a/Gemfile.lock b/Gemfile.lock index 5511d718938..0c28975060c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -772,7 +772,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - vmstat (2.1.1) + vmstat (2.2.0) warden (1.2.6) rack (>= 1.0) web-console (2.3.0) @@ -980,7 +980,7 @@ DEPENDENCIES unicorn-worker-killer (~> 0.4.2) version_sorter (~> 2.1.0) virtus (~> 1.0.1) - vmstat (~> 2.1.1) + vmstat (~> 2.2) web-console (~> 2.0) webmock (~> 1.21.0) wikicloth (= 0.8.1) -- cgit v1.2.1 From 6a2d2bd18d0a07c33200d74a09b7cf9adcb7a84d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 25 Aug 2016 15:41:37 +0800 Subject: Add a download icon for artifacts, too. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14400694 --- app/views/projects/buttons/_download.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 73dcb9c079e..5f5e071eb40 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -38,4 +38,5 @@ - artifacts.each do |job| %li = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do + %i.fa.fa-download %span Download '#{job.name}' -- cgit v1.2.1 From d64c15c5c391e8f49a9f261ba0e59bafb99d97fe Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 25 Aug 2016 10:59:30 +0300 Subject: Add code improvements to merge request version feature * Add MergeRequestDiff#latest? * Remove unnecessary variable assignment Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/merge_requests_controller.rb | 2 +- app/models/merge_request_diff.rb | 16 ++++++++-------- .../projects/merge_requests/show/_versions.html.haml | 3 +-- spec/models/merge_request_diff_spec.rb | 9 +++++++++ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index eff959c781c..3be9bdb076d 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -93,7 +93,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html { define_discussion_vars } format.json do - if @merge_request_diff != @merge_request.merge_request_diff + unless @merge_request_diff.latest? # Disable comments if browsing older version of the diff @diff_notes_disabled = true end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 42ab6b620bd..e353bdb24b8 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -79,14 +79,10 @@ class MergeRequestDiff < ActiveRecord::Base def raw_diffs(options = {}) if options[:ignore_whitespace_change] @diffs_no_whitespace ||= - begin - compare = Gitlab::Git::Compare.new( - repository.raw_repository, - safe_start_commit_sha, - safe_head_commit_sha - ) - compare.diffs(options) - end + Gitlab::Git::Compare.new( + repository.raw_repository, + safe_start_commit_sha, + safe_head_commit_sha).diffs(options) else @raw_diffs ||= {} @raw_diffs[options] ||= load_diffs(st_diffs, options) @@ -164,6 +160,10 @@ class MergeRequestDiff < ActiveRecord::Base end end + def latest? + self == merge_request.merge_request_diff + end + private def dump_commits(commits) diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 15d94b16ced..08c54f0aeff 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,5 +1,4 @@ - merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff -- latest_diff = merge_request_diffs.first - if merge_request_diffs.size > 1 .mr-version-switch @@ -7,7 +6,7 @@ %span.dropdown.inline %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } %strong.monospace< - - if latest_diff == @merge_request_diff + - if @merge_request_diff.latest? #{"latest"} - else #{@merge_request_diff.head_commit.short_id} diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 2f3bb3ed2c4..e5b185dc3f6 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -13,6 +13,15 @@ describe MergeRequestDiff, models: true do it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } end + describe '#latest' do + let!(:mr) { create(:merge_request, :with_diffs) } + let!(:first_diff) { mr.merge_request_diff } + let!(:last_diff) { mr.create_merge_request_diff } + + it { expect(last_diff.latest?).to be_truthy } + it { expect(first_diff.latest?).to be_falsey } + end + describe '#diffs' do let(:mr) { create(:merge_request, :with_diffs) } let(:mr_diff) { mr.merge_request_diff } -- cgit v1.2.1 From 70fe671c299513a43797dc1e7ab0e32f14503175 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 25 Aug 2016 11:16:03 +0300 Subject: Make it more obvious when the user isn't currently viewing the latest mr version Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/merge_requests/show/_versions.html.haml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 08c54f0aeff..2da70ce7137 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -22,6 +22,10 @@ #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, = time_ago_with_tooltip(merge_request_diff.created_at) + - unless @merge_request_diff.latest? + %span.prepend-left-default + = icon('info-circle') + This version is not the latest one. Comments are disabled .pull-right %span.monospace #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} -- cgit v1.2.1 From cad63d4045623391d5e36ff541d531b6dfae7d81 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Aug 2016 10:21:51 +0200 Subject: Use new image for Issue Board page [ci skip] --- doc/user/project/img/issue_board.png | Bin 200614 -> 275093 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png index accd0900cbb..63c269f6dbc 100644 Binary files a/doc/user/project/img/issue_board.png and b/doc/user/project/img/issue_board.png differ -- cgit v1.2.1 From a943ccf10ecf2af3331927cb83268fbc70f43634 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 25 Aug 2016 11:58:17 +0300 Subject: Change the way merge request diff compare works * remove ref fetch (we do it during creation anyway) * remove safe_head_commit_sha for diff compare (do not depend on the source branch) Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index e353bdb24b8..445179a4487 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -65,13 +65,6 @@ class MergeRequestDiff < ActiveRecord::Base start_commit_sha || merge_request.target_branch_sha end - # This method will rely on repository branch sha - # in case head_commit_sha is nil. Its necesarry for old merge request diff - # created before version 8.4 to work - def safe_head_commit_sha - head_commit_sha || merge_request.source_branch_sha - end - def size real_size.presence || raw_diffs.size end @@ -82,7 +75,7 @@ class MergeRequestDiff < ActiveRecord::Base Gitlab::Git::Compare.new( repository.raw_repository, safe_start_commit_sha, - safe_head_commit_sha).diffs(options) + head_commit_sha).diffs(options) else @raw_diffs ||= {} @raw_diffs[options] ||= load_diffs(st_diffs, options) @@ -148,16 +141,11 @@ class MergeRequestDiff < ActiveRecord::Base def compare @compare ||= - begin - # Update ref for merge request - merge_request.fetch_ref - - Gitlab::Git::Compare.new( - repository.raw_repository, - safe_start_commit_sha, - safe_head_commit_sha - ) - end + Gitlab::Git::Compare.new( + repository.raw_repository, + safe_start_commit_sha, + head_commit_sha + ) end def latest? -- cgit v1.2.1 From 4c8e9a8d27c34fe97216e12d17782f982818fb5c Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 18 Aug 2016 15:00:20 +0200 Subject: Remove gitorious --- CHANGELOG | 1 + app/assets/stylesheets/pages/import.scss | 19 ------ app/controllers/application_controller.rb | 6 +- app/controllers/import/gitorious_controller.rb | 47 --------------- app/models/application_setting.rb | 2 +- app/views/import/gitorious/status.html.haml | 54 ----------------- app/views/projects/new.html.haml | 5 -- config/initializers/1_settings.rb | 2 +- config/routes.rb | 6 -- doc/workflow/gitlab_flow.md | 2 +- features/steps/dashboard/new_project.rb | 1 - lib/gitlab/current_settings.rb | 2 +- lib/gitlab/gitorious_import.rb | 5 -- lib/gitlab/gitorious_import/client.rb | 29 --------- lib/gitlab/gitorious_import/project_creator.rb | 27 --------- lib/gitlab/gitorious_import/repository.rb | 35 ----------- lib/gitlab/import_sources.rb | 13 ++-- .../import/gitorious_controller_spec.rb | 69 ---------------------- .../gitorious_import/project_creator_spec.rb | 26 -------- 19 files changed, 12 insertions(+), 339 deletions(-) delete mode 100644 app/controllers/import/gitorious_controller.rb delete mode 100644 app/views/import/gitorious/status.html.haml delete mode 100644 lib/gitlab/gitorious_import.rb delete mode 100644 lib/gitlab/gitorious_import/client.rb delete mode 100644 lib/gitlab/gitorious_import/project_creator.rb delete mode 100644 lib/gitlab/gitorious_import/repository.rb delete mode 100644 spec/controllers/import/gitorious_controller_spec.rb delete mode 100644 spec/lib/gitlab/gitorious_import/project_creator_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..d0344837308 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.12.0 (unreleased) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) + - Remove Gitorious import - Add Sentry logging to API calls - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss index 84cc35239f9..a4f76a9495a 100644 --- a/app/assets/stylesheets/pages/import.scss +++ b/app/assets/stylesheets/pages/import.scss @@ -1,22 +1,3 @@ -i.icon-gitorious { - display: inline-block; - background-position: 0 0; - background-size: contain; - background-repeat: no-repeat; -} - -i.icon-gitorious-small { - background-image: image-url('gitorious-logo-blue.png'); - width: 13px; - height: 13px; -} - -i.icon-gitorious-big { - background-image: image-url('gitorious-logo-black.png'); - width: 18px; - height: 18px; -} - .import-jobs-from-col, .import-jobs-to-col { width: 40%; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 70a2275592b..ebc2a4651ba 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :abilities, :can?, :current_application_settings - helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? + helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -250,10 +250,6 @@ class ApplicationController < ActionController::Base Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present? end - def gitorious_import_enabled? - current_application_settings.import_sources.include?('gitorious') - end - def google_code_import_enabled? current_application_settings.import_sources.include?('google_code') end diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb deleted file mode 100644 index a4c4ad23027..00000000000 --- a/app/controllers/import/gitorious_controller.rb +++ /dev/null @@ -1,47 +0,0 @@ -class Import::GitoriousController < Import::BaseController - before_action :verify_gitorious_import_enabled - - def new - redirect_to client.authorize_url(callback_import_gitorious_url) - end - - def callback - session[:gitorious_repos] = params[:repos] - redirect_to status_import_gitorious_path - end - - def status - @repos = client.repos - - @already_added_projects = current_user.created_projects.where(import_type: "gitorious") - already_added_projects_names = @already_added_projects.pluck(:import_source) - - @repos.reject! { |repo| already_added_projects_names.include? repo.full_name } - end - - def jobs - jobs = current_user.created_projects.where(import_type: "gitorious").to_json(only: [:id, :import_status]) - render json: jobs - end - - def create - @repo_id = params[:repo_id] - repo = client.repo(@repo_id) - @target_namespace = params[:new_namespace].presence || repo.namespace - @project_name = repo.name - - namespace = get_or_create_namespace || (render and return) - - @project = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, current_user).execute - end - - private - - def client - @client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos]) - end - - def verify_gitorious_import_enabled - render_404 unless gitorious_import_enabled? - end -end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index f0bcb2d7cda..246477ffe88 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -146,7 +146,7 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project], + import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml deleted file mode 100644 index ed3afb0ce33..00000000000 --- a/app/views/import/gitorious/status.html.haml +++ /dev/null @@ -1,54 +0,0 @@ -- page_title "Gitorious import" -- header_title "Projects", root_path -%h3.page-title - %i.icon-gitorious.icon-gitorious-big - Import projects from Gitorious.org - -%p.light - Select projects you want to import. -%hr -%p - = button_tag class: "btn btn-import btn-success js-import-all" do - Import all projects - = icon("spinner spin", class: "loading-icon") - -.table-responsive - %table.table.import-jobs - %colgroup.import-jobs-from-col - %colgroup.import-jobs-to-col - %colgroup.import-jobs-status-col - %thead - %tr - %th From Gitorious.org - %th To GitLab - %th Status - %tbody - - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td - = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" - %td - = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] - %td.job-status - - if project.import_status == 'finished' - %span - %i.fa.fa-check - done - - elsif project.import_status == 'started' - %i.fa.fa-spinner.fa-spin - started - - else - = project.human_import_status_name - - - @repos.each do |repo| - %tr{id: "repo_#{repo.id}"} - %td - = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank" - %td.import-target - = repo.full_name - %td.import-actions.job-status - = button_tag class: "btn btn-import js-add-to-import" do - Import - = icon("spinner spin", class: "loading-icon") - -.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } } diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index ea4898f2107..0a1e2bb2cc6 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -59,11 +59,6 @@ = icon('gitlab', text: 'GitLab.com') - unless gitlab_import_configured? = render 'gitlab_import_modal' - %div - - if gitorious_import_enabled? - = link_to new_import_gitorious_path, class: 'btn import_gitorious' do - %i.icon-gitorious.icon-gitorious-small - Gitorious.org %div - if google_code_import_enabled? = link_to new_import_google_code_path, class: 'btn import_google_code' do diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 7a9376def02..4a01b9e40fb 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -212,7 +212,7 @@ Settings.gitlab.default_projects_features['builds'] = true if Settin Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil? Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['domain_whitelist'] ||= [] -Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project] +Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab google_code fogbugz git gitlab_project] Settings.gitlab['trusted_proxies'] ||= [] # diff --git a/config/routes.rb b/config/routes.rb index e93b640fbc0..24f9b44a53a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -157,12 +157,6 @@ Rails.application.routes.draw do get :jobs end - resource :gitorious, only: [:create, :new], controller: :gitorious do - get :status - get :callback - get :jobs - end - resource :google_code, only: [:create, :new], controller: :google_code do get :status post :callback diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 8119324bb62..7c0eb90d540 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -115,7 +115,7 @@ In this flow it is not common to have a production branch (or git flow master br Merge or pull requests are created in a git management application and ask an assigned person to merge two branches. Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch. -Tools such as GitLab and Gitorious choose the name merge request since that is the final action that is requested of the assignee. +Tools such as GitLab and others choose the name merge request since that is the final action that is requested of the assignee. In this article we'll refer to them as merge requests. If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team. diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index f0d8d498e46..2f0941e4113 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -18,7 +18,6 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps expect(page).to have_link('GitHub') expect(page).to have_link('Bitbucket') expect(page).to have_link('GitLab.com') - expect(page).to have_link('Gitorious.org') expect(page).to have_link('Google Code') expect(page).to have_link('Repo by URL') end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 27acd817e51..12fbb78c53e 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -41,7 +41,7 @@ module Gitlab default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project], + import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, diff --git a/lib/gitlab/gitorious_import.rb b/lib/gitlab/gitorious_import.rb deleted file mode 100644 index 8d0132a744c..00000000000 --- a/lib/gitlab/gitorious_import.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Gitlab - module GitoriousImport - GITORIOUS_HOST = "https://gitorious.org" - end -end diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb deleted file mode 100644 index 99fe5bdebfc..00000000000 --- a/lib/gitlab/gitorious_import/client.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Gitlab - module GitoriousImport - class Client - attr_reader :repo_list - - def initialize(repo_list) - @repo_list = repo_list - end - - def authorize_url(redirect_uri) - "#{GITORIOUS_HOST}/gitlab-import?callback_url=#{redirect_uri}" - end - - def repos - @repos ||= repo_names.map { |full_name| GitoriousImport::Repository.new(full_name) } - end - - def repo(id) - repos.find { |repo| repo.id == id } - end - - private - - def repo_names - repo_list.to_s.split(',').map(&:strip).reject(&:blank?) - end - end - end -end diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb deleted file mode 100644 index 8e22aa9286d..00000000000 --- a/lib/gitlab/gitorious_import/project_creator.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Gitlab - module GitoriousImport - class ProjectCreator - attr_reader :repo, :namespace, :current_user - - def initialize(repo, namespace, current_user) - @repo = repo - @namespace = namespace - @current_user = current_user - end - - def execute - ::Projects::CreateService.new( - current_user, - name: repo.name, - path: repo.path, - description: repo.description, - namespace_id: namespace.id, - visibility_level: Gitlab::VisibilityLevel::PUBLIC, - import_type: "gitorious", - import_source: repo.full_name, - import_url: repo.import_url - ).execute - end - end - end -end diff --git a/lib/gitlab/gitorious_import/repository.rb b/lib/gitlab/gitorious_import/repository.rb deleted file mode 100644 index c88f1ae358d..00000000000 --- a/lib/gitlab/gitorious_import/repository.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Gitlab - module GitoriousImport - Repository = Struct.new(:full_name) do - def id - Digest::SHA1.hexdigest(full_name) - end - - def namespace - segments.first - end - - def path - segments.last - end - - def name - path.titleize - end - - def description - "" - end - - def import_url - "#{GITORIOUS_HOST}/#{full_name}.git" - end - - private - - def segments - full_name.split('/') - end - end - end -end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 59a05411fe9..94261b7eeed 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -14,13 +14,12 @@ module Gitlab def options { - 'GitHub' => 'github', - 'Bitbucket' => 'bitbucket', - 'GitLab.com' => 'gitlab', - 'Gitorious.org' => 'gitorious', - 'Google Code' => 'google_code', - 'FogBugz' => 'fogbugz', - 'Repo by URL' => 'git', + 'GitHub' => 'github', + 'Bitbucket' => 'bitbucket', + 'GitLab.com' => 'gitlab', + 'Google Code' => 'google_code', + 'FogBugz' => 'fogbugz', + 'Repo by URL' => 'git', 'GitLab export' => 'gitlab_project' } end diff --git a/spec/controllers/import/gitorious_controller_spec.rb b/spec/controllers/import/gitorious_controller_spec.rb deleted file mode 100644 index 4ae2b78e11c..00000000000 --- a/spec/controllers/import/gitorious_controller_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'spec_helper' - -describe Import::GitoriousController do - include ImportSpecHelper - - let(:user) { create(:user) } - - before do - sign_in(user) - end - - describe "GET new" do - it "redirects to import endpoint on gitorious.org" do - get :new - - expect(controller).to redirect_to("https://gitorious.org/gitlab-import?callback_url=http://test.host/import/gitorious/callback") - end - end - - describe "GET callback" do - it "stores repo list in session" do - get :callback, repos: 'foo/bar,baz/qux' - - expect(session[:gitorious_repos]).to eq('foo/bar,baz/qux') - end - end - - describe "GET status" do - before do - @repo = OpenStruct.new(full_name: 'asd/vim') - end - - it "assigns variables" do - @project = create(:project, import_type: 'gitorious', creator_id: user.id) - stub_client(repos: [@repo]) - - get :status - - expect(assigns(:already_added_projects)).to eq([@project]) - expect(assigns(:repos)).to eq([@repo]) - end - - it "does not show already added project" do - @project = create(:project, import_type: 'gitorious', creator_id: user.id, import_source: 'asd/vim') - stub_client(repos: [@repo]) - - get :status - - expect(assigns(:already_added_projects)).to eq([@project]) - expect(assigns(:repos)).to eq([]) - end - end - - describe "POST create" do - before do - @repo = Gitlab::GitoriousImport::Repository.new('asd/vim') - end - - it "takes already existing namespace" do - namespace = create(:namespace, name: "asd", owner: user) - expect(Gitlab::GitoriousImport::ProjectCreator). - to receive(:new).with(@repo, namespace, user). - and_return(double(execute: true)) - stub_client(repo: @repo) - - post :create, format: :js - end - end -end diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb deleted file mode 100644 index 946712ca38e..00000000000 --- a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'spec_helper' - -describe Gitlab::GitoriousImport::ProjectCreator, lib: true do - let(:user) { create(:user) } - let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') } - let(:namespace){ create(:group, owner: user) } - - before do - namespace.add_owner(user) - end - - it 'creates project' do - allow_any_instance_of(Project).to receive(:add_import_job) - - project_creator = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, user) - project = project_creator.execute - - expect(project.name).to eq("Bar Baz Qux") - expect(project.path).to eq("bar-baz-qux") - expect(project.namespace).to eq(namespace) - expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) - expect(project.import_type).to eq("gitorious") - expect(project.import_source).to eq("foo/bar-baz-qux") - expect(project.import_url).to eq("https://gitorious.org/foo/bar-baz-qux.git") - end -end -- cgit v1.2.1 From 4a3d1a5838685ff95a6c9416bb3e453ad143f50d Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 24 Aug 2016 15:20:06 +0100 Subject: Handle case where conflicts aren't on disk yet --- app/models/merge_request.rb | 2 +- lib/gitlab/conflict/file.rb | 11 +++++++++++ spec/models/merge_request_spec.rb | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5330a07ee35..7fa52d0b7a7 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -778,7 +778,7 @@ class MergeRequest < ActiveRecord::Base begin @conflicts_can_be_resolved_in_ui = conflicts.files.each(&:lines) - rescue Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing + rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing @conflicts_can_be_resolved_in_ui = false end end diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 0a1fd27ced5..dff9e29c6a5 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -181,6 +181,17 @@ module Gitlab sections: sections } end + + # Don't try to print merge_request or repository. + def inspect + instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode].map do |instance_variable| + value = instance_variable_get("@#{instance_variable}") + + "#{instance_variable}=\"#{value}\"" + end + + "#<#{self.class} #{instance_variables.join(' ')}>" + end end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 64c56d922ff..ae71eaeda52 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -890,6 +890,13 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey end + it 'returns a falsey value when the MR has a missing ref after a force push' do + merge_request = create_merge_request('conflict-resolvable') + allow(merge_request.conflicts).to receive(:merge_index).and_raise(Rugged::OdbError) + + expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey + end + it 'returns a falsey value when the MR does not support new diff notes' do merge_request = create_merge_request('conflict-resolvable') merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) -- cgit v1.2.1 From 67beaf6fb989e4f7981769b1f01bf383fc0de652 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 25 Aug 2016 12:32:35 +0100 Subject: Don't show conflicts when there are none --- app/models/merge_request.rb | 6 +++++- spec/models/merge_request_spec.rb | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7fa52d0b7a7..19a684d1d0c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -777,7 +777,11 @@ class MergeRequest < ActiveRecord::Base return @conflicts_can_be_resolved_in_ui = false unless has_complete_diff_refs? begin - @conflicts_can_be_resolved_in_ui = conflicts.files.each(&:lines) + # Try to parse each conflict. If the MR's mergeable status hasn't been updated, + # ensure that we don't say there are conflicts to resolve when there are no conflict + # files. + conflicts.files.each(&:lines) + @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing @conflicts_can_be_resolved_in_ui = false end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index ae71eaeda52..c4138c4f3f3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -890,6 +890,12 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey end + it 'returns a falsey value when the MR is marked as having conflicts, but has none' do + merge_request = create_merge_request('master') + + expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey + end + it 'returns a falsey value when the MR has a missing ref after a force push' do merge_request = create_merge_request('conflict-resolvable') allow(merge_request.conflicts).to receive(:merge_index).and_raise(Rugged::OdbError) -- cgit v1.2.1 From 0d0d46dce2a933f0060b2b05216e53197a3d449b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 25 Aug 2016 12:36:06 +0100 Subject: Update CHANGELOG --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..9a7753d691c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,10 @@ v 8.12.0 (unreleased) - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) +v 8.11.3 (unreleased) + - Don't show resolve conflicts link before MR status is updated + - Don't prevent viewing the MR when git refs for conflicts can't be found on disk + v 8.11.2 (unreleased) - Show "Create Merge Request" widget for push events to fork projects on the source project -- cgit v1.2.1 From 01a1e3925acf01b925ad3b8ca370db30384111d8 Mon Sep 17 00:00:00 2001 From: Miroslav Meca Date: Thu, 25 Aug 2016 12:01:52 +0000 Subject: Update projects.md The wrong example for "Branches". Added option parameters in protect branch section. Here is reason: https://gitlab.com/gitlab-org/gitlab-ee/commit/3ab07b8aae8dae43cfa3aae1306c59ea264a8594 Maybe this section could/should be deleted. Because in file repositories.md it had been deleted: https://gitlab.com/gitlab-org/gitlab-ee/commit/8f3701eff005aeedcebff8ce02074f5056a369b3 --- doc/api/projects.md | 54 ++++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 0e4806e31c5..eacdb1e1ee6 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -998,6 +998,8 @@ is available before it is returned in the JSON response or an empty response is ## Branches +For more information please consult the [Branches](branches.md) documentation. + ### List branches Lists all branches of a project. @@ -1016,56 +1018,46 @@ Parameters: "name": "async", "commit": { "id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca", - "parents": [ - { - "id": "3f94fc7c85061973edc9906ae170cc269b07ca55" - } + "parent_ids": [ + "3f94fc7c85061973edc9906ae170cc269b07ca55" ], - "tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee", "message": "give Caolan credit where it's due (up top)", - "author": { - "name": "Jeremy Ashkenas", - "email": "jashkenas@example.com" - }, - "committer": { - "name": "Jeremy Ashkenas", - "email": "jashkenas@example.com" - }, + "author_name": "Jeremy Ashkenas", + "author_email": "jashkenas@example.com", "authored_date": "2010-12-08T21:28:50+00:00", + "committer_name": "Jeremy Ashkenas", + "committer_email": "jashkenas@example.com", "committed_date": "2010-12-08T21:28:50+00:00" }, - "protected": false + "protected": false, + "developers_can_push": false, + "developers_can_merge": false }, { "name": "gh-pages", "commit": { "id": "101c10a60019fe870d21868835f65c25d64968fc", - "parents": [ - { - "id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa" - } + "parent_ids": [ + "9c15d2e26945a665131af5d7b6d30a06ba338aaa" ], - "tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a", "message": "Underscore.js 1.5.2", - "author": { - "name": "Jeremy Ashkenas", - "email": "jashkenas@example.com" - }, - "committer": { - "name": "Jeremy Ashkenas", - "email": "jashkenas@example.com" - }, + "author_name": "Jeremy Ashkenas", + "author_email": "jashkenas@example.com", "authored_date": "2013-09-07T12:58:21+00:00", + "committer_name": "Jeremy Ashkenas", + "committer_email": "jashkenas@example.com", "committed_date": "2013-09-07T12:58:21+00:00" }, - "protected": false + "protected": false, + "developers_can_push": false, + "developers_can_merge": false } ] ``` -### List single branch +### Single branch -Lists a specific branch of a project. +A specific branch of a project. ``` GET /projects/:id/repository/branches/:branch @@ -1075,6 +1067,8 @@ Parameters: - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project - `branch` (required) - The name of the branch. +- `developers_can_push` - Flag if developers can push to the branch. +- `developers_can_merge` - Flag if developers can merge to the branch. ### Protect single branch -- cgit v1.2.1 From 3bfb94b7593c3e14fa974701d733d6dc3c744bd2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 24 Aug 2016 14:57:57 -0300 Subject: Label list shows all issues (opened or closed) with that label --- app/services/boards/issues/list_service.rb | 7 ++++++- spec/services/boards/issues/list_service_spec.rb | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 435a8c6e681..34efd09ed9f 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -36,7 +36,12 @@ module Boards end def set_state - params[:state] = list.done? ? 'closed' : 'opened' + params[:state] = + case list.list_type.to_sym + when :backlog then 'opened' + when :done then 'closed' + else 'all' + end end def board_label_ids diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index f7f45983d26..cf4c5f13635 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -30,7 +30,7 @@ describe Boards::Issues::ListService, services: true do let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) } let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) } let!(:closed_issue3) { create(:issue, :closed, project: project) } - let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) } + let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1, development]) } before do project.team << [user, :developer] @@ -58,15 +58,15 @@ describe Boards::Issues::ListService, services: true do issues = described_class.new(project, user, params).execute - expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1] + expect(issues).to eq [closed_issue2, closed_issue3, closed_issue1] end - it 'returns opened issues that have label list applied when listing issues from a label list' do + it 'returns opened/closed issues that have label list applied when listing issues from a label list' do params = { id: list1.id } issues = described_class.new(project, user, params).execute - expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] + expect(issues).to eq [closed_issue4, list1_issue3, list1_issue1, list1_issue2] end end end -- cgit v1.2.1 From 3eb4c74f88dfb316d695dc9396b88555bf314189 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 24 Aug 2016 15:19:36 -0300 Subject: Update Issue board documentation --- doc/user/project/issue_board.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 9df9ed9c9da..cac926b3e28 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -31,10 +31,9 @@ Below is a table of the definitions used for GitLab's Issue Board. There are three types of lists, the ones you create based on your labels, and two default: -- **Backlog** (default): shows all issues that do not fall in one of the other - lists. Always appears on the very left. -- **Done** (default): shows all closed issues. Always appears on the very right. -- Label list: a list based on a label. It shows all issues with that label. +- **Backlog** (default): shows all opened issues that do not fall in one of the other lists. Always appears on the very left. +- **Done** (default): shows all closed issues that do not fall in one of the other lists. Always appears on the very right. +- Label list: a list based on a label. It shows all opened or closed issues with that label. ![GitLab Issue Board](img/issue_board.png) -- cgit v1.2.1 From baa73b659ae50fa82d9c4be6ab5080a9475d7041 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 25 Aug 2016 09:41:49 -0300 Subject: Update CHANGELOG --- CHANGELOG | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..32d30ac8c60 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,10 +10,13 @@ v 8.12.0 (unreleased) - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) -v 8.11.2 (unreleased) +v 8.11.3 (unreleased) + - Label list shows all issues (opened or closed) with that label + +v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project -v 8.11.1 (unreleased) +v 8.11.1 - Does not halt the GitHub import process when an error occurs - Fix file links on project page when default view is Files !5933 - Change using size to use count and caching it for number of group members -- cgit v1.2.1 From 6280fd3777920670ee42111fddf29576cbf85988 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Tue, 23 Aug 2016 11:27:22 +0200 Subject: Reduce number of database queries on builds tab --- CHANGELOG | 1 + app/models/ci/build.rb | 2 +- app/models/ci/pipeline.rb | 2 +- app/models/commit_status.rb | 2 ++ app/views/projects/commit/_ci_stage.html.haml | 4 ++-- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..dfa8c23fd13 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.12.0 (unreleased) v 8.11.2 (unreleased) - Show "Create Merge Request" widget for push events to fork projects on the source project + - Reduce number of database queries on builds tab v 8.11.1 (unreleased) - Does not halt the GitHub import process when an error occurs diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 096b3b801af..23c8de6f650 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -352,7 +352,7 @@ module Ci end def artifacts? - !artifacts_expired? && artifacts_file.exists? + !artifacts_expired? && self[:artifacts_file].present? end def artifacts_metadata? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 087abe4cbb1..aff908ea16c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -83,7 +83,7 @@ module Ci end def stages_with_latest_statuses - statuses.latest.order(:stage_idx).group_by(&:stage) + statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage) end def project_id diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 84ceeac7d3e..1b403fa646d 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -25,6 +25,8 @@ class CommitStatus < ActiveRecord::Base scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } + scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } + scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) } state_machine :status do event :enqueue do diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml index 9d925cacc0d..6bb900e3fc1 100644 --- a/app/views/projects/commit/_ci_stage.html.haml +++ b/app/views/projects/commit/_ci_stage.html.haml @@ -8,8 +8,8 @@ - if stage   = stage.titleize - = render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true - = render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true + = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true + = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true %tr %td{colspan: 10}   -- cgit v1.2.1 From 79fd77953005062bfd89161770d1c0166774bb3d Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 25 Aug 2016 13:46:59 +0100 Subject: Allow setting branch refs for MR --- app/models/merge_request.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5330a07ee35..918be8b7e66 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -238,12 +238,12 @@ class MergeRequest < ActiveRecord::Base def source_branch_head source_branch_ref = @source_branch_sha || source_branch - source_project.repository.commit(source_branch) if source_branch_ref + source_project.repository.commit(source_branch_ref) if source_branch_ref end def target_branch_head target_branch_ref = @target_branch_sha || target_branch - target_project.repository.commit(target_branch) if target_branch_ref + target_project.repository.commit(target_branch_ref) if target_branch_ref end def target_branch_sha -- cgit v1.2.1 From f16eabfc3335119015302798cb270445ddc0dc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Fri, 12 Aug 2016 16:29:13 +0200 Subject: Display project icon from default branch This commit makes sure that the project icon is being read from the default branch instead of 'master' --- CHANGELOG | 1 + app/controllers/projects/avatars_controller.rb | 2 +- app/models/project.rb | 1 + app/models/repository.rb | 2 +- spec/models/project_spec.rb | 31 ++++++++++++++++++++++++++ 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 518e80a360a..ab675653138 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.11.1 (unreleased) - Does not halt the GitHub import process when an error occurs - Fix file links on project page when default view is Files !5933 - Change using size to use count and caching it for number of group members + - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index 5962f74c39b..ada7db3c552 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -4,7 +4,7 @@ class Projects::AvatarsController < Projects::ApplicationController before_action :authorize_admin_project!, only: [:destroy] def show - @blob = @repository.blob_at_branch('master', @project.avatar_in_git) + @blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git) if @blob headers['X-Content-Type-Options'] = 'nosniff' diff --git a/app/models/project.rb b/app/models/project.rb index 8cf093be4c3..0e4fb94f8eb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1035,6 +1035,7 @@ class Project < ActiveRecord::Base "refs/heads/#{branch}", force: true) repository.copy_gitattributes(branch) + repository.expire_avatar_cache(branch) reload_default_branch end diff --git a/app/models/repository.rb b/app/models/repository.rb index bdc3b9d1c1c..91bdafdac99 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1065,7 +1065,7 @@ class Repository @avatar ||= cache.fetch(:avatar) do AVATAR_FILES.find do |file| - blob_at_branch('master', file) + blob_at_branch(root_ref, file) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9a3660012f9..b2baeeb31bb 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1442,4 +1442,35 @@ describe Project, models: true do expect(shared_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true) end end + + describe 'change_head' do + let(:project) { create(:project) } + + it 'calls the before_change_head method' do + expect(project.repository).to receive(:before_change_head) + project.change_head(project.default_branch) + end + + it 'creates the new reference with rugged' do + expect(project.repository.rugged.references).to receive(:create).with('HEAD', + "refs/heads/#{project.default_branch}", + force: true) + project.change_head(project.default_branch) + end + + it 'copies the gitattributes' do + expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch) + project.change_head(project.default_branch) + end + + it 'expires the avatar cache' do + expect(project.repository).to receive(:expire_avatar_cache).with(project.default_branch) + project.change_head(project.default_branch) + end + + it 'reloads the default branch' do + expect(project).to receive(:reload_default_branch) + project.change_head(project.default_branch) + end + end end -- cgit v1.2.1 From cb5e6fe71e95d61cead3dd7985a044b383529413 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Aug 2016 15:36:56 +0200 Subject: Fix markdown link in doc_styleguide.md [ci skip] --- doc/development/doc_styleguide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 927a1872413..dc7d832ce3a 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -6,7 +6,7 @@ it organized and easy to find. ## Location and naming of documents >**Note:** -These guidelines derive from the discussion taken place in issue [#3349](ce-3349). +These guidelines derive from the discussion taken place in issue [#3349][ce-3349]. The documentation hierarchy can be vastly improved by providing a better layout and organization of directories. -- cgit v1.2.1 From 5b846a5d450197e7bcb4d8cbfc65388812beae63 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 25 Aug 2016 15:53:08 +0200 Subject: Moved two 8.11 changelog entries to 8.12 [ci skip] --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 64f4fb76bda..104f37003c9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,16 +9,16 @@ v 8.12.0 (unreleased) - Add Sentry logging to API calls - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) + - Reduce number of database queries on builds tab + - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) v 8.11.2 (unreleased) - Show "Create Merge Request" widget for push events to fork projects on the source project - - Reduce number of database queries on builds tab v 8.11.1 (unreleased) - Does not halt the GitHub import process when an error occurs - Fix file links on project page when default view is Files !5933 - Change using size to use count and caching it for number of group members - - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 -- cgit v1.2.1 From a5079f68e6aa3de4c6e430f96f7bf482bc4c492a Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 22 Aug 2016 17:56:46 +0200 Subject: Adds response mime type to transaction metric action when it's not HTML --- CHANGELOG | 1 + lib/gitlab/metrics/rack_middleware.rb | 22 ++++++++++++++++++++-- spec/lib/gitlab/metrics/rack_middleware_spec.rb | 21 +++++++++++++++++---- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 104f37003c9..ae0322a3ff9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.12.0 (unreleased) - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Reduce number of database queries on builds tab - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) + - Adds response mime type to transaction metric action when it's not HTML v 8.11.2 (unreleased) - Show "Create Merge Request" widget for push events to fork projects on the source project diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb index b4493bf44d2..01c96a6fe96 100644 --- a/lib/gitlab/metrics/rack_middleware.rb +++ b/lib/gitlab/metrics/rack_middleware.rb @@ -4,6 +4,17 @@ module Gitlab class RackMiddleware CONTROLLER_KEY = 'action_controller.instance' ENDPOINT_KEY = 'api.endpoint' + CONTENT_TYPES = { + 'text/html' => :html, + 'text/plain' => :txt, + 'application/json' => :json, + 'text/js' => :js, + 'application/atom+xml' => :atom, + 'image/png' => :png, + 'image/jpeg' => :jpeg, + 'image/gif' => :gif, + 'image/svg+xml' => :svg + } def initialize(app) @app = app @@ -46,8 +57,15 @@ module Gitlab end def tag_controller(trans, env) - controller = env[CONTROLLER_KEY] - trans.action = "#{controller.class.name}##{controller.action_name}" + controller = env[CONTROLLER_KEY] + action = "#{controller.class.name}##{controller.action_name}" + suffix = CONTENT_TYPES[controller.content_type] + + if suffix && suffix != :html + action += ".#{suffix}" + end + + trans.action = action end def tag_endpoint(trans, env) diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb index a30cb2a5e38..bcaffd27909 100644 --- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Metrics::RackMiddleware do end it 'tags a transaction with the name and action of a controller' do - klass = double(:klass, name: 'TestController') + klass = double(:klass, name: 'TestController', content_type: 'text/html') controller = double(:controller, class: klass, action_name: 'show') env['action_controller.instance'] = controller @@ -32,7 +32,7 @@ describe Gitlab::Metrics::RackMiddleware do middleware.call(env) end - it 'tags a transaction with the method andpath of the route in the grape endpoint' do + it 'tags a transaction with the method and path of the route in the grape endpoint' do route = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)") endpoint = double(:endpoint, route: route) @@ -87,17 +87,30 @@ describe Gitlab::Metrics::RackMiddleware do describe '#tag_controller' do let(:transaction) { middleware.transaction_from_env(env) } + let(:content_type) { 'text/html' } - it 'tags a transaction with the name and action of a controller' do + before do klass = double(:klass, name: 'TestController') - controller = double(:controller, class: klass, action_name: 'show') + controller = double(:controller, class: klass, action_name: 'show', content_type: content_type) env['action_controller.instance'] = controller + end + it 'tags a transaction with the name and action of a controller' do middleware.tag_controller(transaction, env) expect(transaction.action).to eq('TestController#show') end + + context 'when the response content type is not :html' do + let(:content_type) { 'application/json' } + + it 'appends the mime type to the transaction action' do + middleware.tag_controller(transaction, env) + + expect(transaction.action).to eq('TestController#show.json') + end + end end describe '#tag_endpoint' do -- cgit v1.2.1 From 77c22e95563f09e1430ac4fafcea4540463e3939 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 9 Aug 2016 14:44:43 -0500 Subject: Add font color contrast to external label in admin area --- CHANGELOG | 1 + app/assets/stylesheets/framework/nav.scss | 6 +----- app/assets/stylesheets/pages/admin.scss | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 104f37003c9..e45a9479ea4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Add two-factor recovery endpoint to internal API !5510 + - Add font color contrast to external label in admin area (ClemMakesApps) - Change merge_error column from string to text type - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 9e924f99e9c..c72db86d958 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -71,7 +71,7 @@ .badge { font-weight: normal; background-color: #eee; - color: #78a; + color: $btn-transparent-color; vertical-align: baseline; } } @@ -335,10 +335,6 @@ } } - .badge { - color: $gl-icon-color; - } - &:hover { a, i { color: $black; diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index c9cdfdcd29c..8f71381f5c4 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -96,6 +96,10 @@ line-height: inherit; } } + + .label-default { + color: $btn-transparent-color; + } } .abuse-reports { -- cgit v1.2.1 From 7a83df749e3ff3c88cff9f348ed5ed222a15508a Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Thu, 25 Aug 2016 15:41:51 +0100 Subject: Link to the issue templates in Contributing guide Change to use a better title style Add backticks to code segments Spelling and Grammar --- .gitlab/issue_templates/Bug.md | 26 +++++++++++++------------- .gitlab/issue_templates/Feature Proposal.md | 9 +++++---- CONTRIBUTING.md | 4 ++-- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index f6f5879c21a..b676916fdf4 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -1,44 +1,44 @@ -## Summary +### Summary (Summarize the bug encountered concisely) -## Steps to reproduce +### Steps to reproduce (How one can reproduce the issue - this is very important) -## Expected behavior +### Expected behavior (What you should see instead) -## Actual behaviour +### Actual behaviour (What actually happens) -## Relevant logs and/or screenshots +### Relevant logs and/or screenshots (Paste any relevant logs - please use code blocks (```) to format console output, logs, and code as it's very hard to read otherwise.) -## Output of checks +### Output of checks -### Results of GitLab Application Check +#### Results of GitLab application Check (For installations with omnibus-gitlab package run and paste the output of: -sudo gitlab-rake gitlab:check SANITIZE=true) +`sudo gitlab-rake gitlab:check SANITIZE=true`) (For installations from source run and paste the output of: -sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true) +`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`) (we will only investigate if the tests are passing) -### Results of GitLab Environment Info +#### Results of GitLab environment info (For installations with omnibus-gitlab package run and paste the output of: -sudo gitlab-rake gitlab:env:info) +`sudo gitlab-rake gitlab:env:info`) (For installations from source run and paste the output of: -sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production) +`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) -## Possible fixes +### Possible fixes (If you can, link to the line of code that might be responsible for the problem) diff --git a/.gitlab/issue_templates/Feature Proposal.md b/.gitlab/issue_templates/Feature Proposal.md index ea009c32296..ea895ee6275 100644 --- a/.gitlab/issue_templates/Feature Proposal.md +++ b/.gitlab/issue_templates/Feature Proposal.md @@ -1,6 +1,7 @@ -## Description -Include problem, use cases, benefits, and/or goals +### Description -## Proposal +(Include problem, use cases, benefits, and/or goals) -## Links / references +### Proposal + +### Links / references diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f4b3d5d244..c457af2ae6f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -144,7 +144,7 @@ code snippet right after your description in a new line: `~"feature proposal"`. Please keep feature proposals as small and simple as possible, complex ones might be edited to make them small and simple. -Please submit Feature Proposals using the 'Feature Proposal' issue template provided on the issue tracker. +Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker. For changes in the interface, it can be helpful to create a mockup first. If you want to create something yourself, consider opening an issue first to @@ -157,7 +157,7 @@ submitting your own, there's a good chance somebody else had the same issue or feature proposal. Show your support with an award emoji and/or join the discussion. -Please submit bugs using the 'Bug' issue template provided on the issue tracker. +Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker. The text in the parenthesis is there to help you with what to include. Omit it when submitting the actual issue. You can copy-paste it and then edit as you see fit. -- cgit v1.2.1 From 5ab8977f183f15d3c638f5dfcfb2ac484c862124 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 25 Aug 2016 09:53:42 -0500 Subject: Fix inline emoji text alignment --- CHANGELOG | 1 + app/assets/stylesheets/framework/common.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 104f37003c9..9b79288e1bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.12.0 (unreleased) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add Sentry logging to API calls - Added tests for diff notes + - Fix inline emoji text alignment (ClemMakesApps) - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Reduce number of database queries on builds tab - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index c1e5305644b..8984bce616c 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -248,7 +248,7 @@ li.note { img.emoji { height: 20px; - vertical-align: middle; + vertical-align: top; width: 20px; } -- cgit v1.2.1 From fc3dfb61b7f0dd7975f04cc068ff3e26c3a8b947 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 25 Aug 2016 16:07:20 +0100 Subject: Use icon helper --- app/views/admin/system_info/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index 7f3b95699db..bfc6142067a 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -12,7 +12,7 @@ - if @cpus %h1= "#{@cpus.length} cores" - else - %i.fa.fa-warning.text-warning + = icon('warning', class: 'text-warning') Unable to collect CPU info .col-sm-4 .light-well @@ -21,7 +21,7 @@ - if @memory %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}" - else - %i.fa.fa-warning.text-warning + = icon('warning', class: 'text-warning') Unable to collect memory info .col-sm-4 .light-well -- cgit v1.2.1 From fa3ec9d44aa13adf170b45c882f0ad6698f7f7f0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 23 Aug 2016 11:14:06 -0500 Subject: Fix groups sort dropdown alignment --- CHANGELOG | 1 + app/views/explore/groups/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4e12f5d4f00..97bb6da6f06 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.12.0 (unreleased) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Fix groups sort dropdown alignment (ClemMakesApps) - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Reduce number of database queries on builds tab diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 57f6e7e0612..b8248a80a27 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -24,7 +24,7 @@ - else = sort_title_recently_created %b.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-align-right %li = link_to explore_groups_path(sort: sort_value_recently_created) do = sort_title_recently_created -- cgit v1.2.1 From 77abf8cccfff01fa09a4014aea0f5afc309a7ca2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 25 Aug 2016 10:47:40 -0500 Subject: Capitalize mentioned issue timeline notes --- CHANGELOG | 1 + app/services/system_note_service.rb | 10 +++++----- features/steps/shared/issuable.rb | 2 +- spec/features/issues/new_branch_button_spec.rb | 2 +- spec/features/notes_on_merge_requests_spec.rb | 2 +- spec/models/note_spec.rb | 2 +- spec/requests/api/notes_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 10 +++++----- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4e12f5d4f00..f2e98a11a5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.12.0 (unreleased) - Added tests for diff notes - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Reduce number of database queries on builds tab + - Capitalize mentioned issue timeline notes (ClemMakesApps) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) v 8.11.2 (unreleased) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 546a8f11330..0c8446e7c3d 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -269,11 +269,11 @@ module SystemNoteService # # Example Note text: # - # "mentioned in #1" + # "Mentioned in #1" # - # "mentioned in !2" + # "Mentioned in !2" # - # "mentioned in 54f7727c" + # "Mentioned in 54f7727c" # # See cross_reference_note_content. # @@ -308,7 +308,7 @@ module SystemNoteService # Check if a cross-reference is disallowed # - # This method prevents adding a "mentioned in !1" note on every single commit + # This method prevents adding a "Mentioned in !1" note on every single commit # in a merge request. Additionally, it prevents the creation of references to # external issues (which would fail). # @@ -417,7 +417,7 @@ module SystemNoteService end def cross_reference_note_prefix - 'mentioned in ' + 'Mentioned in ' end def cross_reference_note_content(gfm_reference) diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index aa666a954bc..df9845ba569 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -179,7 +179,7 @@ module SharedIssuable project = Project.find_by(name: from_project_name) expect(page).to have_content(user_name) - expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") + expect(page).to have_content("Mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") end def expect_sidebar_content(content) diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index e528aff4d41..fb0c4704285 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -20,7 +20,7 @@ feature 'Start new branch from an issue', feature: true do context "when there is a referenced merge request" do let(:note) do create(:note, :on_issue, :system, project: project, - note: "mentioned in !#{referenced_mr.iid}") + note: "Mentioned in !#{referenced_mr.iid}") end let(:referenced_mr) do create(:merge_request, :simple, source_project: project, target_project: project, diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 7a9edbbe339..f1c522155d3 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -141,7 +141,7 @@ describe 'Comments', feature: true do let(:project2) { create(:project, :private) } let(:issue) { create(:issue, project: project2) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') } - let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") } + let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "Mentioned in #{issue.to_reference(project)}") } it 'shows the system note' do login_as :admin diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index ef2747046b9..9e8ae07e0b2 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -225,7 +225,7 @@ describe Note, models: true do let(:note) do create :note, noteable: ext_issue, project: ext_proj, - note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 737fa14cbb0..223444ea39f 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -25,7 +25,7 @@ describe API::API, api: true do let!(:cross_reference_note) do create :note, noteable: ext_issue, project: ext_proj, - note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 00427d6db2a..3d854a959f3 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -330,13 +330,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project2.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}" + expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference(project)}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}" + expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference(project)}" end end end @@ -346,13 +346,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}" + expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}" + expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference}" end end end @@ -362,7 +362,7 @@ describe SystemNoteService, services: true do describe '.cross_reference?' do it 'is truthy when text begins with expected text' do - expect(described_class.cross_reference?('mentioned in something')).to be_truthy + expect(described_class.cross_reference?('Mentioned in something')).to be_truthy end it 'is falsey when text does not begin with expected text' do -- cgit v1.2.1 From 9a6b7da91adce3cf8021a872e870ad828998cfb1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 25 Aug 2016 17:56:03 +0200 Subject: Fix wrong Koding link --- doc/README.md | 1 - doc/integration/README.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/README.md b/doc/README.md index 047035dfb09..254394eb63e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -19,7 +19,6 @@ - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. -- [Koding](user/project/koding.md) Learn how to use Koding, the online IDE. ## Administrator documentation diff --git a/doc/integration/README.md b/doc/integration/README.md index 70895abbcad..c2fd299db07 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -15,7 +15,7 @@ See the documentation below for details on how to configure these services. - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages - [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users - [Akismet](akismet.md) Configure Akismet to stop spam -- [Koding](koding.md) Configure Koding to use IDE integration +- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. -- cgit v1.2.1 From 82302b04fc53446a01fe1f36da261345415dca91 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 4 Aug 2016 10:53:05 -0500 Subject: Add hover color to emoji icon --- CHANGELOG | 1 + app/assets/stylesheets/pages/notes.scss | 16 +++++----------- app/views/projects/notes/_note.html.haml | 4 ++-- spec/javascripts/fixtures/awards_handler.html.haml | 2 +- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4522b268592..5d83a4b9520 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) + - Add hover color to emoji icon (ClemMakesApps) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add Sentry logging to API calls diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 08d1692c888..54124a3d658 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -281,19 +281,13 @@ ul.notes { font-size: 17px; } - &.js-note-delete { - i { - &:hover { - color: $gl-text-red; - } + &:hover { + .danger-highlight { + color: $gl-text-red; } - } - &.js-note-edit { - i { - &:hover { - color: $gl-link-color; - } + .link-highlight { + color: $gl-link-color; } } } diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index d2ac1ce2b9a..7c82177f9ea 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -52,11 +52,11 @@ - if note.emoji_awardable? = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do = icon('spinner spin') - = icon('smile-o') + = icon('smile-o', class: 'link-highlight') - if note_editable = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do - = icon('pencil') + = icon('pencil', class: 'link-highlight') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do = icon('trash-o') .note-body{class: note_editable ? 'js-task-list-container' : ''} diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml index d55936ee4f9..1ef2e8f8624 100644 --- a/spec/javascripts/fixtures/awards_handler.html.haml +++ b/spec/javascripts/fixtures/awards_handler.html.haml @@ -39,7 +39,7 @@ %span.note-role Reporter %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"} %i.fa.fa-spinner.fa-spin - %i.fa.fa-smile-o + %i.fa.fa-smile-o.link-highlight .js-task-list-container.note-body.is-task-list-enabled .note-text %p Suscipit sunt quia quisquam sed eveniet ipsam. -- cgit v1.2.1 From 7b4b4a0e557459fef2ef34361f5c231274006ab7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 25 Aug 2016 13:53:39 -0700 Subject: Add lock_version to merge_requests table Closes #21465 It appears this column was mistakenly omitted in !5623 due to rebasing issues in earlier comments. --- db/schema.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/schema.rb b/db/schema.rb index 38779065225..5a105a91ad1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -620,6 +620,7 @@ ActiveRecord::Schema.define(version: 20160823081327) do t.string "merge_commit_sha" t.datetime "deleted_at" t.string "in_progress_merge_commit_sha" + t.integer "lock_version" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree -- cgit v1.2.1 From a15e9f02b81f26cc07536d1458a576aede861529 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 11 Aug 2016 15:47:03 -0500 Subject: Reduce contributions calendar data payload --- CHANGELOG | 1 + .../javascripts/lib/utils/datetime_utility.js | 8 ++++ app/assets/javascripts/users/calendar.js | 51 +++++++++++++--------- lib/gitlab/contributions_calendar.rb | 1 - spec/javascripts/datetime_utility_spec.js.coffee | 19 ++++++++ 5 files changed, 58 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 69ad046dd11..adbaf5060f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Change merge_error column from string to text type + - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 10afa7e4329..d4d5927d3b0 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -67,6 +67,14 @@ $.timeago.settings.strings = tmpLocale; }; + w.gl.utils.getDayDifference = function(a, b) { + var millisecondsPerDay = 1000 * 60 * 60 * 24; + var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); + var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); + + return Math.floor((date2 - date1) / millisecondsPerDay); + } + })(window); }).call(this); diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 8b3dbf5f5ae..74ecf4f4cf9 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -3,7 +3,6 @@ this.Calendar = (function() { function Calendar(timestamps, calendar_activities_path) { - var group, i; this.calendar_activities_path = calendar_activities_path; this.clickDay = bind(this.clickDay, this); this.currentSelectedDate = ''; @@ -13,26 +12,36 @@ this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; this.months = []; this.timestampsTmp = []; - i = 0; - group = 0; - _.each(timestamps, (function(_this) { - return function(count, date) { - var day, innerArray, newDate; - newDate = new Date(parseInt(date) * 1000); - day = newDate.getDay(); - if ((day === 0 && i !== 0) || i === 0) { - _this.timestampsTmp.push([]); - group++; - } - innerArray = _this.timestampsTmp[group - 1]; - innerArray.push({ - count: count, - date: newDate, - day: day - }); - return i++; - }; - })(this)); + var group = 0; + + var today = new Date() + today.setHours(0, 0, 0, 0, 0); + + var oneYearAgo = new Date(today); + oneYearAgo.setFullYear(today.getFullYear() - 1); + + var days = gl.utils.getDayDifference(oneYearAgo, today); + + for(var i = 0; i <= days; i++) { + var date = new Date(oneYearAgo); + date.setDate(date.getDate() + i); + + var day = date.getDay(); + var count = timestamps[date.getTime() * 0.001]; + + if ((day === 0 && i !== 0) || i === 0) { + this.timestampsTmp.push([]); + group++; + } + + var innerArray = this.timestampsTmp[group - 1]; + innerArray.push({ + count: count || 0, + date: date, + day: day + }); + } + this.colorKey = this.initColorKey(); this.color = this.initColor(); this.renderSvg(group); diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 9dc2602867e..bd681f03173 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -23,7 +23,6 @@ module Gitlab dates.each do |date| date_id = date.to_time.to_i.to_s - @timestamps[date_id] = 0 day_events = events.find { |day_events| day_events["date"] == date } if day_events diff --git a/spec/javascripts/datetime_utility_spec.js.coffee b/spec/javascripts/datetime_utility_spec.js.coffee index 6b9617341fe..8bd113e7d86 100644 --- a/spec/javascripts/datetime_utility_spec.js.coffee +++ b/spec/javascripts/datetime_utility_spec.js.coffee @@ -29,3 +29,22 @@ describe 'Date time utils', -> it 'should return Saturday', -> day = gl.utils.getDayName(new Date('07/23/2016')) expect(day).toBe('Saturday') + + describe 'get day difference', -> + it 'should return 7', -> + firstDay = new Date('07/01/2016') + secondDay = new Date('07/08/2016') + difference = gl.utils.getDayDifference(firstDay, secondDay) + expect(difference).toBe(7) + + it 'should return 31', -> + firstDay = new Date('07/01/2016') + secondDay = new Date('08/01/2016') + difference = gl.utils.getDayDifference(firstDay, secondDay) + expect(difference).toBe(31) + + it 'should return 365', -> + firstDay = new Date('07/02/2015') + secondDay = new Date('07/01/2016') + difference = gl.utils.getDayDifference(firstDay, secondDay) + expect(difference).toBe(365) \ No newline at end of file -- cgit v1.2.1 From 1aed63dad2fc87ec04cbef86627507348a2eca47 Mon Sep 17 00:00:00 2001 From: "Federico \"Lox" Date: Thu, 25 Aug 2016 21:01:43 +0000 Subject: Fix `Stack Build` wizard description in koding.md --- doc/user/project/koding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/koding.md b/doc/user/project/koding.md index e54587fab68..c56a1efe3c2 100644 --- a/doc/user/project/koding.md +++ b/doc/user/project/koding.md @@ -68,7 +68,7 @@ GitLab instance. For details about what's next you can follow [this guide](https://www.koding.com/docs/creating-an-aws-stack) from step 8. Once stack initialized you will see the `README.md` content from your project -in `Stack Build` wizard, this wizard will let you to build the stack and import +in `Stack Build` wizard, this wizard will let you build the stack and import your project into it. **Once it's completed it will automatically open the related vm instead of importing from scratch**. -- cgit v1.2.1 From abcafc38f12e37b4475b048249741b871c54e477 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 25 Aug 2016 14:06:48 -0500 Subject: Fix changelog --- CHANGELOG | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4da8845ad09..4a335f58594 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,11 +19,7 @@ v 8.12.0 (unreleased) v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - -v 8.11.3 (unreleased) - Label list shows all issues (opened or closed) with that label - -v 8.11.2 - Don't show resolve conflicts link before MR status is updated - Don't prevent viewing the MR when git refs for conflicts can't be found on disk @@ -31,6 +27,7 @@ v 8.11.3 (unreleased) - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters v 8.11.2 (unreleased) +v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project v 8.11.1 -- cgit v1.2.1 From 9d95dce61b073a094eccf7f9193321f8558355b7 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 25 Aug 2016 16:12:43 -0500 Subject: Fix CHANGELOG entries related to 8.11 release. --- CHANGELOG | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4a335f58594..b0574726126 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,7 +11,18 @@ v 8.12.0 (unreleased) - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Added tests for diff notes - - Fix inline emoji text alignment (ClemMakesApps) + - Add delimiter to project stars and forks count (ClemMakesApps) + - Fix badge count alignment (ClemMakesApps) + - Fix branch title trailing space on hover (ClemMakesApps) + - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) + - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) + - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) + - Update merge_requests.md with a simpler way to check out a merge request. !5944 + - Fix button missing type (ClemMakesApps) + - Move to project dropdown with infinite scroll for better performance + - Load branches asynchronously in Cherry Pick and Revert dialogs. + - Add merge request versions !5467 + - Change using size to use count and caching it for number of group members. !5935 - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Reduce number of database queries on builds tab - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) @@ -23,29 +34,23 @@ v 8.11.3 (unreleased) - Don't show resolve conflicts link before MR status is updated - Don't prevent viewing the MR when git refs for conflicts can't be found on disk -v 8.11.3 (unreleased) - - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters - -v 8.11.2 (unreleased) v 8.11.2 - - Show "Create Merge Request" widget for push events to fork projects on the source project + - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 + - Use gitlab-workhorse 0.7.11 !5983 + - Does not halt the GitHub import process when an error occurs. !5763 + - Fix file links on project page when default view is Files !5933 + - Fixed enter key in search input not working !5888 v 8.11.1 - - Does not halt the GitHub import process when an error occurs - - Fix file links on project page when default view is Files !5933 - - Change using size to use count and caching it for number of group members + - Pulled due to packaging error. v 8.11.0 - - Add merge request versions !5467 - -v 8.11.0 (unreleased) - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Add Koding (online IDE) integration - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - - Add delimiter to project stars and forks count (ClemMakesApps) - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix adding line comments on the initial commit to a repo !5900 - Fix the title of the toggle dropdown button. !5515 (herminiotorres) @@ -61,7 +66,6 @@ v 8.11.0 (unreleased) - Use long options for curl examples in documentation !5703 (winniehell) - Added tooltip listing label names to the labels value in the collapsed issuable sidebar - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - - Fix badge count alignment (ClemMakesApps) - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Allow naming U2F devices !5833 @@ -102,7 +106,6 @@ v 8.11.0 (unreleased) - Enforce 2FA restrictions on API authentication endpoints !5820 - Limit git rev-list output count to one in forced push check - Show deployment status on merge requests with external URLs - - Fix branch title trailing space on hover (ClemMakesApps) - Clean up unused routes (Josef Strzibny) - Fix issue on empty project to allow developers to only push to protected branches if given permission - API: Add enpoints for pipelines @@ -119,7 +122,6 @@ v 8.11.0 (unreleased) - Fix devise deprecation warnings. - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 - Update version_sorter and use new interface for faster tag sorting - - Load branches asynchronously in Cherry Pick and Revert dialogs. - Optimize checking if a user has read access to a list of issues !5370 - Store all DB secrets in secrets.yml, under descriptive names !5274 - Fix syntax highlighting in file editor @@ -140,8 +142,6 @@ v 8.11.0 (unreleased) - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Add pipeline events hook - - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) - Make branches sortable without push permission !5462 (winniehell) @@ -153,14 +153,12 @@ v 8.11.0 (unreleased) - Fix search for notes which belongs to deleted objects - Allow Akismet to be trained by submitting issues as spam or ham !5538 - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - Allow branch names ending with .json for graph and network page !5579 (winniehell) - Add the `sprockets-es6` gem - Improve OAuth2 client documentation (muteor) - Fix diff comments inverted toggle bug (ClemMakesApps) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed - - Fix button missing type (ClemMakesApps) - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - Add commit stats in commit api. !5517 (dixpac) @@ -169,7 +167,6 @@ v 8.11.0 (unreleased) - edit_blob_link will use blob passed onto the options parameter - Make error pages responsive (Takuya Noguchi) - The performance of the project dropdown used for moving issues has been improved - - Move to project dropdown with infinite scroll for better performance - Fix skip_repo parameter being ignored when destroying a namespace - Add all builds into stage/job dropdowns on builds page - Change requests_profiles resource constraint to catch virtually any file @@ -201,7 +198,6 @@ v 8.11.0 (unreleased) - Eliminate unneeded calls to Repository#blob_at when listing commits with no path - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done - - Update merge_requests.md with a simpler way to check out a merge request. !5944 v 8.10.7 - Upgrade Hamlit to 2.6.1. !5873 -- cgit v1.2.1 From ace38e8397bd4c3eca11e0aafe9a8682ad81bf45 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 24 Aug 2016 20:06:16 -0700 Subject: Fix external issue tracker "Issues" link leading to 404s a70431f874 modified the behavior to link to the external issue tracker issues URL instead of the project path URL. This restores the previous behavior. Closes #21252, #21402 --- CHANGELOG | 1 + app/controllers/projects/issues_controller.rb | 2 +- spec/controllers/projects/issues_controller_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index abd94a0cd0c..134c89472a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.11.3 (unreleased) - Label list shows all issues (opened or closed) with that label - Don't show resolve conflicts link before MR status is updated - Don't prevent viewing the MR when git refs for conflicts can't be found on disk + - Fix external issue tracker "Issues" link leading to 404s v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 7b0189150f8..7c03dcd2e64 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -212,7 +212,7 @@ class Projects::IssuesController < Projects::ApplicationController if action_name == 'new' redirect_to external.new_issue_path else - redirect_to external.issues_url + redirect_to external.project_path end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 0836b71056c..16929767ddf 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -8,13 +8,13 @@ describe Projects::IssuesController do describe "GET #index" do context 'external issue tracker' do it 'redirects to the external issue tracker' do - external = double(issues_url: 'https://example.com/issues') + external = double(project_path: 'https://example.com/project') allow(project).to receive(:external_issue_tracker).and_return(external) controller.instance_variable_set(:@project, project) get :index, namespace_id: project.namespace.path, project_id: project - expect(response).to redirect_to('https://example.com/issues') + expect(response).to redirect_to('https://example.com/project') end end -- cgit v1.2.1 From 4d6dfeee1af07f6e8950ff73b30707d2a72fc938 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 10 Aug 2016 15:41:31 -0500 Subject: Add horizontal scrolling to all sub-navs on mobile viewports --- CHANGELOG | 1 + app/assets/stylesheets/framework/nav.scss | 20 ++++----- app/views/admin/background_jobs/_head.html.haml | 46 +++++++++++---------- app/views/admin/dashboard/_head.html.haml | 54 +++++++++++++------------ app/views/projects/commits/_head.html.haml | 5 +-- app/views/projects/graphs/_head.html.haml | 32 ++++++++------- app/views/projects/issues/_head.html.haml | 54 +++++++++++++------------ app/views/projects/pipelines/_head.html.haml | 36 +++++++++-------- app/views/projects/wikis/_nav.html.haml | 22 +++++----- app/views/shared/_nav_scroll.html.haml | 4 ++ 10 files changed, 145 insertions(+), 129 deletions(-) create mode 100644 app/views/shared/_nav_scroll.html.haml diff --git a/CHANGELOG b/CHANGELOG index abd94a0cd0c..8df18ded959 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.12.0 (unreleased) - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Fix groups sort dropdown alignment (ClemMakesApps) + - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Added tests for diff notes - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 9e924f99e9c..7854f419bc2 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -1,4 +1,4 @@ -@mixin fade($gradient-direction, $rgba, $gradient-color) { +@mixin fade($gradient-direction, $gradient-color) { visibility: hidden; opacity: 0; z-index: 2; @@ -8,10 +8,10 @@ height: 30px; transition-duration: .3s; -webkit-transform: translateZ(0); - background: -webkit-linear-gradient($gradient-direction, $rgba, $gradient-color 45%); - background: -o-linear-gradient($gradient-direction, $rgba, $gradient-color 45%); - background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%); - background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%); + background: -webkit-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); + background: -o-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); + background: -moz-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); + background: linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); &.scrolling { visibility: visible; @@ -356,7 +356,7 @@ } .fade-right { - @include fade(left, rgba(255, 255, 255, 0.4), $background-color); + @include fade(left, $background-color); right: -5px; .fa { @@ -365,7 +365,7 @@ } .fade-left { - @include fade(right, rgba(255, 255, 255, 0.4), $background-color); + @include fade(right, $background-color); left: -5px; .fa { @@ -376,6 +376,7 @@ &.sub-nav-scroll { .fade-right { + @include fade(left, $dark-background-color); right: 0; .fa { @@ -384,6 +385,7 @@ } .fade-left { + @include fade(right, $dark-background-color); left: 0; .fa { @@ -400,7 +402,7 @@ @include scrolling-links(); .fade-right { - @include fade(left, rgba(255, 255, 255, 0.4), $white-light); + @include fade(left, $white-light); right: -5px; .fa { @@ -409,7 +411,7 @@ } .fade-left { - @include fade(right, rgba(255, 255, 255, 0.4), $white-light); + @include fade(right, $white-light); left: -5px; .fa { diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml index 89d7a40d6b0..107fc25244a 100644 --- a/app/views/admin/background_jobs/_head.html.haml +++ b/app/views/admin/background_jobs/_head.html.haml @@ -1,22 +1,24 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } - = nav_link(controller: :system_info) do - = link_to admin_system_info_path, title: 'System Info' do - %span - System Info - = nav_link(controller: :background_jobs) do - = link_to admin_background_jobs_path, title: 'Background Jobs' do - %span - Background Jobs - = nav_link(controller: :logs) do - = link_to admin_logs_path, title: 'Logs' do - %span - Logs - = nav_link(controller: :health_check) do - = link_to admin_health_check_path, title: 'Health Check' do - %span - Health Check - = nav_link(controller: :requests_profiles) do - = link_to admin_requests_profiles_path, title: 'Requests Profiles' do - %span - Requests Profiles +.scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: :system_info) do + = link_to admin_system_info_path, title: 'System Info' do + %span + System Info + = nav_link(controller: :background_jobs) do + = link_to admin_background_jobs_path, title: 'Background Jobs' do + %span + Background Jobs + = nav_link(controller: :logs) do + = link_to admin_logs_path, title: 'Logs' do + %span + Logs + = nav_link(controller: :health_check) do + = link_to admin_health_check_path, title: 'Health Check' do + %span + Health Check + = nav_link(controller: :requests_profiles) do + = link_to admin_requests_profiles_path, title: 'Requests Profiles' do + %span + Requests Profiles diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml index b74da64f82e..c91ab4cb946 100644 --- a/app/views/admin/dashboard/_head.html.haml +++ b/app/views/admin/dashboard/_head.html.haml @@ -1,26 +1,28 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } - = nav_link(controller: :dashboard, html_options: {class: 'home'}) do - = link_to admin_root_path, title: 'Overview' do - %span - Overview - = nav_link(controller: [:admin, :projects]) do - = link_to admin_namespaces_projects_path, title: 'Projects' do - %span - Projects - = nav_link(controller: :users) do - = link_to admin_users_path, title: 'Users' do - %span - Users - = nav_link(controller: :groups) do - = link_to admin_groups_path, title: 'Groups' do - %span - Groups - = nav_link path: 'builds#index' do - = link_to admin_builds_path, title: 'Builds' do - %span - Builds - = nav_link path: ['runners#index', 'runners#show'] do - = link_to admin_runners_path, title: 'Runners' do - %span - Runners +.scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: :dashboard, html_options: {class: 'home'}) do + = link_to admin_root_path, title: 'Overview' do + %span + Overview + = nav_link(controller: [:admin, :projects]) do + = link_to admin_namespaces_projects_path, title: 'Projects' do + %span + Projects + = nav_link(controller: :users) do + = link_to admin_users_path, title: 'Users' do + %span + Users + = nav_link(controller: :groups) do + = link_to admin_groups_path, title: 'Groups' do + %span + Groups + = nav_link path: 'builds#index' do + = link_to admin_builds_path, title: 'Builds' do + %span + Builds + = nav_link path: ['runners#index', 'runners#show'] do + = link_to admin_runners_path, title: 'Runners' do + %span + Runners diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 61152649907..4d1ee1c5318 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,8 +1,5 @@ .scrolling-tabs-container.sub-nav-scroll - .fade-left - = icon('angle-left') - .fade-right - = icon('angle-right') + = render 'shared/nav_scroll' .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index 45e51389c00..a231d684559 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -1,16 +1,18 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } +.scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } - - content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/chart.js') - = page_specific_javascript_tag('graphs/graphs_bundle.js') - = nav_link(action: :show) do - = link_to 'Contributors', namespace_project_graph_path - = nav_link(action: :commits) do - = link_to 'Commits', commits_namespace_project_graph_path - = nav_link(action: :languages) do - = link_to 'Languages', languages_namespace_project_graph_path - - if @project.builds_enabled? - = nav_link(action: :ci) do - = link_to ci_namespace_project_graph_path do - Continuous Integration + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/chart.js') + = page_specific_javascript_tag('graphs/graphs_bundle.js') + = nav_link(action: :show) do + = link_to 'Contributors', namespace_project_graph_path + = nav_link(action: :commits) do + = link_to 'Commits', commits_namespace_project_graph_path + = nav_link(action: :languages) do + = link_to 'Languages', languages_namespace_project_graph_path + - if @project.builds_enabled? + = nav_link(action: :ci) do + = link_to ci_namespace_project_graph_path do + Continuous Integration diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index b6cb559afcb..f88b33018d0 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -1,30 +1,32 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } - - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) - = nav_link(controller: :issues) do - = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do - %span - Issues +.scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) + = nav_link(controller: :issues) do + = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do + %span + Issues - = nav_link(controller: :boards) do - = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do - %span - Board + = nav_link(controller: :boards) do + = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do + %span + Board - - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) - = nav_link(controller: :merge_requests) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do - %span - Merge Requests + - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) + = nav_link(controller: :merge_requests) do + = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do + %span + Merge Requests - - if project_nav_tab? :labels - = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do - %span - Labels + - if project_nav_tab? :labels + = nav_link(controller: :labels) do + = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + %span + Labels - - if project_nav_tab? :milestones - = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do - %span - Milestones + - if project_nav_tab? :milestones + = nav_link(controller: :milestones) do + = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + %span + Milestones \ No newline at end of file diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index d65faf86d4e..f611ddc8f5f 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,19 +1,21 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } - - if project_nav_tab? :pipelines - = nav_link(controller: :pipelines) do - = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do - %span - Pipelines +.scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + - if project_nav_tab? :pipelines + = nav_link(controller: :pipelines) do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do + %span + Pipelines - - if project_nav_tab? :builds - = nav_link(controller: %w(builds)) do - = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do - %span - Builds + - if project_nav_tab? :builds + = nav_link(controller: %w(builds)) do + = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do + %span + Builds - - if project_nav_tab? :environments - = nav_link(controller: %w(environments)) do - = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do - %span - Environments + - if project_nav_tab? :environments + = nav_link(controller: %w(environments)) do + = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do + %span + Environments diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index f8ea479e0b1..551a20c1044 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,13 +1,15 @@ -.nav-links.sub-nav - %ul{ class: (container_class) } - = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do - = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) +.scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do + = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) - = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) + = nav_link(path: 'wikis#pages') do + = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) - = nav_link(path: 'wikis#git_access') do - = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do - Git Access + = nav_link(path: 'wikis#git_access') do + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do + Git Access - = render 'projects/wikis/new' + = render 'projects/wikis/new' diff --git a/app/views/shared/_nav_scroll.html.haml b/app/views/shared/_nav_scroll.html.haml new file mode 100644 index 00000000000..4e3b1b3a571 --- /dev/null +++ b/app/views/shared/_nav_scroll.html.haml @@ -0,0 +1,4 @@ +.fade-left + = icon('angle-left') +.fade-right + = icon('angle-right') \ No newline at end of file -- cgit v1.2.1 From 7ed0acd422a95c4cbb1406f67929a5da669f2681 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 4 Aug 2016 12:18:00 -0600 Subject: turned ES5 users.js to ES6 users.js.es6 for babel added semicolons remove users.js rename users to user in filename removed uneeded semi-colons and returning null in constructor class is wrapped - a lot of builds will fail formatting replaced 'new User' with 'new gl.User' in app/users/show.html.haml window.gl || window.gl = {} - seeing if rspec9/spinach6/spinach9 will pass putting window logic before IIFE Fixed typo in users show view - extracted jquery calls in constructor to prototype methods fixed window declaration in IIFE argument adding new line --- app/assets/javascripts/user.js | 29 ----------------------------- app/assets/javascripts/user.js.es6 | 35 +++++++++++++++++++++++++++++++++++ app/views/users/show.html.haml | 2 +- 3 files changed, 36 insertions(+), 30 deletions(-) delete mode 100644 app/assets/javascripts/user.js create mode 100644 app/assets/javascripts/user.js.es6 diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js deleted file mode 100644 index 6c4d88cf407..00000000000 --- a/app/assets/javascripts/user.js +++ /dev/null @@ -1,29 +0,0 @@ -(function() { - this.User = (function() { - function User(opts) { - this.opts = opts; - $('.profile-groups-avatars').tooltip({ - "placement": "top" - }); - this.initTabs(); - $('.hide-project-limit-message').on('click', function(e) { - $.cookie('hide_project_limit_message', 'false', { - path: gon.relative_url_root || '/' - }); - $(this).parents('.project-limit-message').remove(); - return e.preventDefault(); - }); - } - - User.prototype.initTabs = function() { - return new UserTabs({ - parentEl: '.user-profile', - action: this.opts.action - }); - }; - - return User; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 new file mode 100644 index 00000000000..c934afa458a --- /dev/null +++ b/app/assets/javascripts/user.js.es6 @@ -0,0 +1,35 @@ +(global => { + global.User = class { + constructor(opts) { + this.opts = opts; + this.placeTop(); + this.initTabs(); + this.hideProjectLimitMessage(); + } + + placeTop() { + $('.profile-groups-avatars').tooltip({ + "placement": "top" + }); + } + + initTabs() { + return new UserTabs({ + parentEl: '.user-profile', + action: this.opts.action + }); + } + + hideProjectLimitMessage() { + $('.hide-project-limit-message').on('click', e => { + const path = '/'; + $.cookie('hide_project_limit_message', 'false', { + path: path + }); + $(this).parents('.project-limit-message').remove(); + e.preventDefault(); + return; + }); + } + } +})(window.gl || (window.gl = {})); diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index c7f39868e71..9a052abe40a 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -123,6 +123,6 @@ :javascript var userProfile; - userProfile = new User({ + userProfile = new gl.User({ action: "#{controller.action_name}" }); -- cgit v1.2.1 From f0866b28d384f569345eccb6e64ad93d29a3e570 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 25 Aug 2016 20:43:24 -0500 Subject: Fix repo title alignment --- CHANGELOG | 1 + app/assets/stylesheets/framework/header.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index be1063a2330..36c757021d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.12.0 (unreleased) - Added tests for diff notes - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) + - Fix repo title alignment (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 0c607071840..41ffaa38f6c 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -94,7 +94,7 @@ header { .side-nav-toggle { position: absolute; left: -10px; - margin: 6px 0; + margin: 7px 0; font-size: 18px; padding: 6px 10px; border: none; -- cgit v1.2.1 From 0fe4cf2b0fdbc33572f11bba1a4426ee05ed7599 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 24 Aug 2016 20:06:16 -0700 Subject: Fix Sentry not reporting right program for Sidekiq workers Moves program tag into the global configuration since this doesn't change and since Sidekiq workers get a unique context for each event. Closes #21410 --- app/helpers/sentry_helper.rb | 22 ++-------------------- config/initializers/sentry.rb | 1 + lib/gitlab/sentry.rb | 27 +++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 lib/gitlab/sentry.rb diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb index f8cccade15b..3d255df66a0 100644 --- a/app/helpers/sentry_helper.rb +++ b/app/helpers/sentry_helper.rb @@ -1,27 +1,9 @@ module SentryHelper def sentry_enabled? - Rails.env.production? && current_application_settings.sentry_enabled? + Gitlab::Sentry.enabled? end def sentry_context - return unless sentry_enabled? - - if current_user - Raven.user_context( - id: current_user.id, - email: current_user.email, - username: current_user.username, - ) - end - - Raven.tags_context(program: sentry_program_context) - end - - def sentry_program_context - if Sidekiq.server? - 'sidekiq' - else - 'rails' - end + Gitlab::Sentry.context(current_user) end end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index 74fef7cadfe..5892c1de024 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -18,6 +18,7 @@ if Rails.env.production? # Sanitize fields based on those sanitized from Rails. config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) + config.tags = { program: Gitlab::Sentry.program_context } end end end diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb new file mode 100644 index 00000000000..117fc508135 --- /dev/null +++ b/lib/gitlab/sentry.rb @@ -0,0 +1,27 @@ +module Gitlab + module Sentry + def self.enabled? + Rails.env.production? && current_application_settings.sentry_enabled? + end + + def self.context(current_user = nil) + return unless self.enabled? + + if current_user + Raven.user_context( + id: current_user.id, + email: current_user.email, + username: current_user.username, + ) + end + end + + def self.program_context + if Sidekiq.server? + 'sidekiq' + else + 'rails' + end + end + end +end -- cgit v1.2.1 From 71d552029126b42de1191a68dd270b5491d849ea Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 11 Aug 2016 10:08:06 -0500 Subject: Fix branches page dropdown sort alignment --- CHANGELOG | 1 + app/assets/stylesheets/framework/nav.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index df8dec7bdde..1f14ed51eba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.12.0 (unreleased) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Add hover color to emoji icon (ClemMakesApps) + - Fix branches page dropdown sort alignment (ClemMakesApps) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 9e924f99e9c..ceafdc20ebc 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -161,6 +161,7 @@ > .dropdown { margin-right: $gl-padding-top; display: inline-block; + vertical-align: top; &:last-child { margin-right: 0; -- cgit v1.2.1 From b17df0507b704a347d6a8af035526b642ec85284 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 26 Aug 2016 13:10:03 +0800 Subject: Extract ref_name and path in before_action, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5142#note_14469768 --- app/controllers/projects/artifacts_controller.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 4c63bec90e5..59222637961 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -4,6 +4,7 @@ class Projects::ArtifactsController < Projects::ApplicationController layout 'project' before_action :authorize_read_build! before_action :authorize_update_build!, only: [:keep] + before_action :extract_ref_name_and_path before_action :validate_artifacts! def download @@ -48,6 +49,12 @@ class Projects::ArtifactsController < Projects::ApplicationController private + def extract_ref_name_and_path + return unless params[:ref_name_and_path] + + @ref_name, @path = extract_ref(params[:ref_name_and_path]) + end + def validate_artifacts! render_404 unless build && build.artifacts? end @@ -61,12 +68,10 @@ class Projects::ArtifactsController < Projects::ApplicationController end def build_from_ref - if params[:ref_name_and_path] - ref_name, @path = extract_ref(params[:ref_name_and_path]) - builds = project.latest_successful_builds_for(ref_name) + return unless @ref_name - builds.find_by(name: params[:job]) - end + builds = project.latest_successful_builds_for(@ref_name) + builds.find_by(name: params[:job]) end def artifacts_file -- cgit v1.2.1 From 1ca59776b03201f2337aaba7233e957f3597ca3b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 24 Aug 2016 09:11:29 -0500 Subject: Fix enormous IE11 fork button --- CHANGELOG | 4 ++++ app/assets/stylesheets/framework/buttons.scss | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index df8dec7bdde..a377186bcac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -48,6 +48,10 @@ v 8.11.2 v 8.11.1 - Pulled due to packaging error. + - Does not halt the GitHub import process when an error occurs + - Fix file links on project page when default view is Files !5933 + - Change using size to use count and caching it for number of group members + - Fix IE11 fork button bug !5982 v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 6c3786b49bb..cd3ddf5fee9 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -200,7 +200,7 @@ svg { height: 15px; - width: auto; + width: 15px; position: relative; top: 2px; } -- cgit v1.2.1 From cfb77171bd16f20902265456178dc92527e82adf Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 26 Aug 2016 00:38:15 -0500 Subject: Update CHANGELOG --- CHANGELOG | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a377186bcac..2b3c715a32c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label - Don't show resolve conflicts link before MR status is updated + - Fix IE11 fork button bug !598 - Don't prevent viewing the MR when git refs for conflicts can't be found on disk - Fix external issue tracker "Issues" link leading to 404s @@ -48,10 +49,6 @@ v 8.11.2 v 8.11.1 - Pulled due to packaging error. - - Does not halt the GitHub import process when an error occurs - - Fix file links on project page when default view is Files !5933 - - Change using size to use count and caching it for number of group members - - Fix IE11 fork button bug !5982 v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 -- cgit v1.2.1 From 1fc0d4f4b50a9aeae73a0b0a9a4641e57786e6b3 Mon Sep 17 00:00:00 2001 From: Jake Romer Date: Fri, 26 Aug 2016 06:12:30 +0000 Subject: Clarify blank line rule in newlines_styleguide.md To clarify what's meant by "from a logical perspective" here, I consulted Python's PEP8 style guide, which provides some helpfully precise language: > Extra blank lines may be used (sparingly) to separate groups of > related functions. Blank lines may be omitted between a bunch of > related one-liners (e.g. a set of dummy implementations). https://www.python.org/dev/peps/pep-0008/#blank-lines I adapted this passage to the existing language for the newline rule. --- doc/development/newlines_styleguide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/newlines_styleguide.md b/doc/development/newlines_styleguide.md index e03adcaadea..32aac2529a4 100644 --- a/doc/development/newlines_styleguide.md +++ b/doc/development/newlines_styleguide.md @@ -2,7 +2,7 @@ This style guide recommends best practices for newlines in Ruby code. -## Rule: separate code with newlines only when it makes sense from logic perspectice +## Rule: separate code with newlines only to group together related logic ```ruby # bad -- cgit v1.2.1 From c40fd0b1c2aa5d3354fd50e37a4fe22483df2042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20=C3=98ivind=20Bj=C3=B8rnsen?= Date: Fri, 26 Aug 2016 07:24:13 +0000 Subject: docs: make sure to update 8.10-to-8.11 workhorse version too (see !5983) --- doc/update/8.10-to-8.11.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index 98721763566..b058f8e2a03 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -82,7 +82,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.7.8 +sudo -u git -H git checkout v0.7.11 sudo -u git -H make ``` -- cgit v1.2.1 From 3a8fb95c98262986ec09655bf5572c3e3d2145d6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 26 Aug 2016 15:40:12 +0800 Subject: Fix tests --- lib/gitlab/badge/coverage/report.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb index 95d925dc7f3..9a0482306b7 100644 --- a/lib/gitlab/badge/coverage/report.rb +++ b/lib/gitlab/badge/coverage/report.rb @@ -12,9 +12,7 @@ module Gitlab @ref = ref @job = job - @pipeline = @project.pipelines - .latest_successful_for(@ref) - .first + @pipeline = @project.pipelines.latest_successful_for(@ref) end def entity -- cgit v1.2.1 From bfd14f876388f6c9a4006ae270b8a98912415226 Mon Sep 17 00:00:00 2001 From: winniehell Date: Fri, 26 Aug 2016 10:54:17 +0200 Subject: Check for existence of elements under test in application_spec.js (!6051) --- spec/javascripts/application_spec.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index b48026c3b77..56b98856614 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -13,17 +13,21 @@ gl.utils.preventDisabledButtons(); isClicked = false; $button = $('#test-button'); + expect($button).toExist(); $button.click(function() { return isClicked = true; }); $button.trigger('click'); return expect(isClicked).toBe(false); }); - return it('should be on the same page if a disabled link clicked', function() { - var locationBeforeLinkClick; + + it('should be on the same page if a disabled link clicked', function() { + var locationBeforeLinkClick, $link; locationBeforeLinkClick = window.location.href; gl.utils.preventDisabledButtons(); - $('#test-link').click(); + $link = $('#test-link'); + expect($link).toExist(); + $link.click(); return expect(window.location.href).toBe(locationBeforeLinkClick); }); }); -- cgit v1.2.1 From 41a0b7b22f7cdec7d216f32d561442c9fc3587be Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 26 Aug 2016 17:32:00 +0800 Subject: Fix CHANGELOG --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index db3e891bdcd..a262dc54e5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,8 +15,6 @@ v 8.12.0 (unreleased) - Fix groups sort dropdown alignment (ClemMakesApps) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - -v 8.11.1 (unreleased) - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps) -- cgit v1.2.1 From 40884010ad48c83a91ece50c17eaa78f3c7c9a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Thu, 25 Aug 2016 21:23:23 +0000 Subject: doc: fix (batches) being treated as link reference rather than text --- doc/ci/pipelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index 20cd88c8d20..ca9b986a060 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -5,7 +5,7 @@ Introduced in GitLab 8.8. ## Pipelines -A pipeline is a group of [builds] that get executed in [stages] (batches). All +A pipeline is a group of [builds] that get executed in [stages] \(batches). All of the builds in a stage are executed in parallel (if there are enough concurrent [runners]), and if they all succeed, the pipeline moves on to the next stage. If one of the builds fails, the next stage is not (usually) -- cgit v1.2.1 From 770ecd55dd7fa483e440386a6635c2833c5239c1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 25 Aug 2016 12:48:15 -0500 Subject: Curved lines at top of pipeline graph --- app/assets/stylesheets/pages/pipelines.scss | 46 +++++++++++++++++++++- .../projects/ci/builds/_build_pipeline.html.haml | 1 + 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9bdf0d250bb..9b7bf9575ff 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -370,8 +370,11 @@ // Connect second build to first build with smaller curved line &:nth-child(2) { &::after, &::before { - height: 45px; - top: -26px; + height: 29px; + top: -10px; + } + .curve { + display: block; } } } @@ -390,6 +393,12 @@ border: none; } } + // Remove opposite curve + .curve { + &::before { + display: none; + } + } } } @@ -401,6 +410,39 @@ border: none; } } + // Remove opposite curve + .curve { + &::after { + display: none; + } + } + } + } + + // Curve first child connecting lines in opposite direction + .curve { + display: none; + + &::before, + &::after { + content: ''; + width: 21px; + height: 25px; + position: absolute; + top: -28.5px; + border-top: 2px solid $border-color; + } + + &::after { + left: -39px; + border-right: 2px solid $border-color; + border-radius: 0 50% 0; + } + + &::before { + right: -39px; + border-left: 2px solid $border-color; + border-radius: 50% 0 0; } } } diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 04cbd0c3591..ebdf60ae370 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,5 +1,6 @@ - is_playable = subject.playable? && can?(current_user, :update_build, @project) %li.build{class: ("playable" if is_playable)} + .curve .build-content - if is_playable = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do -- cgit v1.2.1 From 906c14e31a8f58478aab2528ecb8d874404dfa16 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 25 Aug 2016 14:40:51 -0500 Subject: Scss lint fix --- app/assets/stylesheets/pages/pipelines.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9b7bf9575ff..df8fc0ee154 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -436,7 +436,7 @@ &::after { left: -39px; border-right: 2px solid $border-color; - border-radius: 0 50% 0; + border-radius: 0 50%; } &::before { -- cgit v1.2.1 From a2be0e59e0cdb3de518740805266c719c6255f7b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 26 Aug 2016 09:28:00 -0500 Subject: Unify border radiuses to 15px, move columns further apart --- app/assets/stylesheets/pages/pipelines.scss | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index df8fc0ee154..743fe89a9f4 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -273,7 +273,7 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 50px; + margin-right: 65px; li { list-style: none; @@ -334,9 +334,9 @@ content: ''; position: absolute; top: 50%; - right: -54px; + right: -69px; border-top: 2px solid $border-color; - width: 54px; + width: 69px; height: 1px; } } @@ -356,14 +356,14 @@ &::after { right: -20px; border-right: 2px solid $border-color; - border-radius: 0 0 50px; + border-radius: 0 0 15px; } // Left connecting curves &::before { left: -20px; border-left: 2px solid $border-color; - border-radius: 0 0 0 50px; + border-radius: 0 0 0 15px; } } @@ -436,13 +436,13 @@ &::after { left: -39px; border-right: 2px solid $border-color; - border-radius: 0 50%; + border-radius: 0 15px; } &::before { right: -39px; border-left: 2px solid $border-color; - border-radius: 50% 0 0; + border-radius: 15px 0 0; } } } -- cgit v1.2.1 From aeb280b0ccb7186b75cf73fcd2e051c5ed661443 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 25 Aug 2016 09:19:14 -0500 Subject: Fix markdown help references --- CHANGELOG | 1 + app/helpers/search_helper.rb | 2 +- app/views/admin/appearances/_form.html.haml | 2 +- app/views/help/ui.html.haml | 2 +- doc/development/doc_styleguide.md | 2 +- doc/user/markdown.md | 20 ++++++++++---------- spec/routing/routing_spec.rb | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 16c4b7cb858..f8dde8ca372 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.12.0 (unreleased) - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) + - Fix markdown help references (ClemMakesApps) - Added tests for diff notes - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index c0195713f4a..4549c2e5bb6 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -44,7 +44,7 @@ module SearchHelper def help_autocomplete [ { category: "Help", label: "API Help", url: help_page_path("api/README") }, - { category: "Help", label: "Markdown Help", url: help_page_path("markdown/markdown") }, + { category: "Help", label: "Markdown Help", url: help_page_path("user/markdown") }, { category: "Help", label: "Permissions Help", url: help_page_path("user/permissions") }, { category: "Help", label: "Public Access Help", url: help_page_path("public_access/public_access") }, { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks/README") }, diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 92e2dae4842..9175b3d3f96 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -13,7 +13,7 @@ .col-sm-10 = f.text_area :description, class: "form-control", rows: 10 .hint - Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown/markdown'), target: '_blank'}. + Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}. .form-group = f.label :logo, class: 'control-label' .col-sm-10 diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 85e188d6f8b..d16bd61b779 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -549,4 +549,4 @@ %li wiki page %li help page - You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown/markdown")}. + You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("user/markdown")}. diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index dc7d832ce3a..8bf24f70a9a 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -422,7 +422,7 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain [cURL]: http://curl.haxx.se/ "cURL website" [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html -[gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation" +[gfm]: http://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation" [doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation" [ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure" [graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 7fe96e67dbb..c7fda8a497f 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -66,7 +66,7 @@ dependency to do so. Please see the [github-markup gem readme](https://github.co ## Newlines > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#newlines GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p). @@ -86,7 +86,7 @@ Sugar is sweet ## Multiple underscores in words > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiple-underscores-in-words It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words: @@ -101,7 +101,7 @@ do_this_and_do_that_and_another_thing ## URL auto-linking > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#url-auto-linking GFM will autolink almost any URL you copy and paste into your text: @@ -122,7 +122,7 @@ GFM will autolink almost any URL you copy and paste into your text: ## Multiline Blockquote > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiline-blockquote On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, GFM supports multiline blockquotes fenced by >>>: @@ -156,7 +156,7 @@ you can quote that without having to manually prepend `>` to every line! ## Code and Syntax Highlighting > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting _GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a list of supported languages visit the Rouge website._ @@ -226,7 +226,7 @@ But let's throw in a tag. ## Inline Diff > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-diff With inline diffs tags you can display {+ additions +} or [- deletions -]. @@ -242,7 +242,7 @@ However the wrapping tags cannot be mixed as such: ## Emoji > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: @@ -307,7 +307,7 @@ GFM also recognizes certain cross-project references: ## Task Lists > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-lists You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: @@ -330,7 +330,7 @@ Task lists can only be created in descriptions, not in titles. Task item state c ## Videos > If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#videos Image tags with a video extension are automatically converted to a video player. @@ -780,7 +780,7 @@ A link starting with a `/` is relative to the wiki root. - The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. - [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. -[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md +[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md [rouge]: http://rouge.jneen.net/ "Rouge website" [redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" [^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index d65648dd0b2..4bc3cddd9c2 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -107,9 +107,9 @@ describe HelpController, "routing" do end it 'to #show' do - path = '/help/markdown/markdown.md' + path = '/help/user/markdown.md' expect(get(path)).to route_to('help#show', - path: 'markdown/markdown', + path: 'user/markdown', format: 'md') path = '/help/workflow/protected_branches/protected_branches1.png' -- cgit v1.2.1 From 0e0d0ede2c265de4b2fdf76a1376cfced3efaacd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 25 Aug 2016 12:42:22 -0500 Subject: Fix inconsistent checkbox alignment --- CHANGELOG | 1 + app/assets/stylesheets/pages/issues.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index df8dec7bdde..31f8f11288a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.12.0 (unreleased) - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Reduce number of database queries on builds tab - Capitalize mentioned issue timeline notes (ClemMakesApps) + - Fix inconsistent checkbox alignment (ClemMakesApps) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index dfe1e3075da..3a3bb10feac 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -12,6 +12,10 @@ padding-right: 8px; margin-bottom: 10px; min-width: 15px; + + .selected_issue { + vertical-align: text-top; + } } .issue-labels { -- cgit v1.2.1 From 6686084c6502f10fd7e6b8963ab52526cb6831bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Fri, 26 Aug 2016 17:20:00 -0300 Subject: Fix "Wiki" link not appearing in navigation for projects with external wiki --- CHANGELOG | 1 + app/models/ability.rb | 2 +- app/models/project.rb | 4 ++++ spec/models/ability_spec.rb | 13 +++++++++++++ spec/models/project_spec.rb | 12 ++++++++++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7817470d95e..b5ae1adf1d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label - Don't show resolve conflicts link before MR status is updated + - Fix "Wiki" link not appearing in navigation for projects with external wiki - Fix IE11 fork button bug !598 - Don't prevent viewing the MR when git refs for conflicts can't be found on disk - Fix external issue tracker "Issues" link leading to 404s diff --git a/app/models/ability.rb b/app/models/ability.rb index a49dd703926..c1df4a865f6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -355,7 +355,7 @@ class Ability rules += named_abilities('project_snippet') end - unless project.wiki_enabled + unless project.has_wiki? rules += named_abilities('wiki') end diff --git a/app/models/project.rb b/app/models/project.rb index 0e4fb94f8eb..0fa41ebbec3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -680,6 +680,10 @@ class Project < ActiveRecord::Base update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) end + def has_wiki? + wiki_enabled? || has_external_wiki? + end + def external_wiki if has_external_wiki.nil? cache_has_external_wiki # Populate diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index aa3b2bbf471..c50ca38bdd9 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -282,4 +282,17 @@ describe Ability, lib: true do end end end + + describe '.project_disabled_features_rules' do + let(:project) { build(:project) } + + subject { described_class.project_disabled_features_rules(project) } + + context 'wiki named abilities' do + it 'disables wiki abilities if the project has no wiki' do + expect(project).to receive(:has_wiki?).and_return(false) + expect(subject).to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki) + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index b2baeeb31bb..3b637b0defc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -506,6 +506,18 @@ describe Project, models: true do end end + describe '#has_wiki?' do + let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) } + let(:wiki_enabled_project) { build(:project, wiki_enabled: true) } + let(:external_wiki_project) { build(:project, has_external_wiki: true) } + + it 'returns true if project is wiki enabled or has external wiki' do + expect(wiki_enabled_project).to have_wiki + expect(external_wiki_project).to have_wiki + expect(no_wiki_project).not_to have_wiki + end + end + describe '#external_wiki' do let(:project) { create(:project) } -- cgit v1.2.1 From 9c2d061ad468e6a47d21617fb2c6b874e22c13bc Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 24 Aug 2016 11:43:44 +0100 Subject: Drop unused CI tables and files These tables, web hooks and services, are unused but where not dropped with the commits d5c91bb9a601a1a344d94763654f0b0996857497 and 2988e1fbf50b3c9e803a9358933e3e969e64dcc3. The file was left too, but never called. --- app/services/ci/web_hook_service.rb | 35 ---------------------- db/migrate/20160824103857_drop_unused_ci_tables.rb | 11 +++++++ db/schema.rb | 19 +----------- 3 files changed, 12 insertions(+), 53 deletions(-) delete mode 100644 app/services/ci/web_hook_service.rb create mode 100644 db/migrate/20160824103857_drop_unused_ci_tables.rb diff --git a/app/services/ci/web_hook_service.rb b/app/services/ci/web_hook_service.rb deleted file mode 100644 index 92e6df442b4..00000000000 --- a/app/services/ci/web_hook_service.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Ci - class WebHookService - def build_end(build) - execute_hooks(build.project, build_data(build)) - end - - def execute_hooks(project, data) - project.web_hooks.each do |web_hook| - async_execute_hook(web_hook, data) - end - end - - def async_execute_hook(hook, data) - Sidekiq::Client.enqueue(Ci::WebHookWorker, hook.id, data) - end - - def build_data(build) - project = build.project - data = {} - data.merge!({ - build_id: build.id, - build_name: build.name, - build_status: build.status, - build_started_at: build.started_at, - build_finished_at: build.finished_at, - project_id: project.id, - project_name: project.name, - gitlab_url: project.gitlab_url, - ref: build.ref, - before_sha: build.before_sha, - sha: build.sha, - }) - end - end -end diff --git a/db/migrate/20160824103857_drop_unused_ci_tables.rb b/db/migrate/20160824103857_drop_unused_ci_tables.rb new file mode 100644 index 00000000000..65cf46308d9 --- /dev/null +++ b/db/migrate/20160824103857_drop_unused_ci_tables.rb @@ -0,0 +1,11 @@ +class DropUnusedCiTables < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + drop_table(:ci_services) + drop_table(:ci_web_hooks) + end +end diff --git a/db/schema.rb b/db/schema.rb index 5a105a91ad1..227e10294e4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160823081327) do +ActiveRecord::Schema.define(version: 20160824103857) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -295,16 +295,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree - create_table "ci_services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "active", default: false, null: false - t.text "properties" - end - create_table "ci_sessions", force: :cascade do |t| t.string "session_id", null: false t.text "data" @@ -360,13 +350,6 @@ ActiveRecord::Schema.define(version: 20160823081327) do add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree - create_table "ci_web_hooks", force: :cascade do |t| - t.string "url", null: false - t.integer "project_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - end - create_table "deploy_keys_projects", force: :cascade do |t| t.integer "deploy_key_id", null: false t.integer "project_id", null: false -- cgit v1.2.1 From 12784559316fc2c3860641bee9c6f8e2bc12c4ba Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 3 Aug 2016 22:20:08 -0500 Subject: Change logo animation to CSS --- CHANGELOG | 1 + app/assets/javascripts/logo.js | 42 +--------- app/assets/stylesheets/framework.scss | 1 + app/assets/stylesheets/framework/header.scss | 32 +------- app/assets/stylesheets/framework/logo.scss | 118 +++++++++++++++++++++++++++ app/assets/stylesheets/framework/mixins.scss | 5 ++ app/views/shared/_logo.svg | 16 ++-- 7 files changed, 136 insertions(+), 79 deletions(-) create mode 100644 app/assets/stylesheets/framework/logo.scss diff --git a/CHANGELOG b/CHANGELOG index 3548115dff3..713e0e033d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Add two-factor recovery endpoint to internal API !5510 - Add font color contrast to external label in admin area (ClemMakesApps) + - Change logo animation to CSS (ClemMakesApps) - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js index 218f24fe908..e5d4fd44c96 100644 --- a/app/assets/javascripts/logo.js +++ b/app/assets/javascripts/logo.js @@ -1,50 +1,12 @@ (function() { - var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work; - Turbolinks.enableProgressBar(); - defaultClass = 'tanuki-shape'; - - pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek']; - - pieceIndex = 0; - - firstPiece = pieces[0]; - - currentTimer = null; - - delay = 150; - - clearHighlights = function() { - return $("." + defaultClass + ".highlight").attr('class', defaultClass); - }; - start = function() { - clearHighlights(); - pieceIndex = 0; - if (pieces[0] !== firstPiece) { - pieces.reverse(); - } - if (currentTimer) { - clearInterval(currentTimer); - } - return currentTimer = setInterval(work, delay); + $('.tanuki-logo').addClass('animate'); }; stop = function() { - clearInterval(currentTimer); - return clearHighlights(); - }; - - work = function() { - clearHighlights(); - $(pieces[pieceIndex]).attr('class', defaultClass + " highlight"); - if (pieceIndex === pieces.length - 1) { - pieceIndex = 0; - return pieces.reverse(); - } else { - return pieceIndex++; - } + $('.tanuki-logo').removeClass('animate'); }; $(document).on('page:fetch', start); diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index a306b8f3f29..d5cca1b10fb 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -24,6 +24,7 @@ @import "framework/issue_box.scss"; @import "framework/jquery.scss"; @import "framework/lists.scss"; +@import "framework/logo.scss"; @import "framework/markdown_area.scss"; @import "framework/mobile.scss"; @import "framework/modal.scss"; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 0c607071840..afe4a276ae5 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -2,16 +2,6 @@ * Application Header * */ -@mixin tanuki-logo-colors($path-color) { - fill: $path-color; - transition: all 0.8s; - - &:hover, - &.highlight { - fill: lighten($path-color, 25%); - transition: all 0.1s; - } -} header { transition: padding $sidebar-transition-duration; @@ -25,7 +15,7 @@ header { margin: 8px 0; text-align: center; - #tanuki-logo, img { + .tanuki-logo, img { height: 36px; } } @@ -205,26 +195,6 @@ header { } } -#tanuki-logo { - - #tanuki-left-ear, - #tanuki-right-ear, - #tanuki-nose { - @include tanuki-logo-colors($tanuki-red); - } - - #tanuki-left-eye, - #tanuki-right-eye { - @include tanuki-logo-colors($tanuki-orange); - } - - #tanuki-left-cheek, - #tanuki-right-cheek { - @include tanuki-logo-colors($tanuki-yellow); - } - -} - @media (max-width: $screen-xs-max) { header .container-fluid { font-size: 18px; diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss new file mode 100644 index 00000000000..3ee3fb4cee5 --- /dev/null +++ b/app/assets/stylesheets/framework/logo.scss @@ -0,0 +1,118 @@ +@mixin unique-keyframes { + $animation-name: unique-id(); + @include webkit-prefix(animation-name, $animation-name); + + @-webkit-keyframes #{$animation-name} { + @content; + } + @keyframes #{$animation-name} { + @content; + } +} + +@mixin tanuki-logo-colors($path-color) { + fill: $path-color; + transition: all 0.8s; + + &:hover { + fill: lighten($path-color, 25%); + transition: all 0.1s; + } +} + +@mixin tanuki-second-highlight-animations($tanuki-color) { + @include unique-keyframes { + 10%, 80% { + fill: #{$tanuki-color} + } + 20%, 90% { + fill: lighten($tanuki-color, 25%); + } + } +} + +@mixin tanuki-forth-highlight-animations($tanuki-color) { + @include unique-keyframes { + 30%, 60% { + fill: #{$tanuki-color}; + } + 40%, 70% { + fill: lighten($tanuki-color, 25%); + } + } +} + +.tanuki-logo { + + .tanuki-left-ear, + .tanuki-right-ear, + .tanuki-nose { + @include tanuki-logo-colors($tanuki-red); + } + + .tanuki-left-eye, + .tanuki-right-eye { + @include tanuki-logo-colors($tanuki-orange); + } + + .tanuki-left-cheek, + .tanuki-right-cheek { + @include tanuki-logo-colors($tanuki-yellow); + } + + &.animate { + .tanuki-shape { + @include webkit-prefix(animation-duration, 1.5s); + @include webkit-prefix(animation-iteration-count, infinite); + } + + .tanuki-left-cheek { + @include unique-keyframes { + 0%, 10%, 100% { + fill: lighten($tanuki-yellow, 25%); + } + 90% { + fill: $tanuki-yellow; + } + } + } + + .tanuki-left-eye { + @include tanuki-second-highlight-animations($tanuki-orange); + } + + .tanuki-left-ear { + @include tanuki-second-highlight-animations($tanuki-red); + } + + .tanuki-nose { + @include unique-keyframes { + 20%, 70% { + fill: $tanuki-red; + } + 30%, 80% { + fill: lighten($tanuki-red, 25%); + } + } + } + + .tanuki-right-eye { + @include tanuki-forth-highlight-animations($tanuki-orange); + } + + .tanuki-right-ear { + @include tanuki-forth-highlight-animations($tanuki-red); + } + + .tanuki-right-cheek { + @include unique-keyframes { + 40% { + fill: $tanuki-yellow; + } + 60% { + fill: lighten($tanuki-yellow, 25%); + } + } + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index d2d60ed7196..856642400a6 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -129,3 +129,8 @@ color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.1); } + +@mixin webkit-prefix($property, $value) { + #{'-webkit-' + $property}: $value; + #{$property}: $value; +} diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg index b07f1c5603e..9b67422da2c 100644 --- a/app/views/shared/_logo.svg +++ b/app/views/shared/_logo.svg @@ -1,9 +1,9 @@ -/due in 2 days | this Friday | December 31st | Set due date | | `/remove_due_date` | Remove due date | -- cgit v1.2.1 From 700ffcf16cd6816fa0e4c65c0d40136d6f29fd36 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 29 Aug 2016 10:16:09 +0200 Subject: Add angle brackets around due date in slash cmds docs --- doc/user/project/slash_commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md index cfdf04a32a3..1792a0c501d 100644 --- a/doc/user/project/slash_commands.md +++ b/doc/user/project/slash_commands.md @@ -26,5 +26,5 @@ do. | `/done` | Mark todo as done | | `/subscribe` | Subscribe | | `/unsubscribe` | Unsubscribe | -| /due in 2 days | this Friday | December 31st | Set due date | +| /due <in 2 days | this Friday | December 31st> | Set due date | | `/remove_due_date` | Remove due date | -- cgit v1.2.1 From f4f191c1a07c5b1616d81e0284f73f2a045e0783 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 29 Aug 2016 09:36:46 +0200 Subject: Add Changelog entry for hidden keys fix in CI config --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index f4c850fe00c..8075e5b4d1d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.12.0 (unreleased) - Adds response mime type to transaction metric action when it's not HTML v 8.11.3 (unreleased) + - Do not enforce using hash with hidden key in CI configuration. !6079 - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label - Don't show resolve conflicts link before MR status is updated -- cgit v1.2.1 From 2b33b24a3a5174cb391bf6c93643838f743e3dd2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 27 Aug 2016 12:36:08 -0500 Subject: Shorten task status phrase --- CHANGELOG | 1 + app/models/concerns/taskable.rb | 4 +- spec/features/task_lists_spec.rb | 266 ++++++++++++++++++++++--------- spec/support/taskable_shared_examples.rb | 63 +++++--- 4 files changed, 241 insertions(+), 93 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3548115dff3..ea673da5d3b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.12.0 (unreleased) - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) + - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index df2a9e3e84b..a3ac577cf3e 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -52,11 +52,11 @@ module Taskable end # Return a string that describes the current state of this Taskable's task - # list items, e.g. "20 tasks (12 completed, 8 remaining)" + # list items, e.g. "12 of 20 tasks completed" def task_status return '' if description.blank? sum = tasks.summary - "#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)" + "#{sum.complete_count} of #{sum.item_count} #{'task'.pluralize(sum.item_count)} completed" end end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 6ed279ef9be..abb27c90e0a 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -20,6 +20,22 @@ feature 'Task Lists', feature: true do MARKDOWN end + let(:singleIncompleteMarkdown) do + <<-MARKDOWN.strip_heredoc + This is a task list: + + - [ ] Incomplete entry 1 + MARKDOWN + end + + let(:singleCompleteMarkdown) do + <<-MARKDOWN.strip_heredoc + This is a task list: + + - [x] Incomplete entry 1 + MARKDOWN + end + before do Warden.test_mode! @@ -34,77 +50,145 @@ feature 'Task Lists', feature: true do end describe 'for Issues' do - let!(:issue) { create(:issue, description: markdown, author: user, project: project) } + describe 'multiple tasks' do + let!(:issue) { create(:issue, description: markdown, author: user, project: project) } - it 'renders' do - visit_issue(project, issue) + it 'renders' do + visit_issue(project, issue) - expect(page).to have_selector('ul.task-list', count: 1) - expect(page).to have_selector('li.task-list-item', count: 6) - expect(page).to have_selector('ul input[checked]', count: 2) - end + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 6) + expect(page).to have_selector('ul input[checked]', count: 2) + end + + it 'contains the required selectors' do + visit_issue(project, issue) + + container = '.detail-page-description .description.js-task-list-container' - it 'contains the required selectors' do - visit_issue(project, issue) + expect(page).to have_selector(container) + expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") + expect(page).to have_selector('form.js-issuable-update') + expect(page).to have_selector('a.btn-close') + end - container = '.detail-page-description .description.js-task-list-container' + it 'is only editable by author' do + visit_issue(project, issue) + expect(page).to have_selector('.js-task-list-container') - expect(page).to have_selector(container) - expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") - expect(page).to have_selector("#{container} .js-task-list-field") - expect(page).to have_selector('form.js-issuable-update') - expect(page).to have_selector('a.btn-close') + logout(:user) + + login_as(user2) + visit current_path + expect(page).not_to have_selector('.js-task-list-container') + end + + it 'provides a summary on Issues#index' do + visit namespace_project_issues_path(project.namespace, project) + expect(page).to have_content("2 of 6 tasks completed") + end end - it 'is only editable by author' do - visit_issue(project, issue) - expect(page).to have_selector('.js-task-list-container') + describe 'single incomplete task' do + let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) } - logout(:user) + it 'renders' do + visit_issue(project, issue) - login_as(user2) - visit current_path - expect(page).not_to have_selector('.js-task-list-container') + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 1) + expect(page).to have_selector('ul input[checked]', count: 0) + end + + it 'provides a summary on Issues#index' do + visit namespace_project_issues_path(project.namespace, project) + expect(page).to have_content("0 of 1 task completed") + end end - it 'provides a summary on Issues#index' do - visit namespace_project_issues_path(project.namespace, project) - expect(page).to have_content("6 tasks (2 completed, 4 remaining)") + describe 'single complete task' do + let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) } + + it 'renders' do + visit_issue(project, issue) + + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 1) + expect(page).to have_selector('ul input[checked]', count: 1) + end + + it 'provides a summary on Issues#index' do + visit namespace_project_issues_path(project.namespace, project) + expect(page).to have_content("1 of 1 task completed") + end end end describe 'for Notes' do let!(:issue) { create(:issue, author: user, project: project) } - let!(:note) do - create(:note, note: markdown, noteable: issue, - project: project, author: user) + describe 'multiple tasks' do + let!(:note) do + create(:note, note: markdown, noteable: issue, + project: project, author: user) + end + + it 'renders for note body' do + visit_issue(project, issue) + + expect(page).to have_selector('.note ul.task-list', count: 1) + expect(page).to have_selector('.note li.task-list-item', count: 6) + expect(page).to have_selector('.note ul input[checked]', count: 2) + end + + it 'contains the required selectors' do + visit_issue(project, issue) + + expect(page).to have_selector('.note .js-task-list-container') + expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') + expect(page).to have_selector('.note .js-task-list-container .js-task-list-field') + end + + it 'is only editable by author' do + visit_issue(project, issue) + expect(page).to have_selector('.js-task-list-container') + + logout(:user) + + login_as(user2) + visit current_path + expect(page).not_to have_selector('.js-task-list-container') + end end - it 'renders for note body' do - visit_issue(project, issue) - - expect(page).to have_selector('.note ul.task-list', count: 1) - expect(page).to have_selector('.note li.task-list-item', count: 6) - expect(page).to have_selector('.note ul input[checked]', count: 2) - end + describe 'single incomplete task' do + let!(:note) do + create(:note, note: singleIncompleteMarkdown, noteable: issue, + project: project, author: user) + end - it 'contains the required selectors' do - visit_issue(project, issue) + it 'renders for note body' do + visit_issue(project, issue) - expect(page).to have_selector('.note .js-task-list-container') - expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') - expect(page).to have_selector('.note .js-task-list-container .js-task-list-field') + expect(page).to have_selector('.note ul.task-list', count: 1) + expect(page).to have_selector('.note li.task-list-item', count: 1) + expect(page).to have_selector('.note ul input[checked]', count: 0) + end end - it 'is only editable by author' do - visit_issue(project, issue) - expect(page).to have_selector('.js-task-list-container') + describe 'single complete task' do + let!(:note) do + create(:note, note: singleCompleteMarkdown, noteable: issue, + project: project, author: user) + end - logout(:user) + it 'renders for note body' do + visit_issue(project, issue) - login_as(user2) - visit current_path - expect(page).not_to have_selector('.js-task-list-container') + expect(page).to have_selector('.note ul.task-list', count: 1) + expect(page).to have_selector('.note li.task-list-item', count: 1) + expect(page).to have_selector('.note ul input[checked]', count: 1) + end end end @@ -113,42 +197,78 @@ feature 'Task Lists', feature: true do visit namespace_project_merge_request_path(project.namespace, project, merge) end - let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) } + describe 'multiple tasks' do + let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) } - it 'renders for description' do - visit_merge_request(project, merge) + it 'renders for description' do + visit_merge_request(project, merge) - expect(page).to have_selector('ul.task-list', count: 1) - expect(page).to have_selector('li.task-list-item', count: 6) - expect(page).to have_selector('ul input[checked]', count: 2) - end + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 6) + expect(page).to have_selector('ul input[checked]', count: 2) + end - it 'contains the required selectors' do - visit_merge_request(project, merge) + it 'contains the required selectors' do + visit_merge_request(project, merge) - container = '.detail-page-description .description.js-task-list-container' + container = '.detail-page-description .description.js-task-list-container' - expect(page).to have_selector(container) - expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") - expect(page).to have_selector("#{container} .js-task-list-field") - expect(page).to have_selector('form.js-issuable-update') - expect(page).to have_selector('a.btn-close') - end + expect(page).to have_selector(container) + expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") + expect(page).to have_selector('form.js-issuable-update') + expect(page).to have_selector('a.btn-close') + end - it 'is only editable by author' do - visit_merge_request(project, merge) - expect(page).to have_selector('.js-task-list-container') + it 'is only editable by author' do + visit_merge_request(project, merge) + expect(page).to have_selector('.js-task-list-container') - logout(:user) + logout(:user) - login_as(user2) - visit current_path - expect(page).not_to have_selector('.js-task-list-container') + login_as(user2) + visit current_path + expect(page).not_to have_selector('.js-task-list-container') + end + + it 'provides a summary on MergeRequests#index' do + visit namespace_project_merge_requests_path(project.namespace, project) + expect(page).to have_content("2 of 6 tasks completed") + end + end + + describe 'single incomplete task' do + let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) } + + it 'renders for description' do + visit_merge_request(project, merge) + + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 1) + expect(page).to have_selector('ul input[checked]', count: 0) + end + + it 'provides a summary on MergeRequests#index' do + visit namespace_project_merge_requests_path(project.namespace, project) + expect(page).to have_content("0 of 1 task completed") + end end - it 'provides a summary on MergeRequests#index' do - visit namespace_project_merge_requests_path(project.namespace, project) - expect(page).to have_content("6 tasks (2 completed, 4 remaining)") + describe 'single complete task' do + let!(:merge) { create(:merge_request, :simple, description: singleCompleteMarkdown, author: user, source_project: project) } + + it 'renders for description' do + visit_merge_request(project, merge) + + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 1) + expect(page).to have_selector('ul input[checked]', count: 1) + end + + it 'provides a summary on MergeRequests#index' do + visit namespace_project_merge_requests_path(project.namespace, project) + expect(page).to have_content("1 of 1 task completed") + end end end end diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb index 927c72c7409..201614e45a4 100644 --- a/spec/support/taskable_shared_examples.rb +++ b/spec/support/taskable_shared_examples.rb @@ -3,30 +3,57 @@ # Requires a context containing: # subject { Issue or MergeRequest } shared_examples 'a Taskable' do - before do - subject.description = <<-EOT.strip_heredoc - * [ ] Task 1 - * [x] Task 2 - * [x] Task 3 - * [ ] Task 4 - * [ ] Task 5 - EOT + describe 'with multiple tasks' do + before do + subject.description = <<-EOT.strip_heredoc + * [ ] Task 1 + * [x] Task 2 + * [x] Task 3 + * [ ] Task 4 + * [ ] Task 5 + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('2 of') + expect(subject.task_status).to match('5 tasks completed') + end + + describe '#tasks?' do + it 'returns true when object has tasks' do + expect(subject.tasks?).to eq true + end + + it 'returns false when object has no tasks' do + subject.description = 'Now I have no tasks' + expect(subject.tasks?).to eq false + end + end end - it 'returns the correct task status' do - expect(subject.task_status).to match('5 tasks') - expect(subject.task_status).to match('2 completed') - expect(subject.task_status).to match('3 remaining') + describe 'with an incomplete task' do + before do + subject.description = <<-EOT.strip_heredoc + * [ ] Task 1 + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('0 of') + expect(subject.task_status).to match('1 task completed') + end end - describe '#tasks?' do - it 'returns true when object has tasks' do - expect(subject.tasks?).to eq true + describe 'with a complete task' do + before do + subject.description = <<-EOT.strip_heredoc + * [x] Task 1 + EOT end - it 'returns false when object has no tasks' do - subject.description = 'Now I have no tasks' - expect(subject.tasks?).to eq false + it 'returns the correct task status' do + expect(subject.task_status).to match('1 of') + expect(subject.task_status).to match('1 task completed') end end end -- cgit v1.2.1 From 8581e152ef8fa27b6670760d39b7f06dab5f796b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 27 Jul 2016 13:46:46 -0500 Subject: Add last commit time to repo view --- CHANGELOG | 1 + app/assets/stylesheets/pages/tree.scss | 10 ++++++++++ app/views/projects/tree/_tree_content.html.haml | 11 ++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 83a5d1727f3..09692af3b9c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.12.0 (unreleased) - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Fix markdown help references (ClemMakesApps) + - Add last commit time to repo view (ClemMakesApps) - Added tests for diff notes - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 9da40fe2b09..538f211c65b 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -11,6 +11,16 @@ } } + .last-commit { + max-width: 506px; + + .last-commit-content { + @include str-truncated; + width: calc(100% - 140px); + margin-left: 3px; + } + } + .tree-table { margin-bottom: 0; diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 558e6146ae9..ca5d2d7722a 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -5,16 +5,17 @@ %tr %th Name %th Last Update - %th.hidden-xs - .pull-left Last Commit - .last-commit.hidden-sm.pull-left -   + %th.hidden-xs.last-commit + Last Commit + .last-commit-content.hidden-sm %i.fa.fa-angle-right   %small.light = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" – - = truncate(@commit.title, length: 50) + = time_ago_with_tooltip(@commit.committed_date) + – + = @commit.full_title = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right' - if @path.present? -- cgit v1.2.1 From cfbab1bc73a1a93c4b5fb5b4143af01fe29a2ec6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 22 Aug 2016 16:31:31 -0500 Subject: Add white background for no readme container --- CHANGELOG | 1 + app/assets/stylesheets/pages/projects.scss | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 83a5d1727f3..ca78eff6ac9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.12.0 (unreleased) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) + - Add white background for no readme container (ClemMakesApps) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index eaf2d3270b3..83500a687bb 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -600,18 +600,25 @@ pre.light-well { } } -.project-show-readme .readme-holder { - padding: $gl-padding 0; - border-top: 0; - - .edit-project-readme { - z-index: 2; - position: relative; +.project-show-readme { + .row-content-block { + background-color: inherit; + border: none; } - .wiki h1 { - border-bottom: none; - padding: 0; + .readme-holder { + padding: $gl-padding 0; + border-top: 0; + + .edit-project-readme { + z-index: 2; + position: relative; + } + + .wiki h1 { + border-bottom: none; + padding: 0; + } } } -- cgit v1.2.1 From 2fe2f67da8c325309bd2a0aee06e0068ac7061c4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 29 Aug 2016 11:05:22 -0500 Subject: Fix inconsistent background color for filter input field --- CHANGELOG | 1 + app/assets/stylesheets/framework/nav.scss | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 83a5d1727f3..b661f929024 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.12.0 (unreleased) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import + - Fix inconsistent background color for filter input field (ClemMakesApps) - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Fix groups sort dropdown alignment (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ef2fe844f94..ba0a167c419 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -211,12 +211,6 @@ } } - .project-filter-form { - input { - background-color: $background-color; - } - } - @media (max-width: $screen-xs-max) { padding-bottom: 0; width: 100%; -- cgit v1.2.1 From 48e53bf07a5fde2fa81cc16f4d3218d72ad85673 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 29 Aug 2016 12:18:10 -0500 Subject: Remove unused mixins --- CHANGELOG | 1 + app/assets/stylesheets/framework/mixins.scss | 41 ---------------------------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 83a5d1727f3..1cfdffa63fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.12.0 (unreleased) - Remove Gitorious import - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Remove unused mixins (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Fix markdown help references (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index d2d60ed7196..9fb3bcb271e 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -9,22 +9,6 @@ border-radius: $radius; } -@mixin border-radius-left($radius) { - @include border-radius($radius 0 0 $radius) -} - -@mixin border-radius-right($radius) { - @include border-radius(0 0 $radius $radius) -} - -@mixin linear-gradient($from, $to) { - background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); - background-image: -webkit-linear-gradient($from, $to); - background-image: -moz-linear-gradient($from, $to); - background-image: -ms-linear-gradient($from, $to); - background-image: -o-linear-gradient($from, $to); -} - @mixin transition($transition) { -webkit-transition: $transition; -moz-transition: $transition; @@ -38,14 +22,6 @@ * Mixins with fixed values */ -@mixin shade { - @include box-shadow(0 0 3px #ddd); -} - -@mixin solid-shade { - @include box-shadow(0 0 0 3px #f1f1f1); -} - @mixin str-truncated($max_width: 82%) { display: inline-block; overflow: hidden; @@ -94,23 +70,6 @@ } } -@mixin input-big { - height: 36px; - padding: 5px 10px; - font-size: 16px; - line-height: 24px; - color: #7f8fa4; - background-color: #fff; - border-color: #e7e9ed; -} - -@mixin btn-big { - height: 36px; - padding: 5px 10px; - font-size: 16px; - line-height: 24px; -} - @mixin bulleted-list { > ul { list-style-type: disc; -- cgit v1.2.1 From 157c3acb69952eb5ee256b72872e80921bd70f98 Mon Sep 17 00:00:00 2001 From: Bryce Date: Fri, 26 Aug 2016 17:12:26 +0200 Subject: Make push events have equal vertical spacing. --- CHANGELOG | 1 + app/assets/stylesheets/pages/events.scss | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d06fc24d40a..137b891d7f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 - Add font color contrast to external label in admin area (ClemMakesApps) - Change merge_error column from string to text type diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 5c336bb1c7e..0cd45fb90bf 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -115,11 +115,8 @@ } &.commits-stat { - margin-top: 3px; display: block; - padding: 3px; - padding-left: 0; - + padding: 0 3px 0 0; &:hover { background: none; } -- cgit v1.2.1 From 19e58c9c9246c8c60f3ce6f508b3f8afc165a292 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 24 Aug 2016 08:34:08 -0500 Subject: Move status icon out of build graph links --- CHANGELOG | 1 + app/views/projects/ci/builds/_build_pipeline.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 83a5d1727f3..92dd5d44f08 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.12.0 (unreleased) - Capitalize mentioned issue timeline notes (ClemMakesApps) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML + - Fix hover leading space bug in pipeline graph !5980 v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index ebdf60ae370..b399811db1c 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -3,12 +3,12 @@ .curve .build-content - if is_playable + = render_status_with_link('build', 'play') = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do - = render_status_with_link('build', 'play') = subject.name - elsif can?(current_user, :read_build, @project) + = render_status_with_link('build', subject.status) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = render_status_with_link('build', subject.status) = subject.name - else = render_status_with_link('build', subject.status) -- cgit v1.2.1 From 3e1467ecdbea94dddd4ddaf167ebdcd87de76497 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 26 Aug 2016 17:34:21 -0500 Subject: Keep CI icons being clickable --- app/assets/stylesheets/pages/pipelines.scss | 8 ++++++++ app/views/projects/ci/builds/_build_pipeline.html.haml | 8 ++++---- .../_generic_commit_status_pipeline.html.haml | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 743fe89a9f4..0dcf61dd2dd 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -319,6 +319,14 @@ a { color: $layout-link-gray; + text-decoration: none; + + &:hover { + .ci-status-text { + text-decoration: underline; + } + } + } } diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index b399811db1c..36fb0300aeb 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -3,13 +3,13 @@ .curve .build-content - if is_playable - = render_status_with_link('build', 'play') = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do - = subject.name + = render_status_with_link('build', 'play') + %span.ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) - = render_status_with_link('build', subject.status) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = subject.name + = render_status_with_link('build', subject.status) + %span.ci-status-text= subject.name - else = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 584c0fa18ae..31d40f6ad03 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -3,7 +3,7 @@ - if subject.target_url - link_to subject.target_url do = render_status_with_link('commit status', subject.status) - = subject.name + %span.ci-status-text= subject.name - else = render_status_with_link('commit status', subject.status) - = subject.name + %span.ci-status-text= subject.name -- cgit v1.2.1 From 4cccfc0f171944cd6f2ffaf49e48cf005dcf985e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 22 Aug 2016 11:50:41 -0300 Subject: Fix issue boards leak private label names and descriptions --- app/services/boards/lists/create_service.rb | 9 +++-- .../projects/boards/lists_controller_spec.rb | 39 +++++++++++++--------- spec/services/boards/lists/create_service_spec.rb | 11 +++++- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index 5cb408b9d20..b1887820bd4 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -3,7 +3,10 @@ module Boards class CreateService < Boards::BaseService def execute List.transaction do - create_list_at(next_position) + label = project.labels.find(params[:label_id]) + position = next_position + + create_list(label, position) end end @@ -14,8 +17,8 @@ module Boards max_position.nil? ? 0 : max_position.succ end - def create_list_at(position) - board.lists.create(params.merge(list_type: :label, position: position)) + def create_list(label, position) + board.lists.create(label: label, list_type: :label, position: position) end end end diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index 9496636e3cc..261f35f28ed 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -39,7 +39,7 @@ describe Projects::Boards::ListsController do allow(Ability.abilities).to receive(:allowed?).with(user, :read_list, project).and_return(false) end - it 'returns a successful 403 response' do + it 'returns a forbidden 403 response' do read_board_list user: user expect(response).to have_http_status(403) @@ -56,9 +56,9 @@ describe Projects::Boards::ListsController do end describe 'POST create' do - let(:label) { create(:label, project: project, name: 'Development') } - context 'with valid params' do + let(:label) { create(:label, project: project, name: 'Development') } + it 'returns a successful 200 response' do create_board_list user: user, label_id: label.id @@ -73,20 +73,29 @@ describe Projects::Boards::ListsController do end context 'with invalid params' do - it 'returns an error' do - create_board_list user: user, label_id: nil + context 'when label is nil' do + it 'returns a not found 404 response' do + create_board_list user: user, label_id: nil + + expect(response).to have_http_status(404) + end + end - parsed_response = JSON.parse(response.body) + context 'when label that does not belongs to project' do + it 'returns a not found 404 response' do + label = create(:label, name: 'Development') - expect(parsed_response['label']).to contain_exactly "can't be blank" - expect(response).to have_http_status(422) + create_board_list user: user, label_id: label.id + + expect(response).to have_http_status(404) + end end end context 'with unauthorized user' do - let(:label) { create(:label, project: project, name: 'Development') } + it 'returns a forbidden 403 response' do + label = create(:label, project: project, name: 'Development') - it 'returns a successful 403 response' do create_board_list user: guest, label_id: label.id expect(response).to have_http_status(403) @@ -122,7 +131,7 @@ describe Projects::Boards::ListsController do end context 'with invalid position' do - it 'returns a unprocessable entity 422 response' do + it 'returns an unprocessable entity 422 response' do move user: user, list: planning, position: 6 expect(response).to have_http_status(422) @@ -138,7 +147,7 @@ describe Projects::Boards::ListsController do end context 'with unauthorized user' do - it 'returns a successful 403 response' do + it 'returns a forbidden 403 response' do move user: guest, list: planning, position: 6 expect(response).to have_http_status(403) @@ -180,7 +189,7 @@ describe Projects::Boards::ListsController do end context 'with unauthorized user' do - it 'returns a successful 403 response' do + it 'returns a forbidden 403 response' do remove_board_list user: guest, list: planning expect(response).to have_http_status(403) @@ -213,7 +222,7 @@ describe Projects::Boards::ListsController do end context 'when board lists is not empty' do - it 'returns a unprocessable entity 422 response' do + it 'returns an unprocessable entity 422 response' do create(:list, board: board) generate_default_board_lists user: user @@ -223,7 +232,7 @@ describe Projects::Boards::ListsController do end context 'with unauthorized user' do - it 'returns a successful 403 response' do + it 'returns a forbidden 403 response' do generate_default_board_lists user: guest expect(response).to have_http_status(403) diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index 5e7e145065e..90764b86b16 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -5,7 +5,7 @@ describe Boards::Lists::CreateService, services: true do let(:project) { create(:project_with_board) } let(:board) { project.board } let(:user) { create(:user) } - let(:label) { create(:label, name: 'in-progress') } + let(:label) { create(:label, project: project, name: 'in-progress') } subject(:service) { described_class.new(project, user, label_id: label.id) } @@ -50,5 +50,14 @@ describe Boards::Lists::CreateService, services: true do expect(list2.reload.position).to eq 1 end end + + context 'when provided label does not belongs to the project' do + it 'raises an error' do + label = create(:label, name: 'in-development') + service = described_class.new(project, user, label_id: label.id) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end end end -- cgit v1.2.1 From 6a18b3a563d6e57e5787512838aae76afb39f1ca Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 29 Aug 2016 16:05:38 -0300 Subject: Update CHANGELOG --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f4c850fe00c..3ab9d45ca7d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,9 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML +v 8.11.4 (unreleased) + - Fix issue boards leak private label names and descriptions + v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label -- cgit v1.2.1 From 5f7f98ff6a50c64a32fc2a91f59154112d57ecae Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 27 Aug 2016 12:50:55 -0500 Subject: Remove vendor prefixes for linear-gradient CSS --- CHANGELOG | 1 + app/assets/stylesheets/framework/nav.scss | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3548115dff3..f7f97e11646 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Add two-factor recovery endpoint to internal API !5510 + - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) - Add font color contrast to external label in admin area (ClemMakesApps) - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index cf7cf125504..f0e134ebc16 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -8,10 +8,7 @@ height: 30px; transition-duration: .3s; -webkit-transform: translateZ(0); - background: -webkit-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); - background: -o-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); - background: -moz-linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); - background: linear-gradient($gradient-direction, rgba($gradient-color, 0.4), $gradient-color 45%); + background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4)); &.scrolling { visibility: visible; -- cgit v1.2.1 From d30d9928132adede3ca189dad8d2afa066a7abf1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 29 Aug 2016 22:08:02 +0200 Subject: Use `git grep` instead of `grep` [ci skip] --- doc/development/doc_styleguide.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 4fe69e63d4f..066d29caf19 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -222,12 +222,12 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to ``` 1. Find and replace any occurrences of the old location with the new one. - A quick way to find them is to use `grep`. First go to the root directory + A quick way to find them is to use `git grep`. First go to the root directory where you cloned the `gitlab-ce` repository and then do: ``` - grep -nR "workflow/lfs/lfs_administration" app/ doc/ spec/ - grep -nR "lfs/lfs_administration" app/ doc/ spec/ + git grep -n "workflow/lfs/lfs_administration" + git grep -n "lfs/lfs_administration" ``` Things to note: @@ -235,13 +235,13 @@ Things to note: - Since we also use inline documentation, except for the documentation itself, the document might also be referenced in the views of GitLab (`app/`) which will render when visiting `/help`, and sometimes in the testing suite (`spec/`). -- The above `grep` command will search in the `app/`, `doc/` and `spec/` - directories for `workflow/lfs/lfs_administration` and `lfs/lfs_administration` - recursively and will print the file and the line where this file is mentioned. +- The above `git grep` command will search recursively in the directory you run + it in for `workflow/lfs/lfs_administration` and `lfs/lfs_administration` + and will print the file and the line where this file is mentioned. You may ask why the two greps. Since we use relative paths to link to documentation, sometimes it might be useful to search a path deeper. - The `*.md` extension is not used when a document is linked to GitLab's - built-in help page, that's why we omit it in `grep`. + built-in help page, that's why we omit it in `git grep`. ## Configuration documentation for source and Omnibus installations -- cgit v1.2.1 From 32551faeba232b64cb6b5cbf01eff341abe39174 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Tue, 30 Aug 2016 08:02:29 +0300 Subject: Minor code refactor for inlining functions. --- app/assets/javascripts/logo.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js index e5d4fd44c96..7d8eef1b495 100644 --- a/app/assets/javascripts/logo.js +++ b/app/assets/javascripts/logo.js @@ -1,16 +1,12 @@ (function() { Turbolinks.enableProgressBar(); - start = function() { + $(document).on('page:fetch', function() { $('.tanuki-logo').addClass('animate'); - }; + }); - stop = function() { + $(document).on('page:change', function() { $('.tanuki-logo').removeClass('animate'); - }; - - $(document).on('page:fetch', start); - - $(document).on('page:change', stop); + }); }).call(this); -- cgit v1.2.1 From 43d50117187db1d8e034dbfc01e894a108f55369 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 25 Aug 2016 16:42:20 +0100 Subject: Fix diff comments on legacy MRs --- CHANGELOG | 3 +++ app/models/legacy_diff_note.rb | 4 ++++ spec/features/merge_requests/diff_notes_spec.rb | 31 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index df8dec7bdde..f8391e996fb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,9 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML +v 8.11.4 (unreleased) + - Fix diff commenting on merge requests created prior to 8.10 + v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 40277a9b139..0e1649aafe5 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -53,6 +53,10 @@ class LegacyDiffNote < Note self.line_code end + def to_discussion + Discussion.new([self]) + end + # Check if this note is part of an "active" discussion # # This will always return true for anything except MergeRequest noteables, diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb index a818679a874..06fad1007e8 100644 --- a/spec/features/merge_requests/diff_notes_spec.rb +++ b/spec/features/merge_requests/diff_notes_spec.rb @@ -147,6 +147,37 @@ feature 'Diff notes', js: true, feature: true do end end + context 'when the MR only supports legacy diff notes' do + before do + @merge_request.merge_request_diff.update_attributes(start_commit_sha: nil) + visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, view: 'inline') + end + + context 'with a new line' do + it 'should allow commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]')) + end + end + + context 'with an old line' do + it 'should allow commenting' do + should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]')) + end + end + + context 'with an unchanged line' do + it 'should allow commenting' do + should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]')) + end + end + + context 'with a match line' do + it 'should not allow commenting' do + should_not_allow_commenting(find('.match', match: :first)) + end + end + end + def should_allow_commenting(line_holder, diff_side = nil) line = get_line_components(line_holder, diff_side) line[:content].hover -- cgit v1.2.1 From 19c9ee4752717fb25529ef8b4dba7bc29616a076 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Aug 2016 10:32:19 +0100 Subject: Added search for all lists on issue boards Closes #21139 --- app/assets/javascripts/boards/boards_bundle.js.es6 | 7 +++ .../javascripts/boards/components/board.js.es6 | 10 +--- app/assets/javascripts/boards/models/list.js.es6 | 4 -- .../javascripts/boards/stores/boards_store.js.es6 | 3 +- app/assets/stylesheets/pages/boards.scss | 45 ++++----------- .../projects/boards/components/_board.html.haml | 5 -- app/views/shared/issuable/_filter.html.haml | 21 ++++--- spec/features/boards/boards_spec.rb | 65 +++++++++++++--------- spec/javascripts/boards/list_spec.js.es6 | 9 --- 9 files changed, 71 insertions(+), 98 deletions(-) diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index a612cf0f1ae..91c12570e09 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -54,4 +54,11 @@ $(() => { }); } }); + + gl.IssueBoardsSearch = new Vue({ + el: '#js-boards-seach', + data: { + filters: Store.state.filters + } + }); }); diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index d7f4107cb02..2e3a8f6870b 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -21,15 +21,10 @@ }, data () { return { - query: '', filters: Store.state.filters }; }, watch: { - query () { - this.list.filters = this.getFilterData(); - this.list.getIssues(true); - }, filters: { handler () { this.list.page = 1; @@ -40,10 +35,7 @@ }, methods: { getFilterData () { - const filters = this.filters; - let queryData = { search: this.query }; - - Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; }); + Object.keys(this.filters).forEach((key) => { queryData[key] = this.filters[key]; }); return queryData; } diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index be2b8c568a8..816fa49516c 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -58,10 +58,6 @@ class List { } } - canSearch () { - return this.type === 'backlog'; - } - getIssues (emptyIssues = true) { const filters = this.filters; let data = { page: this.page }; diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index 18f26a1f911..bd07ee0c161 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -15,7 +15,8 @@ author_id: gl.utils.getParameterValues('author_id')[0], assignee_id: gl.utils.getParameterValues('assignee_id')[0], milestone_title: gl.utils.getParameterValues('milestone_title')[0], - label_name: gl.utils.getParameterValues('label_name[]') + label_name: gl.utils.getParameterValues('label_name[]'), + search: '' }; }, addList (listObj) { diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 9ac4d801ac4..d91558bc672 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -10,7 +10,7 @@ .is-dragging { // Important because plugin sets inline CSS opacity: 1!important; - + * { // !important to make sure no style can override this when dragging cursor: -webkit-grabbing!important; @@ -160,40 +160,6 @@ border-bottom: 1px solid $border-color; } -.board-search-container { - position: relative; - background-color: #fff; - - .form-control { - padding-right: 30px; - } -} - -.board-search-icon, -.board-search-clear-btn { - position: absolute; - right: $gl-padding + 10px; - top: 50%; - margin-top: -7px; - font-size: 14px; -} - -.board-search-icon { - color: $gl-placeholder-color; -} - -.board-search-clear-btn { - padding: 0; - line-height: 1; - background: transparent; - border: 0; - outline: 0; - - &:hover { - color: $gl-link-color; - } -} - .board-delete { margin-right: 10px; padding: 0; @@ -304,3 +270,12 @@ margin-right: 8px; font-weight: 500; } + +.issue-boards-search { + width: 335px; + + .form-control { + display: inline-block; + width: 210px; + } +} diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index de53a298f84..6b4bfe0c354 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -21,11 +21,6 @@ %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" } = icon("trash") = icon("spinner spin", class: "board-header-loading-spinner pull-right", "v-show" => "list.loadingMore") - .board-inner-container.board-search-container{ "v-if" => "list.canSearch()" } - %input.form-control{ type: "text", placeholder: "Search issues", "v-model" => "query", "debounce" => "250" } - = icon("search", class: "board-search-icon", "v-show" => "!query") - %button.board-search-clear-btn{ type: "button", role: "button", "aria-label" => "Clear search", "@click" => "query = ''", "v-show" => "query" } - = icon("times", class: "board-search-clear") %board-list{ "inline-template" => true, "v-if" => "list.type !== 'blank'", ":list" => "list", diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 4f8ea7e7cef..0f4f744a71f 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -27,15 +27,18 @@ = render "shared/issuable/label_dropdown" .pull-right - - if controller.controller_name == 'boards' && can?(current_user, :admin_list, @project) - .dropdown - %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } } - Create new list - .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" } - - if can?(current_user, :admin_label, @project) - = render partial: "shared/issuable/label_page_create" - = dropdown_loading + - if controller.controller_name == 'boards' + #js-boards-seach.issue-boards-search + %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" } + - if can?(current_user, :admin_list, @project) + .dropdown.pull-right + %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } } + Create new list + .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable + = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" } + - if can?(current_user, :admin_label, @project) + = render partial: "shared/issuable/label_page_create" + = dropdown_loading - else = render 'shared/sort_dropdown' diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 5d777895542..55e5dc15428 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -110,6 +110,45 @@ describe 'Issue Boards', feature: true, js: true do end end + it 'search backlog list' do + page.within('#js-boards-seach') do + find('.form-control').set(issue1.title) + end + + wait_for_vue_resource + + expect(find('.board:nth-child(1)')).to have_selector('.card', count: 1) + expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0) + end + + it 'search done list' do + page.within('#js-boards-seach') do + find('.form-control').set(issue8.title) + end + + wait_for_vue_resource + + expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1) + end + + it 'search list' do + page.within('#js-boards-seach') do + find('.form-control').set(issue5.title) + end + + wait_for_vue_resource + + expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1) + expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0) + end + it 'allows user to delete board' do page.within(find('.board:nth-child(2)')) do find('.board-delete').click @@ -162,32 +201,6 @@ describe 'Issue Boards', feature: true, js: true do end end - it 'is searchable' do - page.within(find('.board', match: :first)) do - find('.form-control').set issue1.title - - wait_for_vue_resource(spinner: false) - - expect(page).to have_selector('.card', count: 1) - end - end - - it 'clears search' do - page.within(find('.board', match: :first)) do - find('.form-control').set issue1.title - - expect(page).to have_selector('.card', count: 1) - - find('.board-search-clear-btn').click - end - - wait_for_vue_resource - - page.within(find('.board', match: :first)) do - expect(page).to have_selector('.card', count: 6) - end - end - it 'moves issue from backlog into list' do drag_to(list_to_index: 1) diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6 index c206b794442..1688b996162 100644 --- a/spec/javascripts/boards/list_spec.js.es6 +++ b/spec/javascripts/boards/list_spec.js.es6 @@ -60,15 +60,6 @@ describe('List model', () => { }, 0); }); - it('can\'t search when not backlog', () => { - expect(list.canSearch()).toBe(false); - }); - - it('can search when backlog', () => { - list.type = 'backlog'; - expect(list.canSearch()).toBe(true); - }); - it('gets issue from list', (done) => { setTimeout(() => { const issue = list.findIssue(1); -- cgit v1.2.1 From e73f25ce0cfde201987c2f341ffa704ce2b2e79d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Aug 2016 10:34:15 +0100 Subject: CHANGELOG item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index a6cd8f4c7e1..13366a2a543 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.12.0 (unreleased) - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Remove unused mixins (ClemMakesApps) + - Add search to all issue board lists - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Fix markdown help references (ClemMakesApps) -- cgit v1.2.1 From 66948d5f3d1bd634c13a984ae34f7ec5cff9f09c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Aug 2016 10:35:42 +0100 Subject: Removed unused method from component --- app/assets/javascripts/boards/components/board.js.es6 | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index 2e3a8f6870b..7e86f001f44 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -33,13 +33,6 @@ deep: true } }, - methods: { - getFilterData () { - Object.keys(this.filters).forEach((key) => { queryData[key] = this.filters[key]; }); - - return queryData; - } - }, ready () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ disabled: this.disabled, -- cgit v1.2.1 From 1bda1e62def69bc0525a558f92acf182dc05fe8d Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 29 Aug 2016 12:43:09 +0100 Subject: Fix resolving conflicts on forks Forks may not be up-to-date with the target project, and so might not contain one of the parent refs in their repo. Fetch this if it isn't present. --- CHANGELOG | 3 + app/services/merge_requests/resolve_service.rb | 16 +++- .../merge_requests/resolve_service_spec.rb | 87 ++++++++++++++++++++++ spec/support/test_env.rb | 47 +++++++----- 4 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 spec/services/merge_requests/resolve_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index d06fc24d40a..d95072d9952 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,9 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML +v 8.11.4 (unreleased) + - Fix resolving conflicts on forks + v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb index adc71b0c2bc..bd8b9f8cfd4 100644 --- a/app/services/merge_requests/resolve_service.rb +++ b/app/services/merge_requests/resolve_service.rb @@ -1,11 +1,14 @@ module MergeRequests class ResolveService < MergeRequests::BaseService - attr_accessor :conflicts, :rugged, :merge_index + attr_accessor :conflicts, :rugged, :merge_index, :merge_request def execute(merge_request) @conflicts = merge_request.conflicts @rugged = project.repository.rugged @merge_index = conflicts.merge_index + @merge_request = merge_request + + fetch_their_commit! conflicts.files.each do |file| write_resolved_file_to_index(file, params[:sections]) @@ -27,5 +30,16 @@ module MergeRequests merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) merge_index.conflict_remove(our_path) end + + # If their commit (in the target project) doesn't exist in the source project, it + # can't be a parent for the merge commit we're about to create. If that's the case, + # fetch the target branch ref into the source project so the commit exists in both. + # + def fetch_their_commit! + return if rugged.include?(conflicts.their_commit.oid) + + remote = rugged.remotes.create_anonymous(merge_request.target_project.repository.path_to_repo) + remote.fetch(merge_request.target_branch) + end end end diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb new file mode 100644 index 00000000000..d71932458fa --- /dev/null +++ b/spec/services/merge_requests/resolve_service_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe MergeRequests::ResolveService do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:fork_project) do + create(:forked_project_with_submodules) do |fork_project| + fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) + fork_project.save + end + end + + let(:merge_request) do + create(:merge_request, + source_branch: 'conflict-resolvable', source_project: project, + target_branch: 'conflict-start') + end + + let(:merge_request_from_fork) do + create(:merge_request, + source_branch: 'conflict-resolvable-fork', source_project: fork_project, + target_branch: 'conflict-start', target_project: project) + end + + describe '#execute' do + context 'with valid params' do + let(:params) do + { + sections: { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + }, + commit_message: 'This is a commit message!' + } + end + + context 'when the source and target project are the same' do + before do + MergeRequests::ResolveService.new(project, user, params).execute(merge_request) + end + + it 'creates a commit with the message' do + expect(merge_request.source_branch_head.message).to eq(params[:commit_message]) + end + + it 'creates a commit with the correct parents' do + expect(merge_request.source_branch_head.parents.map(&:id)). + to eq(['1450cd639e0bc6721eb02800169e464f212cde06', + '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b']) + end + end + + context 'when the source project is a fork and does not contain the HEAD of the target branch' do + let!(:target_head) do + project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false) + end + + before do + MergeRequests::ResolveService.new(fork_project, user, params).execute(merge_request_from_fork) + end + + it 'creates a commit with the message' do + expect(merge_request_from_fork.source_branch_head.message).to eq(params[:commit_message]) + end + + it 'creates a commit with the correct parents' do + expect(merge_request_from_fork.source_branch_head.parents.map(&:id)). + to eq(['404fa3fc7c2c9b5dacff102f353bdf55b1be2813', + target_head]) + end + end + end + + context 'when a resolution is missing' do + let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } } + let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) } + + it 'raises a MissingResolution error' do + expect { service.execute(merge_request) }. + to raise_error(Gitlab::Conflict::File::MissingResolution) + end + end + end +end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index c7a45fc4ff9..0097dbf8fad 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -6,7 +6,7 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { 'empty-branch' => '7efb185', - 'ends-with.json' => '98b0d8b3', + 'ends-with.json' => '98b0d8b', 'flatten-dir' => 'e56497b', 'feature' => '0b4bc9a', 'feature_conflict' => 'bb5206f', @@ -37,9 +37,10 @@ module TestEnv # need to keep all the branches in sync. # We currently only need a subset of the branches FORKED_BRANCH_SHA = { - 'add-submodule-version-bump' => '3f547c08', - 'master' => '5937ac0', - 'remove-submodule' => '2a33e0c0' + 'add-submodule-version-bump' => '3f547c0', + 'master' => '5937ac0', + 'remove-submodule' => '2a33e0c', + 'conflict-resolvable-fork' => '404fa3f' } # Test environment @@ -117,22 +118,7 @@ module TestEnv system(*%W(#{Gitlab.config.git.bin_path} clone -q #{clone_url} #{repo_path})) end - Dir.chdir(repo_path) do - branch_sha.each do |branch, sha| - # Try to reset without fetching to avoid using the network. - reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha}) - unless system(*reset) - if system(*%W(#{Gitlab.config.git.bin_path} fetch origin)) - unless system(*reset) - raise 'The fetched test seed '\ - 'does not contain the required revision.' - end - else - raise 'Could not fetch test seed repository.' - end - end - end - end + set_repo_refs(repo_path, branch_sha) # We must copy bare repositories because we will push to them. system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare})) @@ -144,6 +130,7 @@ module TestEnv FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path + set_repo_refs(target_repo_path, BRANCH_SHA) end def repos_path @@ -160,6 +147,7 @@ module TestEnv FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path + set_repo_refs(target_repo_path, FORKED_BRANCH_SHA) end # When no cached assets exist, manually hit the root path to create them @@ -209,4 +197,23 @@ module TestEnv def git_env { 'GIT_TEMPLATE_DIR' => '' } end + + def set_repo_refs(repo_path, branch_sha) + Dir.chdir(repo_path) do + branch_sha.each do |branch, sha| + # Try to reset without fetching to avoid using the network. + reset = %W(#{Gitlab.config.git.bin_path} update-ref refs/heads/#{branch} #{sha}) + unless system(*reset) + if system(*%W(#{Gitlab.config.git.bin_path} fetch origin)) + unless system(*reset) + raise 'The fetched test seed '\ + 'does not contain the required revision.' + end + else + raise 'Could not fetch test seed repository.' + end + end + end + end + end end -- cgit v1.2.1 From c9c2503c5186a38302ed606f793b52ffa394f52c Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 26 Jul 2016 13:57:43 +0200 Subject: User can edit closed MR with deleted fork Add test for closed MR without fork Add view test visibility of Reopen and Close buttons Fix controller tests and validation method Fix missing space Remove unused variables from test closed_without_fork? method refactoring Add information about missing fork When closed MR without fork can't edit target branch Tests for closed MR edit view Fix indentation and rebase, refactoring --- CHANGELOG | 1 + app/helpers/merge_requests_helper.rb | 2 +- app/models/merge_request.rb | 33 ++++++----- app/services/merge_requests/update_service.rb | 4 ++ .../merge_requests/show/_mr_title.html.haml | 4 ++ app/views/shared/issuable/_form.html.haml | 41 +++++++------- .../projects/merge_requests_controller_spec.rb | 29 ++++++++++ spec/models/merge_request_spec.rb | 64 ++++++++++++++++++++++ .../projects/merge_requests/edit.html.haml_spec.rb | 41 ++++++++++++++ .../projects/merge_requests/show.html.haml_spec.rb | 40 ++++++++++++++ 10 files changed, 223 insertions(+), 36 deletions(-) create mode 100644 spec/views/projects/merge_requests/edit.html.haml_spec.rb create mode 100644 spec/views/projects/merge_requests/show.html.haml_spec.rb diff --git a/CHANGELOG b/CHANGELOG index a6cd8f4c7e1..cc98863dac8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -223,6 +223,7 @@ v 8.10.6 - Restore "Largest repository" sort option on Admin > Projects page. !5797 - Fix privilege escalation via project export. - Require administrator privileges to perform a project import. + - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 v 8.10.5 - Add a data migration to fix some missing timestamps in the members table. !5670 diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index db6e731c744..a9e175c3f5c 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -98,6 +98,6 @@ module MergeRequestsHelper end def merge_request_button_visibility(merge_request, closed) - return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) + return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1d05e4a85d1..b41a1f0c547 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -91,13 +91,13 @@ class MergeRequest < ActiveRecord::Base end end - validates :source_project, presence: true, unless: [:allow_broken, :importing?] + validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?] validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true validates :merge_user, presence: true, if: :merge_when_build_succeeds? - validate :validate_branches, unless: [:allow_broken, :importing?] - validate :validate_fork + validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] + validate :validate_fork, unless: :closed_without_fork? scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } @@ -305,19 +305,22 @@ class MergeRequest < ActiveRecord::Base def validate_fork return true unless target_project && source_project + return true if target_project == source_project + return true unless fork_missing? - if target_project == source_project - true - else - # If source and target projects are different - # we should check if source project is actually a fork of target project - if source_project.forked_from?(target_project) - true - else - errors.add :validate_fork, - 'Source project is not a fork of target project' - end - end + errors.add :validate_fork, + 'Source project is not a fork of target project' + end + + def closed_without_fork? + closed? && fork_missing? + end + + def fork_missing? + return false unless for_fork? + return true unless source_project + + !source_project.forked_from?(target_project) end def ensure_merge_request_diff diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 30c5f24988c..398ec47f0ea 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -11,6 +11,10 @@ module MergeRequests params.except!(:target_project_id) params.except!(:source_branch) + if merge_request.closed_without_fork? + params.except!(:target_branch, :force_remove_source_branch) + end + merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) update(merge_request) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 098ce19da21..48016645019 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,3 +1,7 @@ +- if @merge_request.closed_without_fork? + .alert.alert-danger + %p Source project is not a fork of the target project + .clearfix.detail-page-header .issuable-header .issuable-status-box.status-box{ class: status_box_class(@merge_request) } diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 22594b46443..75753a6b0af 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -135,28 +135,29 @@ = icon('question-circle') - if issuable.is_a?(MergeRequest) - %hr - - if @merge_request.new_record? + - unless @merge_request.closed_without_fork? + %hr + - if @merge_request.new_record? + .form-group + = f.label :source_branch, class: 'control-label' + .col-sm-10 + .issuable-form-select-holder + = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) .form-group - = f.label :source_branch, class: 'control-label' + = f.label :target_branch, class: 'control-label' .col-sm-10 .issuable-form-select-holder - = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) - .form-group - = f.label :target_branch, class: 'control-label' - .col-sm-10 - .issuable-form-select-holder - = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) - - if @merge_request.new_record? -   - = link_to 'Change branches', mr_change_branches_path(@merge_request) - - if @merge_request.can_remove_source_branch?(current_user) - .form-group - .col-sm-10.col-sm-offset-2 - .checkbox - = label_tag 'merge_request[force_remove_source_branch]' do - = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch? - Remove source branch when merge request is accepted. + = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) + - if @merge_request.new_record? +   + = link_to 'Change branches', mr_change_branches_path(@merge_request) + - if @merge_request.can_remove_source_branch?(current_user) + .form-group + .col-sm-10.col-sm-offset-2 + .checkbox + = label_tag 'merge_request[force_remove_source_branch]' do + = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch? + Remove source branch when merge request is accepted. - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) .row-content-block{class: (is_footer ? "footer-block" : "middle-block")} @@ -175,7 +176,7 @@ = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel' - else .pull-right - - if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) + - if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project) = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c64c2b075c5..f95c3fc771b 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -170,6 +170,35 @@ describe Projects::MergeRequestsController do expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request]) expect(merge_request.reload.closed?).to be_truthy end + + it 'allow to edit closed MR' do + merge_request.close! + + put :update, + namespace_id: project.namespace.path, + project_id: project.path, + id: merge_request.iid, + merge_request: { + title: 'New title' + } + + expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request]) + expect(merge_request.reload.title).to eq 'New title' + end + + it 'does not allow to update target branch closed MR' do + merge_request.close! + + put :update, + namespace_id: project.namespace.path, + project_id: project.path, + id: merge_request.iid, + merge_request: { + target_branch: 'new_branch' + } + + expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch } + end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d67f71bbb9c..5fea6adf329 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -962,4 +962,68 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy end end + + describe "#fork_missing?" do + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:user) { create(:user) } + let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + + context "when fork exists" do + let(:merge_request) do + create(:merge_request, + source_project: fork_project, + target_project: project) + end + + it { expect(merge_request.fork_missing?).to be_falsey } + end + + context "when source project is the target project" do + let(:merge_request) { create(:merge_request, source_project: project) } + + it { expect(merge_request.fork_missing?).to be_falsey } + end + + context "when fork does not exist" do + let(:merge_request) do + create(:merge_request, + source_project: fork_project, + target_project: project) + end + + it do + unlink_project.execute + merge_request.reload + + expect(merge_request.fork_missing?).to be_truthy + end + end + end + + describe "#closed_without_fork?" do + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:user) { create(:user) } + let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + + context "closed MR" do + let(:closed_merge_request) do + create(:closed_merge_request, + source_project: fork_project, + target_project: project) + end + + it "has a fork" do + expect(closed_merge_request.closed_without_fork?).to be_falsey + end + + it "does not have a fork" do + unlink_project.execute + closed_merge_request.reload + + expect(closed_merge_request.closed_without_fork?).to be_truthy + end + end + end end diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb new file mode 100644 index 00000000000..d7a1a2447ea --- /dev/null +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'projects/merge_requests/edit.html.haml' do + include Devise::TestHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:closed_merge_request) do + create(:closed_merge_request, + source_project: fork_project, + target_project: project, + author: user) + end + let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + + before do + assign(:project, project) + assign(:merge_request, closed_merge_request) + + allow(view).to receive(:can?).and_return(true) + allow(view).to receive(:current_user).and_return(User.find(closed_merge_request.author_id)) + end + + context 'when closed MR without fork' do + it "shows editable fields" do + unlink_project.execute + closed_merge_request.reload + render + + expect(rendered).to have_field('merge_request[title]') + expect(rendered).to have_css('label', text: "Title") + expect(rendered).to have_field('merge_request[description]') + expect(rendered).to have_css('label', text: "Description") + expect(rendered).to have_css('label', text: "Assignee") + expect(rendered).to have_css('label', text: "Milestone") + expect(rendered).to have_css('label', text: "Labels") + expect(rendered).not_to have_css('label', text: "Target branch") + end + end +end diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb new file mode 100644 index 00000000000..ed12b730eeb --- /dev/null +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'projects/merge_requests/show.html.haml' do + include Devise::TestHelpers + + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:merge_request) do + create(:merge_request, + source_project: fork_project, + source_branch: 'add-submodule-version-bump', + target_branch: 'master', target_project: project) + end + + before do + assign(:project, project) + assign(:merge_request, merge_request) + assign(:commits_count, 0) + + merge_request.close! + allow(view).to receive(:can?).and_return(true) + end + + context 'closed MR' do + it 'shows Reopen button' do + render + + expect(rendered).to have_css('a', visible: true, text: 'Reopen') + expect(rendered).to have_css('a', visible: false, text: 'Close') + end + + it 'does not show Reopen button without fork' do + fork_project.destroy + render + + expect(rendered).to have_css('a', visible: false, text: 'Reopen') + expect(rendered).to have_css('a', visible: false, text: 'Close') + end + end +end -- cgit v1.2.1 From 2e08f1156998e9cd40b5eba5762182b8cb006c57 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 9 Aug 2016 15:43:15 +0200 Subject: Improve code --- app/views/shared/issuable/_form.html.haml | 41 +++++++++++----------- spec/models/merge_request_spec.rb | 20 ++++++++--- .../projects/merge_requests/edit.html.haml_spec.rb | 30 +++++++++++----- .../projects/merge_requests/show.html.haml_spec.rb | 13 +++---- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 75753a6b0af..c6b60f37f57 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -134,30 +134,29 @@ title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } = icon('question-circle') -- if issuable.is_a?(MergeRequest) - - unless @merge_request.closed_without_fork? - %hr - - if @merge_request.new_record? - .form-group - = f.label :source_branch, class: 'control-label' - .col-sm-10 - .issuable-form-select-holder - = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) +- if issuable.is_a?(MergeRequest) && !@merge_request.closed_without_fork? + %hr + - if @merge_request.new_record? .form-group - = f.label :target_branch, class: 'control-label' + = f.label :source_branch, class: 'control-label' .col-sm-10 .issuable-form-select-holder - = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) - - if @merge_request.new_record? -   - = link_to 'Change branches', mr_change_branches_path(@merge_request) - - if @merge_request.can_remove_source_branch?(current_user) - .form-group - .col-sm-10.col-sm-offset-2 - .checkbox - = label_tag 'merge_request[force_remove_source_branch]' do - = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch? - Remove source branch when merge request is accepted. + = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) + .form-group + = f.label :target_branch, class: 'control-label' + .col-sm-10 + .issuable-form-select-holder + = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) + - if @merge_request.new_record? +   + = link_to 'Change branches', mr_change_branches_path(@merge_request) + - if @merge_request.can_remove_source_branch?(current_user) + .form-group + .col-sm-10.col-sm-offset-2 + .checkbox + = label_tag 'merge_request[force_remove_source_branch]' do + = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch? + Remove source branch when merge request is accepted. - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) .row-content-block{class: (is_footer ? "footer-block" : "middle-block")} diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5fea6adf329..17337833596 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -992,7 +992,7 @@ describe MergeRequest, models: true do target_project: project) end - it do + it "returns true" do unlink_project.execute merge_request.reload @@ -1007,23 +1007,35 @@ describe MergeRequest, models: true do let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } - context "closed MR" do + context "when merge request is closed" do let(:closed_merge_request) do create(:closed_merge_request, source_project: fork_project, target_project: project) end - it "has a fork" do + it "returns false if fork exist" do expect(closed_merge_request.closed_without_fork?).to be_falsey end - it "does not have a fork" do + it "returns true if fork doesn't exist" do unlink_project.execute closed_merge_request.reload expect(closed_merge_request.closed_without_fork?).to be_truthy end end + + context "when merge request is open" do + let(:open_merge_request) do + create(:merge_request, + source_project: fork_project, + target_project: project) + end + + it "returns false" do + expect(open_merge_request.closed_without_fork?).to be_falsey + end + end end end diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index d7a1a2447ea..6fd108c5bae 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -6,36 +6,48 @@ describe 'projects/merge_requests/edit.html.haml' do let(:user) { create(:user) } let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } + let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + let(:closed_merge_request) do create(:closed_merge_request, source_project: fork_project, target_project: project, author: user) end - let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } before do assign(:project, project) assign(:merge_request, closed_merge_request) allow(view).to receive(:can?).and_return(true) - allow(view).to receive(:current_user).and_return(User.find(closed_merge_request.author_id)) + allow(view).to receive(:current_user) + .and_return(User.find(closed_merge_request.author_id)) end - context 'when closed MR without fork' do + context 'when closed merge request without fork' do it "shows editable fields" do unlink_project.execute closed_merge_request.reload + + render + + expect(rendered).to have_field('merge_request[title]') + expect(rendered).to have_field('merge_request[description]') + expect(rendered).to have_selector('#merge_request_assignee_id', visible: false) + expect(rendered).to have_selector('#merge_request_milestone_id', visible: false) + expect(rendered).not_to have_selector('#merge_request_target_branch', visible: false) + end + end + + context 'when closed merge request with fork' do + it "shows editable fields" do render expect(rendered).to have_field('merge_request[title]') - expect(rendered).to have_css('label', text: "Title") expect(rendered).to have_field('merge_request[description]') - expect(rendered).to have_css('label', text: "Description") - expect(rendered).to have_css('label', text: "Assignee") - expect(rendered).to have_css('label', text: "Milestone") - expect(rendered).to have_css('label', text: "Labels") - expect(rendered).not_to have_css('label', text: "Target branch") + expect(rendered).to have_selector('#merge_request_assignee_id', visible: false) + expect(rendered).to have_selector('#merge_request_milestone_id', visible: false) + expect(rendered).to have_selector('#merge_request_target_branch', visible: false) end end end diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index ed12b730eeb..923c3553814 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -5,23 +5,24 @@ describe 'projects/merge_requests/show.html.haml' do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } - let(:merge_request) do - create(:merge_request, + + let(:closed_merge_request) do + create(:closed_merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', - target_branch: 'master', target_project: project) + target_branch: 'master', + target_project: project) end before do assign(:project, project) - assign(:merge_request, merge_request) + assign(:merge_request, closed_merge_request) assign(:commits_count, 0) - merge_request.close! allow(view).to receive(:can?).and_return(true) end - context 'closed MR' do + context 'when merge request is closed' do it 'shows Reopen button' do render -- cgit v1.2.1 From 6b02c82cfe68dc0f19cb3523eed1769a1e6d64b9 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 10 Aug 2016 15:36:30 +0200 Subject: Improve grammar --- app/views/projects/merge_requests/show/_mr_title.html.haml | 2 +- app/views/shared/issuable/_form.html.haml | 2 +- .../controllers/projects/merge_requests_controller_spec.rb | 4 ++-- spec/models/merge_request_spec.rb | 14 +++++++------- spec/views/projects/merge_requests/edit.html.haml_spec.rb | 4 ++-- spec/views/projects/merge_requests/show.html.haml_spec.rb | 7 ++++--- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 48016645019..e35291dff7d 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,6 +1,6 @@ - if @merge_request.closed_without_fork? .alert.alert-danger - %p Source project is not a fork of the target project + %p The source project of this merge request has been removed. .clearfix.detail-page-header .issuable-header diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c6b60f37f57..3856a4917b4 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -134,7 +134,7 @@ title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } = icon('question-circle') -- if issuable.is_a?(MergeRequest) && !@merge_request.closed_without_fork? +- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork? %hr - if @merge_request.new_record? .form-group diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index f95c3fc771b..a219400d75f 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -171,7 +171,7 @@ describe Projects::MergeRequestsController do expect(merge_request.reload.closed?).to be_truthy end - it 'allow to edit closed MR' do + it 'allows editing of a closed merge request' do merge_request.close! put :update, @@ -186,7 +186,7 @@ describe Projects::MergeRequestsController do expect(merge_request.reload.title).to eq 'New title' end - it 'does not allow to update target branch closed MR' do + it 'does not allow to update target branch closed merge request' do merge_request.close! put :update, diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 17337833596..4cbf87ba792 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -969,7 +969,7 @@ describe MergeRequest, models: true do let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } - context "when fork exists" do + context "when the fork exists" do let(:merge_request) do create(:merge_request, source_project: fork_project, @@ -979,13 +979,13 @@ describe MergeRequest, models: true do it { expect(merge_request.fork_missing?).to be_falsey } end - context "when source project is the target project" do + context "when the source project is the same as the target project" do let(:merge_request) { create(:merge_request, source_project: project) } it { expect(merge_request.fork_missing?).to be_falsey } end - context "when fork does not exist" do + context "when the fork does not exist" do let(:merge_request) do create(:merge_request, source_project: fork_project, @@ -1007,18 +1007,18 @@ describe MergeRequest, models: true do let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } - context "when merge request is closed" do + context "when the merge request is closed" do let(:closed_merge_request) do create(:closed_merge_request, source_project: fork_project, target_project: project) end - it "returns false if fork exist" do + it "returns false if the fork exist" do expect(closed_merge_request.closed_without_fork?).to be_falsey end - it "returns true if fork doesn't exist" do + it "returns true if the fork does not exist" do unlink_project.execute closed_merge_request.reload @@ -1026,7 +1026,7 @@ describe MergeRequest, models: true do end end - context "when merge request is open" do + context "when the merge request is open" do let(:open_merge_request) do create(:merge_request, source_project: fork_project, diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index 6fd108c5bae..31bbb150698 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -24,7 +24,7 @@ describe 'projects/merge_requests/edit.html.haml' do .and_return(User.find(closed_merge_request.author_id)) end - context 'when closed merge request without fork' do + context 'when a merge request without fork' do it "shows editable fields" do unlink_project.execute closed_merge_request.reload @@ -39,7 +39,7 @@ describe 'projects/merge_requests/edit.html.haml' do end end - context 'when closed merge request with fork' do + context 'when a merge request with an existing source project is closed' do it "shows editable fields" do render diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index 923c3553814..02fe04253db 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -22,16 +22,17 @@ describe 'projects/merge_requests/show.html.haml' do allow(view).to receive(:can?).and_return(true) end - context 'when merge request is closed' do - it 'shows Reopen button' do + context 'when the merge request is closed' do + it 'shows the "Reopen" button' do render expect(rendered).to have_css('a', visible: true, text: 'Reopen') expect(rendered).to have_css('a', visible: false, text: 'Close') end - it 'does not show Reopen button without fork' do + it 'does not show the "Reopen" button when the source project does not exist' do fork_project.destroy + render expect(rendered).to have_css('a', visible: false, text: 'Reopen') -- cgit v1.2.1 From 8ed6e2ec7ad992dda45042bcacea9e00a1bc6ab5 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 17 Aug 2016 13:12:13 +0200 Subject: Fix test --- spec/views/projects/merge_requests/show.html.haml_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index 02fe04253db..fe0780e72df 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -3,15 +3,16 @@ require 'spec_helper' describe 'projects/merge_requests/show.html.haml' do include Devise::TestHelpers + let(:user) { create(:user) } let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } + let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } let(:closed_merge_request) do create(:closed_merge_request, source_project: fork_project, - source_branch: 'add-submodule-version-bump', - target_branch: 'master', - target_project: project) + target_project: project, + author: user) end before do @@ -31,7 +32,8 @@ describe 'projects/merge_requests/show.html.haml' do end it 'does not show the "Reopen" button when the source project does not exist' do - fork_project.destroy + unlink_project.execute + closed_merge_request.reload render -- cgit v1.2.1 From 4f8a823e64f9ba3e2c8ef4da8dddaab7b6f7fc3d Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 18 Aug 2016 07:14:44 +0000 Subject: Update CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index cc98863dac8..1979158d439 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -217,13 +217,13 @@ v 8.11.0 v 8.10.7 - Upgrade Hamlit to 2.6.1. !5873 - Upgrade Doorkeeper to 4.2.0. !5881 + - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 v 8.10.6 - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - Restore "Largest repository" sort option on Admin > Projects page. !5797 - Fix privilege escalation via project export. - Require administrator privileges to perform a project import. - - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 v 8.10.5 - Add a data migration to fix some missing timestamps in the members table. !5670 -- cgit v1.2.1 From 7226631102ef00c2d880bc6c1e099e52f4fa8659 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 25 Aug 2016 15:08:31 +0200 Subject: Improve grammar and fix CHANGELOG --- CHANGELOG | 8 +++++++- app/models/merge_request.rb | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1979158d439..c5d035661b1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -53,6 +53,13 @@ v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 - Use gitlab-workhorse 0.7.11 !5983 - Does not halt the GitHub import process when an error occurs. !5763 + - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 + +v 8.11.2 (unreleased) + - Show "Create Merge Request" widget for push events to fork projects on the source project + +v 8.11.1 (unreleased) + - Does not halt the GitHub import process when an error occurs - Fix file links on project page when default view is Files !5933 - Fixed enter key in search input not working !5888 @@ -217,7 +224,6 @@ v 8.11.0 v 8.10.7 - Upgrade Hamlit to 2.6.1. !5873 - Upgrade Doorkeeper to 4.2.0. !5881 - - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 v 8.10.6 - Upgrade Rails to 4.2.7.1 for security fixes. !5781 diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b41a1f0c547..27ca5d119d5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -309,7 +309,7 @@ class MergeRequest < ActiveRecord::Base return true unless fork_missing? errors.add :validate_fork, - 'Source project is not a fork of target project' + 'Source project is not a fork of the target project' end def closed_without_fork? -- cgit v1.2.1 From c1b44cfea4fcba1acbb798d934daddcd8de50354 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 8 Jul 2016 14:01:21 +0100 Subject: Hide group control nav if no options present Closes #19120 --- app/views/layouts/nav/_group.html.haml | 2 +- app/views/layouts/nav/_group_settings.html.haml | 38 ++++++++++++++----------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index d7d36c84b6c..27ac1760166 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,5 +1,5 @@ += render 'layouts/nav/group_settings' .scrolling-tabs-container{ class: nav_control_class } - = render 'layouts/nav/group_settings' .fade-left = icon('angle-left') .fade-right diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index bf9a7ecb786..1076ac9270a 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,22 +1,26 @@ - if current_user + - can_admin_projects = can?(current_user, :admin_group, @group) - can_edit = can?(current_user, :admin_group, @group) - member = @group.members.find_by(user_id: current_user.id) - can_leave = member && can?(current_user, :destroy_group_member, member) - .controls - .dropdown.group-settings-dropdown - %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} - = icon('cog') - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - = nav_link(path: 'groups#projects') do - = link_to 'Projects', projects_group_path(@group), title: 'Projects' - %li.divider - - if can_edit - %li - = link_to 'Edit Group', edit_group_path(@group) - - if can_leave - %li - = link_to polymorphic_path([:leave, @group, :members]), - data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do - Leave Group + - if can_admin_projects || can_edit || can_leave + .controls + .dropdown.group-settings-dropdown + %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + - if can_admin_projects + = nav_link(path: 'groups#projects') do + = link_to 'Projects', projects_group_path(@group), title: 'Projects' + - if can_edit || can_leave + %li.divider + - if can_edit + %li + = link_to 'Edit Group', edit_group_path(@group) + - if can_leave + %li + = link_to polymorphic_path([:leave, @group, :members]), + data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do + Leave Group -- cgit v1.2.1 From 475afd37b656fa87e8f528670091a1923e391cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Doursenaud?= Date: Thu, 30 Jul 2015 12:38:21 +0000 Subject: Updated Bitbucket OmniAuth documentation --- doc/integration/bitbucket.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 2eb6266ebe7..0078f4e15b2 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -7,7 +7,7 @@ Bitbucket will generate an application ID and secret key for you to use. 1. Sign in to Bitbucket. -1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you. +1. Navigate to your individual user settings (Manage account) or a team's settings (Manage team), depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you. 1. Select "OAuth" in the left menu. @@ -16,9 +16,17 @@ Bitbucket will generate an application ID and secret key for you to use. 1. Provide the required details. - Name: This can be anything. Consider something like `'s GitLab` or `'s GitLab` or something else descriptive. - Application description: Fill this in if you wish. + - Callback URL: leave blank. - URL: The URL to your GitLab installation. 'https://gitlab.company.com' + +1. Grant at least the following permissions. + - Account: Email + - Repositories: Read + 1. Select "Save". +1. Select your newly created OAuth consumer. + 1. You should now see a Key and Secret in the list of OAuth customers. Keep this page open as you continue configuration. @@ -62,7 +70,7 @@ Bitbucket will generate an application ID and secret key for you to use. app_secret: 'YOUR_APP_SECRET' } ``` -1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7. +1. Change 'YOUR_KEY' to the key from the Bitbucket application page from step 7. 1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7. @@ -137,4 +145,4 @@ To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org 1. Restart GitLab to allow it to find the new public key. -You should now see the "Import projects from Bitbucket" option on the New Project page enabled. +You should now see the "Import projects from Bitbucket" option on the New Project page enabled. \ No newline at end of file -- cgit v1.2.1 From c6d27652923ca287ab1ef29de3e2f4ab9117b121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Doursenaud?= Date: Thu, 30 Jul 2015 12:55:25 +0000 Subject: Updated Bitbucket OmniAuth documentation for omnibus package --- doc/integration/bitbucket.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 0078f4e15b2..94c845c29a4 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -92,7 +92,7 @@ Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and ### Step 1: Public key -To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations. +To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/var/opt/gitlab/.ssh/bitbucket_rsa` for omnibus package and to `/home/git/.ssh/bitbucket_rsa.pub` for installations from source. If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: @@ -102,12 +102,20 @@ If you have that file in place, you're all set and should see the "Import projec sudo -u git -H ssh-keygen ``` - When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`. + When asked `Enter file in which to save the key` specify the correct path, eg. `/var/opt/gitlab/.ssh/bitbucket_rsa` or `/home/git/.ssh/bitbucket_rsa`. Make sure to use an **empty passphrase**. 1. Configure SSH client to use your new key: Open the SSH configuration file of the git user. + + For omnibus package: + + ```sh + sudo editor /var/opt/gitlab/.ssh/config + ``` + + For installations from source: ```sh sudo editor /home/git/.ssh/config -- cgit v1.2.1 From 45421e1da8953d5faca96f277a142576559f3109 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Aug 2016 12:19:46 +0100 Subject: Updated variable name --- app/views/layouts/nav/_group_settings.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index 1076ac9270a..75275afc0f3 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,17 +1,17 @@ - if current_user - - can_admin_projects = can?(current_user, :admin_group, @group) + - can_admin_group = can?(current_user, :admin_group, @group) - can_edit = can?(current_user, :admin_group, @group) - member = @group.members.find_by(user_id: current_user.id) - can_leave = member && can?(current_user, :destroy_group_member, member) - - if can_admin_projects || can_edit || can_leave + - if can_admin_group || can_edit || can_leave .controls .dropdown.group-settings-dropdown %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - - if can_admin_projects + - if can_admin_group = nav_link(path: 'groups#projects') do = link_to 'Projects', projects_group_path(@group), title: 'Projects' - if can_edit || can_leave -- cgit v1.2.1 From c4e00dcc26a8506763848e48526ae16a76dad8db Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 30 Aug 2016 10:56:59 +0200 Subject: Add title to CI lint page --- app/views/ci/lints/show.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index 0044d779c31..889086c62b1 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -1,3 +1,6 @@ +- page_title "CI Lint" +- page_description "Validate your GitLab CI configuration file" + %h2 Check your .gitlab-ci.yml %hr -- cgit v1.2.1 From 2d8d94a788eb0bf3885ee67bda9638556425fa4b Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 30 Aug 2016 13:31:39 +0200 Subject: Change method name --- CHANGELOG | 8 +------- app/models/merge_request.rb | 6 +++--- spec/models/merge_request_spec.rb | 8 ++++---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c5d035661b1..5332aaa1ab2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML - Fix hover leading space bug in pipeline graph !5980 + - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable @@ -53,13 +54,6 @@ v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 - Use gitlab-workhorse 0.7.11 !5983 - Does not halt the GitHub import process when an error occurs. !5763 - - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - -v 8.11.2 (unreleased) - - Show "Create Merge Request" widget for push events to fork projects on the source project - -v 8.11.1 (unreleased) - - Does not halt the GitHub import process when an error occurs - Fix file links on project page when default view is Files !5933 - Fixed enter key in search input not working !5888 diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 27ca5d119d5..a8dd4a306cf 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -306,17 +306,17 @@ class MergeRequest < ActiveRecord::Base def validate_fork return true unless target_project && source_project return true if target_project == source_project - return true unless fork_missing? + return true unless forked_source_project_missing? errors.add :validate_fork, 'Source project is not a fork of the target project' end def closed_without_fork? - closed? && fork_missing? + closed? && forked_source_project_missing? end - def fork_missing? + def forked_source_project_missing? return false unless for_fork? return true unless source_project diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 4cbf87ba792..901b7bad007 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -963,7 +963,7 @@ describe MergeRequest, models: true do end end - describe "#fork_missing?" do + describe "#forked_source_project_missing?" do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } let(:user) { create(:user) } @@ -976,13 +976,13 @@ describe MergeRequest, models: true do target_project: project) end - it { expect(merge_request.fork_missing?).to be_falsey } + it { expect(merge_request.forked_source_project_missing?).to be_falsey } end context "when the source project is the same as the target project" do let(:merge_request) { create(:merge_request, source_project: project) } - it { expect(merge_request.fork_missing?).to be_falsey } + it { expect(merge_request.forked_source_project_missing?).to be_falsey } end context "when the fork does not exist" do @@ -996,7 +996,7 @@ describe MergeRequest, models: true do unlink_project.execute merge_request.reload - expect(merge_request.fork_missing?).to be_truthy + expect(merge_request.forked_source_project_missing?).to be_truthy end end end -- cgit v1.2.1 From 2e442840548c5de132723d611dbeefe2ef6105d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dirk=20H=C3=B6rner?= Date: Mon, 29 Aug 2016 14:33:19 +0000 Subject: lib/backup: fix broken permissions when creating repo dir This commit fixes a typo where the mode argument to FileUtils.mkdir() would be passed in decimal rather than octal format, yielding bad permissions. --- CHANGELOG | 3 +++ lib/backup/repository.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 83a5d1727f3..2a91c099fb1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,9 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML +v 8.11.4 (unreleased) + - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) + v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index f117fc3d37d..9fcd9a3f999 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -55,7 +55,7 @@ module Backup bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s) FileUtils.mv(path, bk_repos_path) # This is expected from gitlab:check - FileUtils.mkdir_p(path, mode: 2770) + FileUtils.mkdir_p(path, mode: 02770) end Project.find_each(batch_size: 1000) do |project| -- cgit v1.2.1 From 8c101f9f860e03be20a90cafdcd19f897e47f62d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 30 Aug 2016 13:43:09 +0200 Subject: Mention that the `:id` of a project can also be `NAMESPACE/PROJECT_NAME` [ci skip] --- doc/api/commits.md | 14 +++++++------- doc/api/projects.md | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/api/commits.md b/doc/api/commits.md index 5c98c5d7565..55d0de7afd9 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -10,7 +10,7 @@ GET /projects/:id/repository/commits | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project | +| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | | `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | | `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | @@ -58,7 +58,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project | +| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash @@ -102,7 +102,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project | +| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash @@ -138,7 +138,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project | +| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash @@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project | +| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit SHA or name of a repository branch or tag | | `note` | string | yes | The text of the comment | | `path` | string | no | The file path relative to the repository | @@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project +| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit SHA | `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch | `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test` @@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project +| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit SHA | `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled` | `ref` | string | no | The `ref` (branch or tag) to which the status refers diff --git a/doc/api/projects.md b/doc/api/projects.md index 0e4806e31c5..22c3d416107 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -489,7 +489,7 @@ PUT /projects/:id Parameters: -- `id` (required) - The ID of a project +- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project - `name` (optional) - project name - `path` (optional) - repository name for project - `description` (optional) - short project description @@ -519,7 +519,7 @@ POST /projects/fork/:id Parameters: -- `id` (required) - The ID of the project to be forked +- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked ### Star a project @@ -532,7 +532,7 @@ POST /projects/:id/star | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project | +| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" @@ -599,7 +599,7 @@ DELETE /projects/:id/star | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project | +| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" @@ -670,7 +670,7 @@ POST /projects/:id/archive | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project | +| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive" @@ -757,7 +757,7 @@ POST /projects/:id/unarchive | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project | +| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive" @@ -839,7 +839,7 @@ DELETE /projects/:id Parameters: -- `id` (required) - The ID of a project +- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked ## Uploads @@ -853,7 +853,7 @@ POST /projects/:id/uploads Parameters: -- `id` (required) - The ID of the project +- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked - `file` (required) - The file to be uploaded ```json @@ -882,7 +882,7 @@ POST /projects/:id/share Parameters: -- `id` (required) - The ID of a project +- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked - `group_id` (required) - The ID of a group - `group_access` (required) - Level of permissions for sharing @@ -1114,7 +1114,7 @@ POST /projects/:id/fork/:forked_from_id Parameters: -- `id` (required) - The ID of the project +- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked - `forked_from_id:` (required) - The ID of the project that was forked from ### Delete an existing forked from relationship @@ -1125,7 +1125,7 @@ DELETE /projects/:id/fork Parameter: -- `id` (required) - The ID of the project +- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked ## Search for projects by name -- cgit v1.2.1 From 74abc527ecaf6a2e94cab4c36221ee921cedda82 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 30 Aug 2016 13:49:17 +0200 Subject: Create bitmaps during 'git gc' --- doc/install/installation.md | 4 + doc/update/8.11-to-8.12.md | 198 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 doc/update/8.11-to-8.12.md diff --git a/doc/install/installation.md b/doc/install/installation.md index d4b89fa8345..f012c9cd51b 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -331,6 +331,10 @@ sudo usermod -aG redis git # Disable 'git gc --auto' because GitLab already runs 'git gc' when needed sudo -u git -H git config --global gc.auto 0 + # Enable packfile bitmaps + sudo -u git -H git config --global repack.writeBitmaps true + + # Configure Redis connection settings sudo -u git -H cp config/resque.yml.example config/resque.yml diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md new file mode 100644 index 00000000000..544a80662d3 --- /dev/null +++ b/doc/update/8.11-to-8.12.md @@ -0,0 +1,198 @@ +# From 8.11 to 8.12 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz +echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz +cd ruby-2.3.1 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-12-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-12-stable-ee +``` + +### 5. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v3.4.0 +``` + +### 6. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +cd /home/git/gitlab-workhorse +sudo -u git -H git fetch --all +sudo -u git -H git checkout v0.7.8 +sudo -u git -H make +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +``` + +### 8. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-11-stable:config/gitlab.yml.example origin/8-12-stable:config/gitlab.yml.example +``` + +#### Git configuration + +```sh +# Enable packfile bitmaps +sudo -u git -H git config --global repack.writeBitmaps true +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-11-stable:lib/support/nginx/gitlab-ssl origin/8-12-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-11-stable:lib/support/nginx/gitlab origin/8-12-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-12-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-12-stable/config/initializers/smtp_settings.rb.sample#L13? + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.11) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.9 to 8.11](8.9-to-8.11.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. -- cgit v1.2.1 From 90b6ea4cdffbb2bc8cea6a87dcde658c31344c3e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 30 Aug 2016 13:51:39 +0200 Subject: Add CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index a6cd8f4c7e1..393c1e2a960 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML - Fix hover leading space bug in pipeline graph !5980 + - Instructions for enabling Git packfile bitmaps !6104 v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable -- cgit v1.2.1 From 2fb28dddfc7848a90294c2008b5d672a305a8596 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 30 Aug 2016 15:42:40 +0200 Subject: Refactor Bitbucket integration documentation --- doc/integration/bitbucket.md | 190 ++++++++++++++++++++++++++----------------- doc/integration/omniauth.md | 4 +- 2 files changed, 119 insertions(+), 75 deletions(-) diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 94c845c29a4..16e54102113 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -1,127 +1,155 @@ -# Integrate your server with Bitbucket +# Integrate your GitLab server with Bitbucket -Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account. +Import projects from Bitbucket and login to your GitLab instance with your +Bitbucket account. -To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket. -Bitbucket will generate an application ID and secret key for you to use. +## Overview -1. Sign in to Bitbucket. +You can set up Bitbucket as an OAuth provider so that you can use your +credentials to authenticate into GitLab or import your projects from Bitbucket. -1. Navigate to your individual user settings (Manage account) or a team's settings (Manage team), depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you. +- To use Bitbucket as an OmniAuth provider, follow the [Bitbucket OmniAuth + provider](#bitbucket-omniauth-provider) section. +- To import projects from Bitbucket, follow both the + [Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) and + [Bitbucket project import](#bitbucket-project-import) sections. -1. Select "OAuth" in the left menu. +## Bitbucket OmniAuth provider + +> **Note:** +Make sure to first follow the [Initial OmniAuth configuration][init-oauth] +before proceeding with setting up the Bitbucket integration. + +To enable the Bitbucket OmniAuth provider you must register your application +with Bitbucket. Bitbucket will generate an application ID and secret key for +you to use. +1. Sign in to Bitbucket. +1. Navigate to your individual user settings (Manage account) or a team's + settings (Manage team), depending on how you want the application registered. + It does not matter if the application is registered as an individual or a + team - that is entirely up to you. +1. Select "OAuth" in the left menu. 1. Select "Add consumer". +1. Provide the required details: -1. Provide the required details. - - Name: This can be anything. Consider something like `'s GitLab` or `'s GitLab` or something else descriptive. - - Application description: Fill this in if you wish. - - Callback URL: leave blank. - - URL: The URL to your GitLab installation. 'https://gitlab.company.com' + | Item | Description | + | :--- | :---------- | + | **Name** | This can be anything. Consider something like `'s GitLab` or `'s GitLab` or something else descriptive. | + | **Application description** | Fill this in if you wish. | + | **Callback URL** | Leave blank. | + | **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. | -1. Grant at least the following permissions. - - Account: Email - - Repositories: Read - -1. Select "Save". +1. Grant at least the following permissions: -1. Select your newly created OAuth consumer. + ``` + Account: Email + Repositories: Read + ``` +1. Select "Save". +1. Select your newly created OAuth consumer. 1. You should now see a Key and Secret in the list of OAuth customers. Keep this page open as you continue configuration. - 1. On your GitLab server, open the configuration file. For omnibus package: ```sh - sudo editor /etc/gitlab/gitlab.rb + sudo editor /etc/gitlab/gitlab.rb ``` For installations from source: ```sh - cd /home/git/gitlab + cd /home/git/gitlab - sudo -u git -H editor config/gitlab.yml + sudo -u git -H editor config/gitlab.yml ``` 1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. - 1. Add the provider configuration: For omnibus package: ```ruby - gitlab_rails['omniauth_providers'] = [ - { - "name" => "bitbucket", - "app_id" => "YOUR_KEY", - "app_secret" => "YOUR_APP_SECRET", - "url" => "https://bitbucket.org/" - } - ] + gitlab_rails['omniauth_providers'] = [ + { + "name" => "bitbucket", + "app_id" => "YOUR_KEY", + "app_secret" => "YOUR_APP_SECRET", + "url" => "https://bitbucket.org/" + } + ] ``` For installation from source: - ``` - - { name: 'bitbucket', app_id: 'YOUR_KEY', + ```yaml + - { name: 'bitbucket', + app_id: 'YOUR_KEY', app_secret: 'YOUR_APP_SECRET' } ``` 1. Change 'YOUR_KEY' to the key from the Bitbucket application page from step 7. - 1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7. - 1. Save the configuration file. - -1. If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```). - 1. Restart GitLab for the changes to take effect. -On the sign in page there should now be a Bitbucket icon below the regular sign in form. -Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application. -If everything goes well the user will be returned to GitLab and will be signed in. +On the sign in page there should now be a Bitbucket icon below the regular sign +in form. Click the icon to begin the authentication process. Bitbucket will ask +the user to sign in and authorize the GitLab application. If everything goes +well the user will be returned to GitLab and will be signed in. ## Bitbucket project import -To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com. +To allow projects to be imported directly into GitLab, Bitbucket requires two +extra setup steps compared to [GitHub](github.md) and [GitLab.com](gitlab.md). -Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key. +Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and +instead requires GitLab to use SSH and identify itself using your GitLab +server's SSH key. -### Step 1: Public key +To be able to access repositories on Bitbucket, GitLab will automatically +register your public key with Bitbucket as a deploy key for the repositories to +be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa` which +translates to `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages and to +`/home/git/.ssh/bitbucket_rsa.pub` for installations from source. -To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/var/opt/gitlab/.ssh/bitbucket_rsa` for omnibus package and to `/home/git/.ssh/bitbucket_rsa.pub` for installations from source. +--- -If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: +Below are the steps that will allow GitLab to be able to import your projects +from Bitbucket. -1. Create a new SSH key: +1. Make sure you [have enabled the Bitbucket OAuth support](#bitbucket-omniauth-provider). +1. Create a new SSH key with an **empty passphrase**: ```sh sudo -u git -H ssh-keygen ``` - When asked `Enter file in which to save the key` specify the correct path, eg. `/var/opt/gitlab/.ssh/bitbucket_rsa` or `/home/git/.ssh/bitbucket_rsa`. - Make sure to use an **empty passphrase**. + When asked to 'Enter file in which to save the key' enter: + `/var/opt/gitlab/.ssh/bitbucket_rsa` for Omnibus packages or + `/home/git/.ssh/bitbucket_rsa` for installations from source. The name is + important so make sure to get it right. -1. Configure SSH client to use your new key: + > **Warning:** + This key must NOT be associated with ANY existing Bitbucket accounts. If it + is, the import will fail with an `Access denied! Please verify you can add + deploy keys to this repository.` error. + +1. Next, you need to to configure the SSH client to use your new key. Open the + SSH configuration file of the `git` user: - Open the SSH configuration file of the git user. - - For omnibus package: - - ```sh - sudo editor /var/opt/gitlab/.ssh/config ``` - - For installations from source: + # For Omnibus packages + sudo editor /var/opt/gitlab/.ssh/config - ```sh - sudo editor /home/git/.ssh/config + # For installations from source + sudo editor /home/git/.ssh/config ``` - Add a host configuration for `bitbucket.org`. +1. Add a host configuration for `bitbucket.org`: ```sh Host bitbucket.org @@ -129,28 +157,44 @@ If you have that file in place, you're all set and should see the "Import projec User git ``` -### Step 2: Known hosts - -To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so: - -1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use: +1. Save the file and exit. +1. Manually connect to `bitbucket.org` over SSH, while logged in as the `git` + user that GitLab will use: ```sh sudo -u git -H ssh bitbucket.org ``` -1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter): + That step is performed because GitLab needs to connect to Bitbucket over SSH, + in order to add `bitbucket.org` to your GitLab server's known SSH hosts. + +1. Verify the RSA key fingerprint you'll see in the response matches the one + in the [Bitbucket documentation][bitbucket-docs] (the specific IP address + doesn't matter): ```sh - The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. - RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. + The authenticity of host 'bitbucket.org (104.192.143.1)' can't be established. + RSA key fingerprint is SHA256:zzXQOXSRBEiUtuE8AikJYKwbHaxvSc0ojez9YXaGp1A. Are you sure you want to continue connecting (yes/no)? ``` -1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts. +1. If the fingerprint matches, type `yes` to continue connecting and have + `bitbucket.org` be added to your known SSH hosts. After confirming you should + see a permission denied message. If you see an authentication successful + message you have done something wrong. The key you are using has already been + added to a Bitbucket account and will cause the import script to fail. Ensure + the key you are using CANNOT authenticate with Bitbucket. +1. Restart GitLab to allow it to find the new public key. -1. Your GitLab server is now able to connect to Bitbucket over SSH. +Your GitLab server is now able to connect to Bitbucket over SSH. You should be +able to see the "Import projects from Bitbucket" option on the New Project page +enabled. -1. Restart GitLab to allow it to find the new public key. +## Acknowledgemts + +Special thanks to the writer behind the following article: + +- http://stratus3d.com/blog/2015/09/06/migrating-from-bitbucket-to-local-gitlab-server/ -You should now see the "Import projects from Bitbucket" option on the New Project page enabled. \ No newline at end of file +[init-oauth]: omniauth.md#initial-omniauth-configuration +[bitbucket-docs]: https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 46b260e7033..8a55fce96fe 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -102,8 +102,8 @@ To change these settings: block_auto_created_users: true ``` -Now we can choose one or more of the Supported Providers listed above to continue -the configuration process. +Now we can choose one or more of the [Supported Providers](#supported-providers) +listed above to continue the configuration process. ## Enable OmniAuth for an Existing User -- cgit v1.2.1 From 59dd9e576bd62f9311316ca31ecaba8ddde50b00 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 30 Aug 2016 16:23:45 +0100 Subject: Use Repository#fetch_ref --- app/services/merge_requests/resolve_service.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb index bd8b9f8cfd4..19caa038c44 100644 --- a/app/services/merge_requests/resolve_service.rb +++ b/app/services/merge_requests/resolve_service.rb @@ -38,8 +38,13 @@ module MergeRequests def fetch_their_commit! return if rugged.include?(conflicts.their_commit.oid) - remote = rugged.remotes.create_anonymous(merge_request.target_project.repository.path_to_repo) - remote.fetch(merge_request.target_branch) + random_string = SecureRandom.hex + + project.repository.fetch_ref( + merge_request.target_project.repository.path_to_repo, + "refs/heads/#{merge_request.target_branch}", + "refs/tmp/#{random_string}/head" + ) end end end -- cgit v1.2.1 From 9b57ad382e69044eb851f64cc0eb35896baa712a Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 30 Aug 2016 16:30:42 +0100 Subject: Move #to_discussion to NoteOnDiff --- app/models/concerns/note_on_diff.rb | 4 ++++ app/models/diff_note.rb | 4 ---- app/models/legacy_diff_note.rb | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb index a881fb83b7f..b8dd27a7afe 100644 --- a/app/models/concerns/note_on_diff.rb +++ b/app/models/concerns/note_on_diff.rb @@ -28,4 +28,8 @@ module NoteOnDiff def can_be_award_emoji? false end + + def to_discussion + Discussion.new([self]) + end end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index c8320ff87fa..4442cefc7e9 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -107,10 +107,6 @@ class DiffNote < Note self.noteable.find_diff_discussion(self.discussion_id) end - def to_discussion - Discussion.new([self]) - end - private def supported? diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 0e1649aafe5..40277a9b139 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -53,10 +53,6 @@ class LegacyDiffNote < Note self.line_code end - def to_discussion - Discussion.new([self]) - end - # Check if this note is part of an "active" discussion # # This will always return true for anything except MergeRequest noteables, -- cgit v1.2.1 From 8fe7817e4d1ec0d97a3d924e2263c9de939efa92 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 30 Aug 2016 17:52:14 +0200 Subject: More Bitbucket integration refactoring --- doc/integration/bitbucket.md | 89 ++++++++++++--------- doc/integration/img/bitbucket_oauth_keys.png | Bin 0 -> 12073 bytes .../img/bitbucket_oauth_settings_page.png | Bin 0 -> 82818 bytes 3 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 doc/integration/img/bitbucket_oauth_keys.png create mode 100644 doc/integration/img/bitbucket_oauth_settings_page.png diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 16e54102113..556d71b8b76 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -1,14 +1,15 @@ # Integrate your GitLab server with Bitbucket -Import projects from Bitbucket and login to your GitLab instance with your -Bitbucket account. +Import projects from Bitbucket.org and login to your GitLab instance with your +Bitbucket.org account. ## Overview -You can set up Bitbucket as an OAuth provider so that you can use your -credentials to authenticate into GitLab or import your projects from Bitbucket. +You can set up Bitbucket.org as an OAuth provider so that you can use your +credentials to authenticate into GitLab or import your projects from +Bitbucket.org. -- To use Bitbucket as an OmniAuth provider, follow the [Bitbucket OmniAuth +- To use Bitbucket.org as an OmniAuth provider, follow the [Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) section. - To import projects from Bitbucket, follow both the [Bitbucket OmniAuth provider](#bitbucket-omniauth-provider) and @@ -21,16 +22,16 @@ Make sure to first follow the [Initial OmniAuth configuration][init-oauth] before proceeding with setting up the Bitbucket integration. To enable the Bitbucket OmniAuth provider you must register your application -with Bitbucket. Bitbucket will generate an application ID and secret key for +with Bitbucket.org. Bitbucket will generate an application ID and secret key for you to use. -1. Sign in to Bitbucket. -1. Navigate to your individual user settings (Manage account) or a team's - settings (Manage team), depending on how you want the application registered. +1. Sign in to [Bitbucket.org](https://bitbucket.org). +1. Navigate to your individual user settings (**Bitbucket settings**) or a team's + settings (**Manage team**), depending on how you want the application registered. It does not matter if the application is registered as an individual or a - team - that is entirely up to you. -1. Select "OAuth" in the left menu. -1. Select "Add consumer". + team, that is entirely up to you. +1. Select **OAuth** in the left menu under "Access Management". +1. Select **Add consumer**. 1. Provide the required details: | Item | Description | @@ -40,66 +41,74 @@ you to use. | **Callback URL** | Leave blank. | | **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. | -1. Grant at least the following permissions: + And grant at least the following permissions: ``` Account: Email - Repositories: Read + Repositories: Read, Admin ``` -1. Select "Save". -1. Select your newly created OAuth consumer. -1. You should now see a Key and Secret in the list of OAuth customers. - Keep this page open as you continue configuration. -1. On your GitLab server, open the configuration file. + >**Note:** + It may seem a little odd to giving GitLab admin permissions to repositories, + but this is needed in order for GitLab to be able to clone the repositories. - For omnibus package: + ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png) - ```sh - sudo editor /etc/gitlab/gitlab.rb - ``` +1. Select **Save**. +1. Select your newly created OAuth consumer and you should now see a Key and + Secret in the list of OAuth customers. Keep this page open as you continue + the configuration. - For installations from source: + ![Bitbucket OAuth key](img/bitbucket_oauth_keys.png) - ```sh - cd /home/git/gitlab +1. On your GitLab server, open the configuration file: - sudo -u git -H editor config/gitlab.yml ``` + # For Omnibus packages + sudo editor /etc/gitlab/gitlab.rb -1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. -1. Add the provider configuration: + # For installations from source + sudo -u git -H editor /home/git/gitlab/config/gitlab.yml + ``` + +1. Follow the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) + for initial settings. +1. Add the Bitbucket provider configuration: - For omnibus package: + For Omnibus packages: ```ruby gitlab_rails['omniauth_providers'] = [ { "name" => "bitbucket", - "app_id" => "YOUR_KEY", - "app_secret" => "YOUR_APP_SECRET", + "app_id" => "BITBUCKET_APP_KEY", + "app_secret" => "BITBUCKET_APP_SECRET", "url" => "https://bitbucket.org/" } ] ``` - For installation from source: + For installations from source: ```yaml - { name: 'bitbucket', - app_id: 'YOUR_KEY', - app_secret: 'YOUR_APP_SECRET' } + app_id: 'BITBUCKET_APP_KEY', + app_secret: 'BITBUCKET_APP_SECRET' } ``` -1. Change 'YOUR_KEY' to the key from the Bitbucket application page from step 7. -1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7. + --- + + Where `BITBUCKET_APP_KEY` is the Key and `BITBUCKET_APP_SECRET` the Secret + from the Bitbucket application page. + 1. Save the configuration file. -1. Restart GitLab for the changes to take effect. +1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you + installed GitLab via Omnibus or from source respectively. On the sign in page there should now be a Bitbucket icon below the regular sign in form. Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application. If everything goes -well the user will be returned to GitLab and will be signed in. +well, the user will be returned to GitLab and will be signed in. ## Bitbucket project import @@ -198,3 +207,5 @@ Special thanks to the writer behind the following article: [init-oauth]: omniauth.md#initial-omniauth-configuration [bitbucket-docs]: https://confluence.atlassian.com/bitbucket/use-the-ssh-protocol-with-bitbucket-cloud-221449711.html#UsetheSSHprotocolwithBitbucketCloud-KnownhostorBitbucket%27spublickeyfingerprints +[reconfigure]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure +[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source diff --git a/doc/integration/img/bitbucket_oauth_keys.png b/doc/integration/img/bitbucket_oauth_keys.png new file mode 100644 index 00000000000..3fb2f7524a3 Binary files /dev/null and b/doc/integration/img/bitbucket_oauth_keys.png differ diff --git a/doc/integration/img/bitbucket_oauth_settings_page.png b/doc/integration/img/bitbucket_oauth_settings_page.png new file mode 100644 index 00000000000..a3047712d8c Binary files /dev/null and b/doc/integration/img/bitbucket_oauth_settings_page.png differ -- cgit v1.2.1 From 47ca2d3202539f759552a277d4342591afd869a9 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 30 Aug 2016 11:55:57 -0500 Subject: Add new icon for created pipeline --- app/assets/stylesheets/pages/status.scss | 9 +++++++++ app/helpers/ci_status_helper.rb | 2 +- app/views/shared/icons/_icon_status_created.svg | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 app/views/shared/icons/_icon_status_created.svg diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 587f2d9f3c1..0ee7ceecae5 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -43,6 +43,15 @@ border-color: $blue-normal; } + &.ci-created { + color: $table-text-gray; + border-color: $table-text-gray; + + svg { + fill: $table-text-gray; + } + } + svg { height: 13px; width: 13px; diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 0327b476d18..00bdb488c9b 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -41,7 +41,7 @@ module CiStatusHelper when 'play' 'icon_play' when 'created' - 'icon_status_pending' + 'icon_status_created' else 'icon_status_cancel' end diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg new file mode 100644 index 00000000000..4a08fd65860 --- /dev/null +++ b/app/views/shared/icons/_icon_status_created.svg @@ -0,0 +1 @@ + -- cgit v1.2.1 From 7532c012c26fc116f7c39f7c88ac3b08d818955c Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 8 Aug 2016 17:25:39 +0100 Subject: user is now notified when creating an issue through the api --- CHANGELOG | 1 + lib/api/issues.rb | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 03d6be67d6b..c440aa1987a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -70,6 +70,7 @@ v 8.11.0 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Add Koding (online IDE) integration - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) + - Creating an issue through our API now emails label subscribers !5720 - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix adding line comments on the initial commit to a repo !5900 diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 077258faee1..1121285f0af 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -154,6 +154,20 @@ module API render_api_error!({ labels: errors }, 400) end + if params[:labels].present? + params[:labels] = params[:labels].split(",").each { |word| word.strip! } + attrs[:label_ids] = [] + + params[:labels].each do |label| + existing_label = user_project.labels.where(title: label).first + + unless existing_label.nil? + attrs[:label_ids] << existing_label.id + params[:labels].delete(label) + end + end + end + project = user_project issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute @@ -163,10 +177,10 @@ module API end if issue.valid? - # Find or create labels and attach to issue. Labels are valid because + # create new labels and attach to issue. Labels are valid because # we already checked its name, so there can't be an error here if params[:labels].present? - issue.add_labels_by_names(params[:labels].split(',')) + issue.add_labels_by_names(params[:labels]) end present issue, with: Entities::Issue, current_user: current_user -- cgit v1.2.1 From b7d29ce659412e9a2acc411c841420eb13d115ba Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Tue, 9 Aug 2016 23:08:59 +0100 Subject: adds test to check whether or not an email is sent to label subscribers after creating a new issue through the api --- lib/api/issues.rb | 25 ++++++------------------- spec/requests/api/issues_spec.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 1121285f0af..9a042e6e70d 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -154,35 +154,22 @@ module API render_api_error!({ labels: errors }, 400) end + # Find or create labels if params[:labels].present? - params[:labels] = params[:labels].split(",").each { |word| word.strip! } - attrs[:label_ids] = [] - - params[:labels].each do |label| - existing_label = user_project.labels.where(title: label).first - - unless existing_label.nil? - attrs[:label_ids] << existing_label.id - params[:labels].delete(label) - end + attrs[:label_ids] = params[:labels].split(",").map do |label_name| + user_project.labels.create_with(color: Label::DEFAULT_COLOR) + .find_or_create_by(title: label_name.strip) + .id end end - project = user_project - - issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute + issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute if issue.spam? render_api_error!({ error: 'Spam detected' }, 400) end if issue.valid? - # create new labels and attach to issue. Labels are valid because - # we already checked its name, so there can't be an error here - if params[:labels].present? - issue.add_labels_by_names(params[:labels]) - end - present issue, with: Entities::Issue, current_user: current_user else render_validation_error!(issue) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index b8038fc85a1..a4c91252472 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers + let(:user) { create(:user) } let(:user2) { create(:user) } let(:non_member) { create(:user) } @@ -478,6 +479,18 @@ describe API::API, api: true do expect(json_response['labels']).to eq(['label', 'label2']) end + it "emails label subscribers" do + clear_enqueued_jobs + label = project.labels.first + label.toggle_subscription(user2) + + expect do + post api("/projects/#{project.id}/issues", user), + title: 'new issue', labels: label.title + end.to change{enqueued_jobs.size}.by(1) + expect(response.status).to eq(201) + end + it "returns a 400 bad request if title not given" do post api("/projects/#{project.id}/issues", user), labels: 'label, label2' expect(response).to have_http_status(400) -- cgit v1.2.1 From 7f0bcf04323ad69b64a90112896971ea8d1a5f99 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 15 Aug 2016 17:50:41 +0100 Subject: refactors update issue api request and some minor comments --- app/services/issuable_base_service.rb | 7 ++++++- lib/api/helpers.rb | 8 ++++++++ lib/api/issues.rb | 26 ++++++++++---------------- spec/requests/api/issues_spec.rb | 22 +++++++++++++++++----- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index e06c37c323e..3b37365612e 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -162,7 +162,12 @@ class IssuableBaseService < BaseService if params.present? && update_issuable(issuable, params) issuable.reset_events_cache - handle_common_system_notes(issuable, old_labels: old_labels) + + # We do not touch as it will affect a update on updated_at field + ActiveRecord::Base.no_touching do + handle_common_system_notes(issuable, old_labels: old_labels) + end + handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index da4b1bf9902..dbad86d8926 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -102,6 +102,14 @@ module API label || not_found!('Label') end + def get_label_ids(labels) + labels.split(",").map do |label_name| + user_project.labels.create_with(color: Label::DEFAULT_COLOR) + .find_or_create_by(title: label_name.strip) + .id + end + end + def find_project_issue(id) issue = user_project.issues.find(id) not_found! unless can?(current_user, :read_issue, issue) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 9a042e6e70d..39a46f69f16 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -154,14 +154,9 @@ module API render_api_error!({ labels: errors }, 400) end - # Find or create labels - if params[:labels].present? - attrs[:label_ids] = params[:labels].split(",").map do |label_name| - user_project.labels.create_with(color: Label::DEFAULT_COLOR) - .find_or_create_by(title: label_name.strip) - .id - end - end + # Find or create labels to attach to the issue. Labels are vaild + # because we already checked its name, so there can't be an error here + attrs[:label_ids] = get_label_ids(params[:labels]) if params[:labels].present? issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute @@ -203,17 +198,16 @@ module API render_api_error!({ labels: errors }, 400) end + # Find or create labels and attach to issue. Labels are valid because + # we already checked its name, so there can't be an error here + if params[:labels] && can?(current_user, :admin_issue, user_project) + issue.remove_labels + attrs[:label_ids] = get_label_ids(params[:labels]) + end + issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) if issue.valid? - # Find or create labels and attach to issue. Labels are valid because - # we already checked its name, so there can't be an error here - if params[:labels] && can?(current_user, :admin_issue, user_project) - issue.remove_labels - # Create and add labels to the new created issue - issue.add_labels_by_names(params[:labels].split(',')) - end - present issue, with: Entities::Issue, current_user: current_user else render_validation_error!(issue) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index a4c91252472..009fb3b2d70 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -479,16 +479,16 @@ describe API::API, api: true do expect(json_response['labels']).to eq(['label', 'label2']) end - it "emails label subscribers" do - clear_enqueued_jobs + it "sends notifications for subscribers of newly added labels" do label = project.labels.first label.toggle_subscription(user2) - expect do + perform_enqueued_jobs do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: label.title - end.to change{enqueued_jobs.size}.by(1) - expect(response.status).to eq(201) + end + + should_email(user2) end it "returns a 400 bad request if title not given" do @@ -646,6 +646,18 @@ describe API::API, api: true do expect(json_response['labels']).to eq([label.title]) end + it "sends notifications for subscribers of newly added labels when issue is updated" do + label = project.labels.first + label.toggle_subscription(user2) + + perform_enqueued_jobs do + put api("/projects/#{project.id}/issues/#{issue.id}", user), + title: 'updated title', labels: label.title + end + + should_email(user2) + end + it 'removes all labels' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' -- cgit v1.2.1 From 76c2901eac89b1b3a9975ec0f91fb929fbed2e70 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Thu, 18 Aug 2016 11:24:44 +0100 Subject: if issue is not valid we revert back to the old labels when updating --- CHANGELOG | 2 +- app/services/issuable_base_service.rb | 13 +++++++++++++ lib/api/helpers.rb | 8 -------- lib/api/issues.rb | 11 ++--------- spec/requests/api/issues_spec.rb | 2 +- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c440aa1987a..313969a87e2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.12.0 (unreleased) v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) + - Creating an issue through our API now emails label subscribers !5720 v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable @@ -70,7 +71,6 @@ v 8.11.0 - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - Add Koding (online IDE) integration - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) - - Creating an issue through our API now emails label subscribers !5720 - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix adding line comments on the initial commit to a repo !5900 diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 3b37365612e..4c8d93999a7 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -45,6 +45,7 @@ class IssuableBaseService < BaseService unless can?(current_user, ability, project) params.delete(:milestone_id) + params.delete(:labels) params.delete(:add_label_ids) params.delete(:remove_label_ids) params.delete(:label_ids) @@ -72,6 +73,7 @@ class IssuableBaseService < BaseService filter_labels_in_param(:add_label_ids) filter_labels_in_param(:remove_label_ids) filter_labels_in_param(:label_ids) + find_or_create_label_ids end def filter_labels_in_param(key) @@ -80,6 +82,17 @@ class IssuableBaseService < BaseService params[key] = project.labels.where(id: params[key]).pluck(:id) end + def find_or_create_label_ids + labels = params.delete(:labels) + return unless labels + + params[:label_ids] = labels.split(",").map do |label_name| + project.labels.create_with(color: Label::DEFAULT_COLOR) + .find_or_create_by(title: label_name.strip) + .id + end + end + def process_label_ids(attributes, existing_label_ids: nil) label_ids = attributes.delete(:label_ids) add_label_ids = attributes.delete(:add_label_ids) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index dbad86d8926..da4b1bf9902 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -102,14 +102,6 @@ module API label || not_found!('Label') end - def get_label_ids(labels) - labels.split(",").map do |label_name| - user_project.labels.create_with(color: Label::DEFAULT_COLOR) - .find_or_create_by(title: label_name.strip) - .id - end - end - def find_project_issue(id) issue = user_project.issues.find(id) not_found! unless can?(current_user, :read_issue, issue) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 39a46f69f16..d0bc7243e54 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -154,9 +154,7 @@ module API render_api_error!({ labels: errors }, 400) end - # Find or create labels to attach to the issue. Labels are vaild - # because we already checked its name, so there can't be an error here - attrs[:label_ids] = get_label_ids(params[:labels]) if params[:labels].present? + attrs[:labels] = params[:labels] if params[:labels] issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute @@ -198,12 +196,7 @@ module API render_api_error!({ labels: errors }, 400) end - # Find or create labels and attach to issue. Labels are valid because - # we already checked its name, so there can't be an error here - if params[:labels] && can?(current_user, :admin_issue, user_project) - issue.remove_labels - attrs[:label_ids] = get_label_ids(params[:labels]) - end + attrs[:labels] = params[:labels] if params[:labels] issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 009fb3b2d70..3362a88d798 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -647,7 +647,7 @@ describe API::API, api: true do end it "sends notifications for subscribers of newly added labels when issue is updated" do - label = project.labels.first + label = create(:label, title: 'foo', color: '#FFAABB', project: project) label.toggle_subscription(user2) perform_enqueued_jobs do -- cgit v1.2.1 From 7d119bab4816355ce0156f24e0117e6e1d81588f Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 9 Aug 2016 13:56:54 -0700 Subject: re-enable the cyclomatic complexity checker --- app/models/ability.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index c1df4a865f6..fcd7740d79f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,6 +1,5 @@ class Ability class << self - # rubocop: disable Metrics/CyclomaticComplexity def allowed(user, subject) return anonymous_abilities(user, subject) if user.nil? return [] unless user.is_a?(User) -- cgit v1.2.1 From 0f4df86a5e559b9c15f07b43edad829928f59e87 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 23 Aug 2016 17:45:49 -0700 Subject: delete project_security_spec re a conversation with @rspeicher, this spec isn't really testing anything. --- spec/models/project_security_spec.rb | 112 ----------------------------------- 1 file changed, 112 deletions(-) delete mode 100644 spec/models/project_security_spec.rb diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb deleted file mode 100644 index 36379074ea0..00000000000 --- a/spec/models/project_security_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'spec_helper' - -describe Project, models: true do - describe 'authorization' do - before do - @p1 = create(:project) - - @u1 = create(:user) - @u2 = create(:user) - @u3 = create(:user) - @u4 = @p1.owner - - @abilities = Six.new - @abilities << Ability - end - - let(:guest_actions) { Ability.project_guest_rules } - let(:report_actions) { Ability.project_report_rules } - let(:dev_actions) { Ability.project_dev_rules } - let(:master_actions) { Ability.project_master_rules } - let(:owner_actions) { Ability.project_owner_rules } - - describe "Non member rules" do - it "denies for non-project users any actions" do - owner_actions.each do |action| - expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey - end - end - end - - describe "Guest Rules" do - before do - @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::GUEST) - end - - it "allows for project user any guest actions" do - guest_actions.each do |action| - expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy - end - end - end - - describe "Report Rules" do - before do - @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER) - end - - it "allows for project user any report actions" do - report_actions.each do |action| - expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy - end - end - end - - describe "Developer Rules" do - before do - @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER) - @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::DEVELOPER) - end - - it "denies for developer master-specific actions" do - [dev_actions - report_actions].each do |action| - expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey - end - end - - it "allows for project user any dev actions" do - dev_actions.each do |action| - expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy - end - end - end - - describe "Master Rules" do - before do - @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER) - @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER) - end - - it "denies for developer master-specific actions" do - [master_actions - dev_actions].each do |action| - expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey - end - end - - it "allows for project user any master actions" do - master_actions.each do |action| - expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy - end - end - end - - describe "Owner Rules" do - before do - @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER) - @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER) - end - - it "denies for masters admin-specific actions" do - [owner_actions - master_actions].each do |action| - expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey - end - end - - it "allows for project owner any admin actions" do - owner_actions.each do |action| - expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy - end - end - end - end -end -- cgit v1.2.1 From 99ee86206e3e19dd93910a4e7a3a5b6e3a7add9a Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 8 Aug 2016 10:07:15 -0700 Subject: remove six, and use a Set instead --- Gemfile | 3 --- Gemfile.lock | 2 -- app/models/ability.rb | 25 +++++++++++++++++++------ lib/api/helpers.rb | 6 +----- spec/models/members/project_member_spec.rb | 3 +-- spec/models/note_spec.rb | 3 +-- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index 194379dd687..96841013815 100644 --- a/Gemfile +++ b/Gemfile @@ -97,9 +97,6 @@ gem 'fog-rackspace', '~> 0.1.1' # for aws storage gem 'unf', '~> 0.1.4' -# Authorization -gem 'six', '~> 0.2.0' - # Seed data gem 'seed-fu', '~> 2.3.5' diff --git a/Gemfile.lock b/Gemfile.lock index 0c28975060c..1d0fcfd3c3a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -683,7 +683,6 @@ GEM rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) - six (0.2.0) slack-notifier (1.2.1) slop (3.6.0) spinach (0.8.10) @@ -954,7 +953,6 @@ DEPENDENCIES sidekiq-cron (~> 0.4.0) simplecov (= 0.12.0) sinatra (~> 1.4.4) - six (~> 0.2.0) slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) spinach-rerun-reporter (~> 0.0.2) diff --git a/app/models/ability.rb b/app/models/ability.rb index fcd7740d79f..622f481a4fc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,7 +1,23 @@ class Ability class << self + + end + + def allowed?(user, action, subject) + allowed(user, subject).include?(action) + end + def allowed(user, subject) - return anonymous_abilities(user, subject) if user.nil? + return uncached_allowed(user, subject) unless RequestStore.active? + + user_key = user ? user.id : 'anonymous' + subject_key = subject ? "#{subject.class.name}/#{subject.id}" : 'global' + key = "/ability/#{user_key}/#{subject_key}" + RequestStore[key] ||= Set.new(uncached_allowed(user, subject)).freeze + end + + def uncached_allowed(user, subject) + return anonymous_abilities(subject) if user.nil? return [] unless user.is_a?(User) return [] if user.blocked? @@ -586,11 +602,8 @@ class Ability end def abilities - @abilities ||= begin - abilities = Six.new - abilities << self - abilities - end + warn 'Ability.abilities is deprecated, use Ability.allowed?(user, action, subject) instead' + self end private diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index da4b1bf9902..1afca5fe2e8 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -409,11 +409,7 @@ module API end def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end + Ability end def secret_token diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 913d74645a7..c2bf48da44e 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -71,8 +71,7 @@ describe ProjectMember, models: true do describe :import_team do before do - @abilities = Six.new - @abilities << Ability + @abilities = Ability @project_1 = create :project @project_2 = create :project diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 9e8ae07e0b2..f4b9fa270e4 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -85,8 +85,7 @@ describe Note, models: true do @u1 = create(:user) @u2 = create(:user) @u3 = create(:user) - @abilities = Six.new - @abilities << Ability + @abilities = Ability end describe 'read' do -- cgit v1.2.1 From 8702cef27146ab62d44065af3f3d388c7effcedb Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 8 Aug 2016 14:02:29 -0700 Subject: don't double-cache project_abilites --- app/models/ability.rb | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 622f481a4fc..595e6be6642 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -181,17 +181,8 @@ class Ability end def project_abilities(user, project) - key = "/user/#{user.id}/project/#{project.id}" - - if RequestStore.active? - RequestStore.store[key] ||= uncached_project_abilities(user, project) - else - uncached_project_abilities(user, project) - end - end - - def uncached_project_abilities(user, project) rules = [] + # Push abilities on the users team role rules.push(*project_team_rules(project.team, user)) @@ -218,7 +209,7 @@ class Ability rules -= project_archived_rules end - (rules - project_disabled_features_rules(project)).uniq + rules - project_disabled_features_rules(project) end def project_team_rules(team, user) -- cgit v1.2.1 From c218dd90dabb0ddff7fab09abbb348fe1c56201b Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 23 Aug 2016 17:29:40 -0700 Subject: make almost everything on Ability private --- app/models/ability.rb | 90 +++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 595e6be6642..3eb8a5f6e03 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,6 +1,48 @@ class Ability class << self + # Given a list of users and a project this method returns the users that can + # read the given project. + def users_that_can_read_project(users, project) + if project.public? + users + else + users.select do |user| + if user.admin? + true + elsif project.internal? && !user.external? + true + elsif project.owner == user + true + elsif project.team.members.include?(user) + true + else + false + end + end + end + end + # Returns an Array of Issues that can be read by the given user. + # + # issues - The issues to reduce down to those readable by the user. + # user - The User for which to check the issues + def issues_readable_by_user(issues, user = nil) + return issues if user && user.admin? + + issues.select { |issue| issue.visible_to_user?(user) } + end + + # TODO: make this private and use the actual abilities stuff for this + def can_edit_note?(user, note) + return false if !note.editable? || !user.present? + return true if note.author == user || user.admin? + + if note.project + max_access_level = note.project.team.max_member_access(user.id) + max_access_level >= Gitlab::Access::MASTER + else + false + end end def allowed?(user, action, subject) @@ -16,6 +58,8 @@ class Ability RequestStore[key] ||= Set.new(uncached_allowed(user, subject)).freeze end + private + def uncached_allowed(user, subject) return anonymous_abilities(subject) if user.nil? return [] unless user.is_a?(User) @@ -44,38 +88,6 @@ class Ability end.concat(global_abilities(user)) end - # Given a list of users and a project this method returns the users that can - # read the given project. - def users_that_can_read_project(users, project) - if project.public? - users - else - users.select do |user| - if user.admin? - true - elsif project.internal? && !user.external? - true - elsif project.owner == user - true - elsif project.team.members.include?(user) - true - else - false - end - end - end - end - - # Returns an Array of Issues that can be read by the given user. - # - # issues - The issues to reduce down to those readable by the user. - # user - The User for which to check the issues - def issues_readable_by_user(issues, user = nil) - return issues if user && user.admin? - - issues.select { |issue| issue.visible_to_user?(user) } - end - # List of possible abilities for anonymous user def anonymous_abilities(user, subject) if subject.is_a?(PersonalSnippet) @@ -420,18 +432,6 @@ class Ability GroupProjectsFinder.new(group).execute(user).any? end - def can_edit_note?(user, note) - return false if !note.editable? || !user.present? - return true if note.author == user || user.admin? - - if note.project - max_access_level = note.project.team.max_member_access(user.id) - max_access_level >= Gitlab::Access::MASTER - else - false - end - end - def namespace_abilities(user, namespace) rules = [] @@ -597,8 +597,6 @@ class Ability self end - private - def restricted_public_level? current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) end -- cgit v1.2.1 From 5853c96b49010aaf33b85caeb94dfc18873d5656 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 8 Aug 2016 11:55:13 -0700 Subject: remove Ability.abilities --- app/controllers/application_controller.rb | 8 ++------ app/finders/issuable_finder.rb | 2 +- app/finders/todos_finder.rb | 2 +- app/mailers/base_mailer.rb | 2 +- app/models/ability.rb | 5 ----- app/models/event.rb | 2 +- app/models/merge_request.rb | 2 +- app/models/user.rb | 6 +----- app/services/base_service.rb | 6 +----- lib/api/helpers.rb | 6 +----- lib/banzai/reference_parser/base_parser.rb | 2 +- .../projects/boards/issues_controller_spec.rb | 4 ++-- .../projects/boards/lists_controller_spec.rb | 4 ++-- spec/controllers/projects/boards_controller_spec.rb | 4 ++-- spec/lib/banzai/reference_parser/base_parser_spec.rb | 8 ++++---- spec/lib/banzai/reference_parser/user_parser_spec.rb | 10 +++++----- spec/models/members/project_member_spec.rb | 6 ++---- spec/models/note_spec.rb | 19 +++++++++---------- 18 files changed, 37 insertions(+), 61 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ebc2a4651ba..bd4ba384b29 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception - helper_method :abilities, :can?, :current_application_settings + helper_method :can?, :current_application_settings helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? rescue_from Encoding::CompatibilityError do |exception| @@ -97,12 +97,8 @@ class ApplicationController < ActionController::Base current_application_settings.after_sign_out_path.presence || new_user_session_path end - def abilities - Ability.abilities - end - def can?(object, action, subject) - abilities.allowed?(object, action, subject) + Ability.allowed?(object, action, subject) end def access_denied! diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 33daac0399e..60996b181f2 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -64,7 +64,7 @@ class IssuableFinder if project? @project = Project.find(params[:project_id]) - unless Ability.abilities.allowed?(current_user, :read_project, @project) + unless Ability.allowed?(current_user, :read_project, @project) @project = nil end else diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 06b3e8a9502..a93a63bdb9b 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -83,7 +83,7 @@ class TodosFinder if project? @project = Project.find(params[:project_id]) - unless Ability.abilities.allowed?(current_user, :read_project, @project) + unless Ability.allowed?(current_user, :read_project, @project) @project = nil end else diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index 8b83bbd93b7..61a574d3dc0 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -9,7 +9,7 @@ class BaseMailer < ActionMailer::Base default reply_to: Proc.new { default_reply_to_address.format } def can? - Ability.abilities.allowed?(current_user, action, subject) + Ability.allowed?(current_user, action, subject) end private diff --git a/app/models/ability.rb b/app/models/ability.rb index 3eb8a5f6e03..891c5ba9276 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -592,11 +592,6 @@ class Ability [:read_user] end - def abilities - warn 'Ability.abilities is deprecated, use Ability.allowed?(user, action, subject) instead' - self - end - def restricted_public_level? current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) end diff --git a/app/models/event.rb b/app/models/event.rb index fd736d12359..a0b7b0dc2b5 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -65,7 +65,7 @@ class Event < ActiveRecord::Base elsif created_project? true elsif issue? || issue_note? - Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target) + Ability.allowed?(user, :read_issue, note? ? note_target : target) else ((merge_request? || note?) && target.present?) || milestone? end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 62163e74000..57d673a5f25 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -411,7 +411,7 @@ class MergeRequest < ActiveRecord::Base def can_remove_source_branch?(current_user) !source_project.protected_branch?(source_branch) && !source_project.root_ref?(source_branch) && - Ability.abilities.allowed?(current_user, :push_code, source_project) && + Ability.allowed?(current_user, :push_code, source_project) && diff_head_commit == source_branch_head end diff --git a/app/models/user.rb b/app/models/user.rb index ad3cfbc03e4..8f5958333d7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -460,16 +460,12 @@ class User < ActiveRecord::Base can?(:create_group, nil) end - def abilities - Ability.abilities - end - def can_select_namespace? several_namespaces? || admin end def can?(action, subject) - abilities.allowed?(self, action, subject) + Ability.allowed?(self, action, subject) end def first_name diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 0d55ba5a981..0c208150fb8 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -7,12 +7,8 @@ class BaseService @project, @current_user, @params = project, user, params.dup end - def abilities - Ability.abilities - end - def can?(object, action, subject) - abilities.allowed?(object, action, subject) + Ability.allowed?(object, action, subject) end def notification_service diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 1afca5fe2e8..fdb70af694d 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -148,7 +148,7 @@ module API end def can?(object, action, subject) - abilities.allowed?(object, action, subject) + Ability.allowed?(object, action, subject) end # Checks the occurrences of required attributes, each attribute must be present in the params hash @@ -408,10 +408,6 @@ module API links.join(', ') end - def abilities - Ability - end - def secret_token File.read(Gitlab.config.gitlab_shell.secret_file).chomp end diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 6cf218aaa0d..e8e03e4a98f 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -211,7 +211,7 @@ module Banzai end def can?(user, permission, subject) - Ability.abilities.allowed?(user, permission, subject) + Ability.allowed?(user, permission, subject) end def find_projects_for_hash_keys(hash) diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index d0ad5e26dbd..2896636db5a 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -41,8 +41,8 @@ describe Projects::Boards::IssuesController do context 'with unauthorized user' do before do - allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true) - allow(Ability.abilities).to receive(:allowed?).with(user, :read_issue, project).and_return(false) + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false) end it 'returns a successful 403 response' do diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index 9496636e3cc..af0491bf486 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -35,8 +35,8 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do before do - allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true) - allow(Ability.abilities).to receive(:allowed?).with(user, :read_list, project).and_return(false) + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_list, project).and_return(false) end it 'returns a successful 403 response' do diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 75a6d39e82c..6f6e608e1f3 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -23,8 +23,8 @@ describe Projects::BoardsController do context 'with unauthorized user' do before do - allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true) - allow(Ability.abilities).to receive(:allowed?).with(user, :read_board, project).and_return(false) + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) end it 'returns a successful 404 response' do diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb index ac9c66e2663..9095d2b1345 100644 --- a/spec/lib/banzai/reference_parser/base_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb @@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do it 'returns the nodes if the attribute value equals the current project ID' do link['data-project'] = project.id.to_s - expect(Ability.abilities).not_to receive(:allowed?) + expect(Ability).not_to receive(:allowed?) expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) end @@ -39,7 +39,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do link['data-project'] = other_project.id.to_s - expect(Ability.abilities).to receive(:allowed?). + expect(Ability).to receive(:allowed?). with(user, :read_project, other_project). and_return(true) @@ -57,7 +57,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do link['data-project'] = other_project.id.to_s - expect(Ability.abilities).to receive(:allowed?). + expect(Ability).to receive(:allowed?). with(user, :read_project, other_project). and_return(false) @@ -221,7 +221,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do it 'delegates the permissions check to the Ability class' do user = double(:user) - expect(Ability.abilities).to receive(:allowed?). + expect(Ability).to receive(:allowed?). with(user, :read_project, project) subject.can?(user, :read_project, project) diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index 9a82891297d..4e7f82a6e09 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -82,7 +82,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do end it 'returns the nodes if the user can read the group' do - expect(Ability.abilities).to receive(:allowed?). + expect(Ability).to receive(:allowed?). with(user, :read_group, group). and_return(true) @@ -90,7 +90,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do end it 'returns an empty Array if the user can not read the group' do - expect(Ability.abilities).to receive(:allowed?). + expect(Ability).to receive(:allowed?). with(user, :read_group, group). and_return(false) @@ -103,7 +103,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do it 'returns the nodes if the attribute value equals the current project ID' do link['data-project'] = project.id.to_s - expect(Ability.abilities).not_to receive(:allowed?) + expect(Ability).not_to receive(:allowed?) expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) end @@ -113,7 +113,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do link['data-project'] = other_project.id.to_s - expect(Ability.abilities).to receive(:allowed?). + expect(Ability).to receive(:allowed?). with(user, :read_project, other_project). and_return(true) @@ -125,7 +125,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do link['data-project'] = other_project.id.to_s - expect(Ability.abilities).to receive(:allowed?). + expect(Ability).to receive(:allowed?). with(user, :read_project, other_project). and_return(false) diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index c2bf48da44e..be57957b569 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -71,8 +71,6 @@ describe ProjectMember, models: true do describe :import_team do before do - @abilities = Ability - @project_1 = create :project @project_2 = create :project @@ -91,8 +89,8 @@ describe ProjectMember, models: true do it { expect(@project_2.users).to include(@user_1) } it { expect(@project_2.users).to include(@user_2) } - it { expect(@abilities.allowed?(@user_1, :create_project, @project_2)).to be_truthy } - it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy } + it { expect(Ability.allowed?(@user_1, :create_project, @project_2)).to be_truthy } + it { expect(Ability.allowed?(@user_2, :read_project, @project_2)).to be_truthy } end describe 'project 1 should not be changed' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index f4b9fa270e4..e6b6e7c0634 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -85,7 +85,6 @@ describe Note, models: true do @u1 = create(:user) @u2 = create(:user) @u3 = create(:user) - @abilities = Ability end describe 'read' do @@ -94,9 +93,9 @@ describe Note, models: true do @p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST) end - it { expect(@abilities.allowed?(@u1, :read_note, @p1)).to be_falsey } - it { expect(@abilities.allowed?(@u2, :read_note, @p1)).to be_truthy } - it { expect(@abilities.allowed?(@u3, :read_note, @p1)).to be_falsey } + it { expect(Ability.allowed?(@u1, :read_note, @p1)).to be_falsey } + it { expect(Ability.allowed?(@u2, :read_note, @p1)).to be_truthy } + it { expect(Ability.allowed?(@u3, :read_note, @p1)).to be_falsey } end describe 'write' do @@ -105,9 +104,9 @@ describe Note, models: true do @p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER) end - it { expect(@abilities.allowed?(@u1, :create_note, @p1)).to be_falsey } - it { expect(@abilities.allowed?(@u2, :create_note, @p1)).to be_truthy } - it { expect(@abilities.allowed?(@u3, :create_note, @p1)).to be_falsey } + it { expect(Ability.allowed?(@u1, :create_note, @p1)).to be_falsey } + it { expect(Ability.allowed?(@u2, :create_note, @p1)).to be_truthy } + it { expect(Ability.allowed?(@u3, :create_note, @p1)).to be_falsey } end describe 'admin' do @@ -117,9 +116,9 @@ describe Note, models: true do @p2.project_members.create(user: @u3, access_level: ProjectMember::MASTER) end - it { expect(@abilities.allowed?(@u1, :admin_note, @p1)).to be_falsey } - it { expect(@abilities.allowed?(@u2, :admin_note, @p1)).to be_truthy } - it { expect(@abilities.allowed?(@u3, :admin_note, @p1)).to be_falsey } + it { expect(Ability.allowed?(@u1, :admin_note, @p1)).to be_falsey } + it { expect(Ability.allowed?(@u2, :admin_note, @p1)).to be_truthy } + it { expect(Ability.allowed?(@u3, :admin_note, @p1)).to be_falsey } end end -- cgit v1.2.1 From e208765a92748086cacbc56225e827c8463750a5 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 11 Aug 2016 15:12:52 -0700 Subject: add policies, and factor out ProjectPolicy --- app/models/ability.rb | 35 +------ app/policies/base_policy.rb | 25 +++++ app/policies/project_policy.rb | 202 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 31 deletions(-) create mode 100644 app/policies/base_policy.rb create mode 100644 app/policies/project_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index 891c5ba9276..4f0ffa09a1f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -71,7 +71,7 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject when CommitStatus then commit_status_abilities(user, subject) - when Project then project_abilities(user, subject) + when Project then ProjectPolicy.new(user, subject).abilities when Issue then issue_abilities(user, subject) when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) @@ -85,7 +85,7 @@ class Ability when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) when Ci::Runner then runner_abilities(user, subject) else [] - end.concat(global_abilities(user)) + end + global_abilities(user) end # List of possible abilities for anonymous user @@ -193,35 +193,8 @@ class Ability end def project_abilities(user, project) - rules = [] - - # Push abilities on the users team role - rules.push(*project_team_rules(project.team, user)) - - owner = user.admin? || - project.owner == user || - (project.group && project.group.has_owner?(user)) - - if owner - rules.push(*project_owner_rules) - end - - if project.public? || (project.internal? && !user.external?) - rules.push(*public_project_rules) - - # Allow to read builds for internal projects - rules << :read_build if project.public_builds? - - unless owner || project.team.member?(user) || project_group_member?(project, user) - rules << :request_access if project.request_access_enabled - end - end - - if project.archived? - rules -= project_archived_rules - end - - rules - project_disabled_features_rules(project) + # temporary patch, deleteme before merge + ProjectPolicy.new(user, project).abilities.to_a end def project_team_rules(team, user) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb new file mode 100644 index 00000000000..3f52b0b005a --- /dev/null +++ b/app/policies/base_policy.rb @@ -0,0 +1,25 @@ +class BasePolicy + def initialize(user, subject) + @user = user + @subject = subject + end + + def abilities + @can = Set.new + @cannot = Set.new + generate! + @can - @cannot + end + + def generate! + raise 'abstract' + end + + def can!(*rules) + @can.merge(rules) + end + + def cannot!(*rules) + @cannot.merge(rules) + end +end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb new file mode 100644 index 00000000000..1e82070e62a --- /dev/null +++ b/app/policies/project_policy.rb @@ -0,0 +1,202 @@ +class ProjectPolicy < BasePolicy + def project + @subject + end + + def guest_access! + can! :read_project + can! :read_board + can! :read_list + can! :read_wiki + can! :read_issue + can! :read_label + can! :read_milestone + can! :read_project_snippet + can! :read_project_member + can! :read_merge_request + can! :read_note + can! :create_project + can! :create_issue + can! :create_note + can! :upload_file + end + + def reporter_access! + can! :download_code + can! :fork_project + can! :create_project_snippet + can! :update_issue + can! :admin_issue + can! :admin_label + can! :read_commit_status + can! :read_build + can! :read_container_image + can! :read_pipeline + can! :read_environment + can! :read_deployment + end + + def developer_access! + can! :admin_merge_request + can! :update_merge_request + can! :create_commit_status + can! :update_commit_status + can! :create_build + can! :update_build + can! :create_pipeline + can! :update_pipeline + can! :create_merge_request + can! :create_wiki + can! :push_code + can! :create_container_image + can! :update_container_image + can! :create_environment + can! :create_deployment + end + + def master_access! + can! :push_code_to_protected_branches + can! :update_project_snippet + can! :update_environment + can! :update_deployment + can! :admin_milestone + can! :admin_project_snippet + can! :admin_project_member + can! :admin_merge_request + can! :admin_note + can! :admin_wiki + can! :admin_project + can! :admin_commit_status + can! :admin_build + can! :admin_container_image + can! :admin_pipeline + can! :admin_environment + can! :admin_deployment + end + + def public_access! + can! :download_code + can! :fork_project + can! :read_commit_status + can! :read_pipeline + can! :read_container_image + end + + def owner_access! + guest_access! + reporter_access! + developer_access! + master_access! + can! :change_namespace + can! :change_visibility_level + can! :rename_project + can! :remove_project + can! :archive_project + can! :remove_fork_project + can! :destroy_merge_request + can! :destroy_issue + end + + # Push abilities on the users team role + def team_access! + access = project.team.max_member_access(@user.id) + + return if access < Gitlab::Access::GUEST + guest_access! + + return if access < Gitlab::Access::REPORTER + reporter_access! + + return if access < Gitlab::Access::DEVELOPER + developer_access! + + return if access < Gitlab::Access::MASTER + master_access! + end + + def archived_access! + cannot! :create_merge_request + cannot! :push_code + cannot! :push_code_to_protected_branches + cannot! :update_merge_request + cannot! :admin_merge_request + end + + def disabled_features! + unless project.issues_enabled + cannot!(*named_abilities(:issue)) + end + + unless project.merge_requests_enabled + cannot!(*named_abilities(:merge_request)) + end + + unless project.issues_enabled or project.merge_requests_enabled + cannot!(*named_abilities(:label)) + cannot!(*named_abilities(:milestone)) + end + + unless project.snippets_enabled + cannot!(*named_abilities(:project_snippet)) + end + + unless project.wiki_enabled + cannot!(*named_abilities(:wiki)) + end + + unless project.builds_enabled + cannot!(*named_abilities(:build)) + cannot!(*named_abilities(:pipeline)) + cannot!(*named_abilities(:environment)) + cannot!(*named_abilities(:deployment)) + end + + unless project.container_registry_enabled + cannot!(*named_abilities(:container_image)) + end + end + + def generate! + team_access! + + owner = @user.admin? || + project.owner == @user || + (project.group && project.group.has_owner?(@user)) + + owner_access! if owner + + if project.public? || (project.internal? && !@user.external?) + guest_access! + public_access! + + # Allow to read builds for internal projects + can! :read_build if project.public_builds? + + if project.request_access_enabled && + !(owner || project.team.member?(@user) || project_group_member?) + can! :request_access + end + end + + archived_access! if project.archived? + + disabled_features! + end + + def project_group_member? + project.group && + ( + project.group.members.exists?(user_id: @user.id) || + project.group.requesters.exists?(user_id: @user.id) + ) + end + + def named_abilities(name) + [ + :"read_#{name}", + :"create_#{name}", + :"update_#{name}", + :"admin_#{name}" + ] + end +end -- cgit v1.2.1 From 29b1623a3615fb7683702f4de2dfeafca10f9c1c Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 23 Aug 2016 16:19:36 -0700 Subject: add project_policy_spec to replace .project_abilities spec --- spec/models/ability_spec.rb | 64 ------------------------------------ spec/policies/project_policy_spec.rb | 36 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 64 deletions(-) create mode 100644 spec/policies/project_policy_spec.rb diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index c50ca38bdd9..c9e6a334c67 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -171,70 +171,6 @@ describe Ability, lib: true do end end - shared_examples_for ".project_abilities" do |enable_request_store| - before do - RequestStore.begin! if enable_request_store - end - - after do - if enable_request_store - RequestStore.end! - RequestStore.clear! - end - end - - describe '.project_abilities' do - let!(:project) { create(:empty_project, :public) } - let!(:user) { create(:user) } - - it 'returns permissions for admin user' do - admin = create(:admin) - - results = described_class.project_abilities(admin, project) - - expect(results.count).to eq(68) - end - - it 'returns permissions for an owner' do - results = described_class.project_abilities(project.owner, project) - - expect(results.count).to eq(68) - end - - it 'returns permissions for a master' do - project.team << [user, :master] - - results = described_class.project_abilities(user, project) - - expect(results.count).to eq(60) - end - - it 'returns permissions for a developer' do - project.team << [user, :developer] - - results = described_class.project_abilities(user, project) - - expect(results.count).to eq(44) - end - - it 'returns permissions for a guest' do - project.team << [user, :guest] - - results = described_class.project_abilities(user, project) - - expect(results.count).to eq(21) - end - end - end - - describe '.project_abilities with RequestStore' do - it_behaves_like ".project_abilities", true - end - - describe '.project_abilities without RequestStore' do - it_behaves_like ".project_abilities", false - end - describe '.issues_readable_by_user' do context 'with an admin user' do it 'returns all given issues' do diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb new file mode 100644 index 00000000000..eda1cafd65e --- /dev/null +++ b/spec/policies/project_policy_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe ProjectPolicy, models: true do + let(:project) { create(:empty_project, :public) } + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + let(:dev) { create(:user) } + let(:master) { create(:user) } + let(:owner) { create(:user) } + let(:admin) { create(:admin) } + + let(:users_ordered_by_permissions) do + [nil, guest, reporter, dev, master, owner, admin] + end + + let(:users_permissions) do + users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size } + end + + before do + project.team << [guest, :guest] + project.team << [master, :master] + project.team << [dev, :developer] + project.team << [reporter, :reporter] + + group = create(:group) + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::MASTER) + group.add_owner(owner) + end + + it 'returns increasing permissions for each level' do + expect(users_permissions).to eq(users_permissions.sort.uniq) + end +end -- cgit v1.2.1 From 1ca9b3354a350b83d1e025b3d46280bc5bb60f2b Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Fri, 12 Aug 2016 11:36:16 -0700 Subject: add support for anonymous abilities --- app/models/ability.rb | 194 ++--------------------------------------- app/policies/base_policy.rb | 26 +++++- app/policies/project_policy.rb | 52 ++++++++--- 3 files changed, 67 insertions(+), 205 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 4f0ffa09a1f..5d2cbde4c0e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -71,7 +71,7 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject when CommitStatus then commit_status_abilities(user, subject) - when Project then ProjectPolicy.new(user, subject).abilities + when Project then ProjectPolicy.abilities(user, subject) when Issue then issue_abilities(user, subject) when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) @@ -96,8 +96,10 @@ class Ability anonymous_project_snippet_abilities(subject) elsif subject.is_a?(CommitStatus) anonymous_commit_status_abilities(subject) - elsif subject.is_a?(Project) || subject.respond_to?(:project) - anonymous_project_abilities(subject) + elsif subject.is_a?(Project) + ProjectPolicy.abilities(nil, subject) + elsif subject.respond_to?(:project) + ProjectPolicy.abilities(nil, subject.project) elsif subject.is_a?(Group) || subject.respond_to?(:group) anonymous_group_abilities(subject) elsif subject.is_a?(User) @@ -194,174 +196,7 @@ class Ability def project_abilities(user, project) # temporary patch, deleteme before merge - ProjectPolicy.new(user, project).abilities.to_a - end - - def project_team_rules(team, user) - # Rules based on role in project - if team.master?(user) - project_master_rules - elsif team.developer?(user) - project_dev_rules - elsif team.reporter?(user) - project_report_rules - elsif team.guest?(user) - project_guest_rules - else - [] - end - end - - def public_project_rules - @public_project_rules ||= project_guest_rules + [ - :download_code, - :fork_project, - :read_commit_status, - :read_pipeline, - :read_container_image - ] - end - - def project_guest_rules - @project_guest_rules ||= [ - :read_project, - :read_wiki, - :read_issue, - :read_board, - :read_list, - :read_label, - :read_milestone, - :read_project_snippet, - :read_project_member, - :read_merge_request, - :read_note, - :create_project, - :create_issue, - :create_note, - :upload_file - ] - end - - def project_report_rules - @project_report_rules ||= project_guest_rules + [ - :download_code, - :fork_project, - :create_project_snippet, - :update_issue, - :admin_issue, - :admin_label, - :admin_list, - :read_commit_status, - :read_build, - :read_container_image, - :read_pipeline, - :read_environment, - :read_deployment - ] - end - - def project_dev_rules - @project_dev_rules ||= project_report_rules + [ - :admin_merge_request, - :update_merge_request, - :create_commit_status, - :update_commit_status, - :create_build, - :update_build, - :create_pipeline, - :update_pipeline, - :create_merge_request, - :create_wiki, - :push_code, - :resolve_note, - :create_container_image, - :update_container_image, - :create_environment, - :create_deployment - ] - end - - def project_archived_rules - @project_archived_rules ||= [ - :create_merge_request, - :push_code, - :push_code_to_protected_branches, - :update_merge_request, - :admin_merge_request - ] - end - - def project_master_rules - @project_master_rules ||= project_dev_rules + [ - :push_code_to_protected_branches, - :update_project_snippet, - :update_environment, - :update_deployment, - :admin_milestone, - :admin_project_snippet, - :admin_project_member, - :admin_merge_request, - :admin_note, - :admin_wiki, - :admin_project, - :admin_commit_status, - :admin_build, - :admin_container_image, - :admin_pipeline, - :admin_environment, - :admin_deployment - ] - end - - def project_owner_rules - @project_owner_rules ||= project_master_rules + [ - :change_namespace, - :change_visibility_level, - :rename_project, - :remove_project, - :archive_project, - :remove_fork_project, - :destroy_merge_request, - :destroy_issue - ] - end - - def project_disabled_features_rules(project) - rules = [] - - unless project.issues_enabled - rules += named_abilities('issue') - end - - unless project.merge_requests_enabled - rules += named_abilities('merge_request') - end - - unless project.issues_enabled or project.merge_requests_enabled - rules += named_abilities('label') - rules += named_abilities('milestone') - end - - unless project.snippets_enabled - rules += named_abilities('project_snippet') - end - - unless project.has_wiki? - rules += named_abilities('wiki') - end - - unless project.builds_enabled - rules += named_abilities('build') - rules += named_abilities('pipeline') - rules += named_abilities('environment') - rules += named_abilities('deployment') - end - - unless project.container_registry_enabled - rules += named_abilities('container_image') - end - - rules + ProjectPolicy.abilities(user, project).to_a end def group_abilities(user, group) @@ -569,15 +404,6 @@ class Ability current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) end - def named_abilities(name) - [ - :"read_#{name}", - :"create_#{name}", - :"update_#{name}", - :"admin_#{name}" - ] - end - def filter_confidential_issues_abilities(user, issue, rules) return rules if user.admin? || !issue.confidential? @@ -589,13 +415,5 @@ class Ability rules end - - def project_group_member?(project, user) - project.group && - ( - project.group.members.exists?(user_id: user.id) || - project.group.requesters.exists?(user_id: user.id) - ) - end end end diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 3f52b0b005a..10ce38329c4 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -1,14 +1,21 @@ class BasePolicy + def self.abilities(user, subject) + new(user, subject).abilities + end + + attr_reader :user, :subject def initialize(user, subject) @user = user @subject = subject end def abilities - @can = Set.new - @cannot = Set.new - generate! - @can - @cannot + return anonymous_abilities if @user.nil? + collect_rules { rules } + end + + def anonymous_abilities + collect_rules { anonymous_rules } end def generate! @@ -22,4 +29,15 @@ class BasePolicy def cannot!(*rules) @cannot.merge(rules) end + + private + + def collect_rules(&b) + return Set.new if @subject.nil? + + @can = Set.new + @cannot = Set.new + yield + @can - @cannot + end end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 1e82070e62a..95e8b71c102 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -28,6 +28,7 @@ class ProjectPolicy < BasePolicy can! :update_issue can! :admin_issue can! :admin_label + can! :admin_list can! :read_commit_status can! :read_build can! :read_container_image @@ -48,6 +49,7 @@ class ProjectPolicy < BasePolicy can! :create_merge_request can! :create_wiki can! :push_code + can! :resolve_note can! :create_container_image can! :update_container_image can! :create_environment @@ -98,8 +100,8 @@ class ProjectPolicy < BasePolicy end # Push abilities on the users team role - def team_access! - access = project.team.max_member_access(@user.id) + def team_access!(user) + access = project.team.max_member_access(user.id) return if access < Gitlab::Access::GUEST guest_access! @@ -140,7 +142,7 @@ class ProjectPolicy < BasePolicy cannot!(*named_abilities(:project_snippet)) end - unless project.wiki_enabled + unless project.has_wiki? cannot!(*named_abilities(:wiki)) end @@ -156,16 +158,16 @@ class ProjectPolicy < BasePolicy end end - def generate! - team_access! + def rules + team_access!(user) - owner = @user.admin? || - project.owner == @user || - (project.group && project.group.has_owner?(@user)) + owner = user.admin? || + project.owner == user || + (project.group && project.group.has_owner?(user)) owner_access! if owner - if project.public? || (project.internal? && !@user.external?) + if project.public? || (project.internal? && !user.external?) guest_access! public_access! @@ -173,7 +175,7 @@ class ProjectPolicy < BasePolicy can! :read_build if project.public_builds? if project.request_access_enabled && - !(owner || project.team.member?(@user) || project_group_member?) + !(owner || project.team.member?(user) || project_group_member?(user)) can! :request_access end end @@ -183,11 +185,35 @@ class ProjectPolicy < BasePolicy disabled_features! end - def project_group_member? + def anonymous_rules + return unless project.public? + + can! :read_project + can! :read_board + can! :read_list + can! :read_wiki + can! :read_label + can! :read_milestone + can! :read_project_snippet + can! :read_project_member + can! :read_merge_request + can! :read_note + can! :read_pipeline + can! :read_commit_status + can! :read_container_image + can! :download_code + + # Allow to read builds by anonymous user if guests are allowed + can! :read_build if project.public_builds? + + disabled_features! + end + + def project_group_member?(user) project.group && ( - project.group.members.exists?(user_id: @user.id) || - project.group.requesters.exists?(user_id: @user.id) + project.group.members.exists?(user_id: user.id) || + project.group.requesters.exists?(user_id: user.id) ) end -- cgit v1.2.1 From 4d904bf3521b4600db228c48214f3892e86ac72a Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 11:10:34 -0700 Subject: port issues to Issu{able,e}Policy --- app/models/ability.rb | 6 ++++-- app/policies/base_policy.rb | 12 ++++++++++-- app/policies/issuable_policy.rb | 14 ++++++++++++++ app/policies/issue_policy.rb | 27 +++++++++++++++++++++++++++ app/policies/project_policy.rb | 3 +++ 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 app/policies/issuable_policy.rb create mode 100644 app/policies/issue_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index 5d2cbde4c0e..1ea97855e04 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -72,7 +72,7 @@ class Ability case subject when CommitStatus then commit_status_abilities(user, subject) when Project then ProjectPolicy.abilities(user, subject) - when Issue then issue_abilities(user, subject) + when Issue then IssuePolicy.abilities(user, subject) when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject) @@ -89,7 +89,7 @@ class Ability end # List of possible abilities for anonymous user - def anonymous_abilities(user, subject) + def anonymous_abilities(subject) if subject.is_a?(PersonalSnippet) anonymous_personal_snippet_abilities(subject) elsif subject.is_a?(ProjectSnippet) @@ -98,6 +98,8 @@ class Ability anonymous_commit_status_abilities(subject) elsif subject.is_a?(Project) ProjectPolicy.abilities(nil, subject) + elsif subject.is_a?(Issue) + IssuePolicy.abilities(nil, subject) elsif subject.respond_to?(:project) ProjectPolicy.abilities(nil, subject.project) elsif subject.is_a?(Group) || subject.respond_to?(:group) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 10ce38329c4..fd5d05a1bd1 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -3,6 +3,10 @@ class BasePolicy new(user, subject).abilities end + def self.class_for(subject) + "#{subject.class.name}Policy".constantize + end + attr_reader :user, :subject def initialize(user, subject) @user = user @@ -18,8 +22,12 @@ class BasePolicy collect_rules { anonymous_rules } end - def generate! - raise 'abstract' + def anonymous_rules + rules + end + + def delegate!(new_subject) + @can.merge(BasePolicy.class_for(new_subject).abilities(@user, new_subject)) end def can!(*rules) diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb new file mode 100644 index 00000000000..c253f9a9399 --- /dev/null +++ b/app/policies/issuable_policy.rb @@ -0,0 +1,14 @@ +class IssuablePolicy < BasePolicy + def action_name + @subject.class.name.underscore + end + + def rules + if @user && (@subject.author == @user || @subject.assignee == @user) + can! :"read_#{action_name}" + can! :"update_#{action_name}" + end + + delegate! @subject.project + end +end diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb new file mode 100644 index 00000000000..08538861364 --- /dev/null +++ b/app/policies/issue_policy.rb @@ -0,0 +1,27 @@ +class IssuePolicy < IssuablePolicy + def issue + @subject + end + + def rules + super + + if @subject.confidential? && !can_read_confidential? + cannot! :read_issue + cannot! :admin_issue + cannot! :update_issue + cannot! :read_issue + end + end + + private + + def can_read_confidential? + return false unless @user + return true if @user.admin? + return true if @subject.author == @user + return true if @subject.assignee == @user + return true if @subject.project.team.member?(@user, Gitlab::Access::REPORTER) + false + end +end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 95e8b71c102..4380b00d962 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -203,6 +203,9 @@ class ProjectPolicy < BasePolicy can! :read_container_image can! :download_code + # NB: may be overridden by IssuePolicy + can! :read_issue + # Allow to read builds by anonymous user if guests are allowed can! :read_build if project.public_builds? -- cgit v1.2.1 From 092861093066f6b474c2dc72de34acf64380a3e6 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 11:32:08 -0700 Subject: add and use MergeRequestPolicy --- app/models/ability.rb | 7 +++++-- app/policies/merge_request_policy.rb | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 app/policies/merge_request_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index 1ea97855e04..b8e3e97b351 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -70,13 +70,14 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject - when CommitStatus then commit_status_abilities(user, subject) when Project then ProjectPolicy.abilities(user, subject) when Issue then IssuePolicy.abilities(user, subject) + when MergeRequest then MergeRequestPolicy.abilities(user, subject) + + when CommitStatus then commit_status_abilities(user, subject) when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject) - when MergeRequest then merge_request_abilities(user, subject) when Group then group_abilities(user, subject) when Namespace then namespace_abilities(user, subject) when GroupMember then group_member_abilities(user, subject) @@ -100,6 +101,8 @@ class Ability ProjectPolicy.abilities(nil, subject) elsif subject.is_a?(Issue) IssuePolicy.abilities(nil, subject) + elsif subject.is_a?(MergeRequest) + MergeRequestPolicy.abilities(nil, subject) elsif subject.respond_to?(:project) ProjectPolicy.abilities(nil, subject.project) elsif subject.is_a?(Group) || subject.respond_to?(:group) diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb new file mode 100644 index 00000000000..bc3afc626fb --- /dev/null +++ b/app/policies/merge_request_policy.rb @@ -0,0 +1,3 @@ +class MergeRequestPolicy < IssuablePolicy + # pass +end -- cgit v1.2.1 From 16fe6dc7b159a0e6b68a586065de1f95d6acecfa Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 12:05:44 -0700 Subject: port CommitStatus/Build --- app/models/ability.rb | 3 ++- app/policies/base_policy.rb | 4 ++++ app/policies/ci/build_policy.rb | 13 +++++++++++++ app/policies/commit_status_policy.rb | 5 +++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 app/policies/ci/build_policy.rb create mode 100644 app/policies/commit_status_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index b8e3e97b351..c89cc9b2e17 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -74,7 +74,8 @@ class Ability when Issue then IssuePolicy.abilities(user, subject) when MergeRequest then MergeRequestPolicy.abilities(user, subject) - when CommitStatus then commit_status_abilities(user, subject) + when Ci::Build then Ci::BuildPolicy.abilities(user, subject) + when CommitStatus then CommitStatus.abilities(user, subject) when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index fd5d05a1bd1..e1757d97e89 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -30,6 +30,10 @@ class BasePolicy @can.merge(BasePolicy.class_for(new_subject).abilities(@user, new_subject)) end + def can?(rule) + @can.include?(rule) && !@cannot.include?(rule) + end + def can!(*rules) @can.merge(rules) end diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb new file mode 100644 index 00000000000..2232e231cf8 --- /dev/null +++ b/app/policies/ci/build_policy.rb @@ -0,0 +1,13 @@ +module Ci + class BuildPolicy < CommitStatusPolicy + def rules + super + + # If we can't read build we should also not have that + # ability when looking at this in context of commit_status + %w(read create update admin).each do |rule| + cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build" + end + end + end +end diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb new file mode 100644 index 00000000000..593df738328 --- /dev/null +++ b/app/policies/commit_status_policy.rb @@ -0,0 +1,5 @@ +class CommitStatusPolicy < BasePolicy + def rules + delegate! @subject.project + end +end -- cgit v1.2.1 From 3656d3b88a01a50a5eaf66a16b6ac47d3c58352c Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 12:55:44 -0700 Subject: add automatic detection of the policy class --- app/models/ability.rb | 18 +++--------------- app/policies/base_policy.rb | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index c89cc9b2e17..ac5e82c14d2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -61,6 +61,9 @@ class Ability private def uncached_allowed(user, subject) + policy_class = BasePolicy.class_for(subject) rescue nil + return policy_class.abilities(user, subject) if policy_class + return anonymous_abilities(subject) if user.nil? return [] unless user.is_a?(User) return [] if user.blocked? @@ -70,13 +73,6 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject - when Project then ProjectPolicy.abilities(user, subject) - when Issue then IssuePolicy.abilities(user, subject) - when MergeRequest then MergeRequestPolicy.abilities(user, subject) - - when Ci::Build then Ci::BuildPolicy.abilities(user, subject) - when CommitStatus then CommitStatus.abilities(user, subject) - when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject) when Group then group_abilities(user, subject) @@ -96,14 +92,6 @@ class Ability anonymous_personal_snippet_abilities(subject) elsif subject.is_a?(ProjectSnippet) anonymous_project_snippet_abilities(subject) - elsif subject.is_a?(CommitStatus) - anonymous_commit_status_abilities(subject) - elsif subject.is_a?(Project) - ProjectPolicy.abilities(nil, subject) - elsif subject.is_a?(Issue) - IssuePolicy.abilities(nil, subject) - elsif subject.is_a?(MergeRequest) - MergeRequestPolicy.abilities(nil, subject) elsif subject.respond_to?(:project) ProjectPolicy.abilities(nil, subject.project) elsif subject.is_a?(Group) || subject.respond_to?(:group) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index e1757d97e89..12f60d8f76e 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -4,7 +4,21 @@ class BasePolicy end def self.class_for(subject) - "#{subject.class.name}Policy".constantize + subject.class.ancestors.each do |klass| + next unless klass.name + + begin + policy_class = "#{klass.name}Policy".constantize + + # NB: the < operator here tests whether policy_class + # inherits from BasePolicy + return policy_class if policy_class < BasePolicy + rescue NameError + nil + end + end + + raise "no policy for #{subject.class.name}" end attr_reader :user, :subject -- cgit v1.2.1 From d87c1d550f4870275432698e3cb19033c6855a15 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 13:08:14 -0700 Subject: port notes and project snippets --- app/policies/note_policy.rb | 19 +++++++++++++++++++ app/policies/project_snippet_policy.rb | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 app/policies/note_policy.rb create mode 100644 app/policies/project_snippet_policy.rb diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb new file mode 100644 index 00000000000..83847466ee2 --- /dev/null +++ b/app/policies/note_policy.rb @@ -0,0 +1,19 @@ +class NotePolicy < BasePolicy + def rules + delegate! @subject.project + + return unless @user + + if @subject.author == @user + can! :read_note + can! :update_note + can! :admin_note + can! :resolve_note + end + + if @subject.for_merge_request? && + @subject.noteable.author == @user + can! :resolve_note + end + end +end diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb new file mode 100644 index 00000000000..57acccfafd9 --- /dev/null +++ b/app/policies/project_snippet_policy.rb @@ -0,0 +1,20 @@ +class ProjectSnippetPolicy < BasePolicy + def rules + can! :read_project_snippet if @subject.public? + return unless @user + + if @user && @subject.author == @user || @user.admin? + can! :read_project_snippet + can! :update_project_snippet + can! :admin_project_snippet + end + + if @subject.internal? && !@user.external? + can! :read_project_snippet + end + + if @subject.private? && @subject.project.team.member?(@user) + can! :read_project_snippet + end + end +end -- cgit v1.2.1 From 3fdcebfdda31b0cc0f5641489bb4066b1f815df3 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 14:09:44 -0700 Subject: trim dead code --- app/models/ability.rb | 81 --------------------------------------------------- 1 file changed, 81 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index ac5e82c14d2..323597c8888 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -73,7 +73,6 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject - when ProjectSnippet then project_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject) when Group then group_abilities(user, subject) when Namespace then namespace_abilities(user, subject) @@ -140,13 +139,6 @@ class Ability end end - def anonymous_commit_status_abilities(subject) - rules = anonymous_project_abilities(subject.project) - # If subject is Ci::Build which inherits from CommitStatus filter the abilities - rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build) - rules - end - def anonymous_group_abilities(subject) rules = [] @@ -169,14 +161,6 @@ class Ability end end - def anonymous_project_snippet_abilities(snippet) - if snippet.public? - [:read_project_snippet] - else - [] - end - end - def anonymous_user_abilities [:read_user] unless restricted_public_level? end @@ -248,46 +232,6 @@ class Ability rules.flatten end - [:issue, :merge_request].each do |name| - define_method "#{name}_abilities" do |user, subject| - rules = [] - - if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user) - rules += [ - :"read_#{name}", - :"update_#{name}", - ] - end - - rules += project_abilities(user, subject.project) - rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue) - rules - end - end - - def note_abilities(user, note) - rules = [] - - if note.author == user - rules += [ - :read_note, - :update_note, - :admin_note, - :resolve_note - ] - end - - if note.respond_to?(:project) && note.project - rules += project_abilities(user, note.project) - end - - if note.for_merge_request? && note.noteable.author == user - rules << :resolve_note - end - - rules - end - def personal_snippet_abilities(user, snippet) rules = [] @@ -306,24 +250,6 @@ class Ability rules end - def project_snippet_abilities(user, snippet) - rules = [] - - if snippet.author == user || user.admin? - rules += [ - :read_project_snippet, - :update_project_snippet, - :admin_project_snippet - ] - end - - if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user)) - rules << :read_project_snippet - end - - rules - end - def group_member_abilities(user, subject) rules = [] target_user = subject.user @@ -362,13 +288,6 @@ class Ability rules end - def commit_status_abilities(user, subject) - rules = project_abilities(user, subject.project) - # If subject is Ci::Build which inherits from CommitStatus filter the abilities - rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build) - rules - end - def filter_build_abilities(rules) # If we can't read build we should also not have that # ability when looking at this in context of commit_status -- cgit v1.2.1 From 4016c5351362a409b9d8bb258e0330089cdb4394 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 15:31:01 -0700 Subject: port personal snippets --- app/models/ability.rb | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 323597c8888..c5392379b32 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -73,7 +73,6 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject - when PersonalSnippet then personal_snippet_abilities(user, subject) when Group then group_abilities(user, subject) when Namespace then namespace_abilities(user, subject) when GroupMember then group_member_abilities(user, subject) @@ -87,11 +86,7 @@ class Ability # List of possible abilities for anonymous user def anonymous_abilities(subject) - if subject.is_a?(PersonalSnippet) - anonymous_personal_snippet_abilities(subject) - elsif subject.is_a?(ProjectSnippet) - anonymous_project_snippet_abilities(subject) - elsif subject.respond_to?(:project) + if subject.respond_to?(:project) ProjectPolicy.abilities(nil, subject.project) elsif subject.is_a?(Group) || subject.respond_to?(:group) anonymous_group_abilities(subject) @@ -153,14 +148,6 @@ class Ability rules end - def anonymous_personal_snippet_abilities(snippet) - if snippet.public? - [:read_personal_snippet] - else - [] - end - end - def anonymous_user_abilities [:read_user] unless restricted_public_level? end @@ -232,24 +219,6 @@ class Ability rules.flatten end - def personal_snippet_abilities(user, snippet) - rules = [] - - if snippet.author == user - rules += [ - :read_personal_snippet, - :update_personal_snippet, - :admin_personal_snippet - ] - end - - if snippet.public? || (snippet.internal? && !user.external?) - rules << :read_personal_snippet - end - - rules - end - def group_member_abilities(user, subject) rules = [] target_user = subject.user -- cgit v1.2.1 From ccfa032ebc101339c1c0842d0fbeb5b555db9278 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 16:28:47 -0700 Subject: port groups --- app/models/ability.rb | 39 +++----------------------------------- app/policies/group_policy.rb | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 36 deletions(-) create mode 100644 app/policies/group_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index c5392379b32..2360bf3d46c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -73,7 +73,6 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject - when Group then group_abilities(user, subject) when Namespace then namespace_abilities(user, subject) when GroupMember then group_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject) @@ -88,8 +87,8 @@ class Ability def anonymous_abilities(subject) if subject.respond_to?(:project) ProjectPolicy.abilities(nil, subject.project) - elsif subject.is_a?(Group) || subject.respond_to?(:group) - anonymous_group_abilities(subject) + elsif subject.respond_to?(:group) + GroupPolicy.abilities(nil, subject.group) elsif subject.is_a?(User) anonymous_user_abilities else @@ -164,38 +163,6 @@ class Ability ProjectPolicy.abilities(user, project).to_a end - def group_abilities(user, group) - rules = [] - rules << :read_group if can_read_group?(user, group) - - owner = user.admin? || group.has_owner?(user) - master = owner || group.has_master?(user) - - # Only group masters and group owners can create new projects - if master - rules += [ - :create_projects, - :admin_milestones - ] - end - - # Only group owner and administrators can admin group - if owner - rules += [ - :admin_group, - :admin_namespace, - :admin_group_member, - :change_visibility_level - ] - end - - if group.public? || (group.internal? && !user.external?) - rules << :request_access if group.request_access_enabled && group.users.exclude?(user) - end - - rules.flatten - end - def can_read_group?(user, group) return true if user.admin? return true if group.public? @@ -225,7 +192,7 @@ class Ability group = subject.group unless group.last_owner?(target_user) - can_manage = group_abilities(user, group).include?(:admin_group_member) + can_manage = allowed?(user, :admin_group_member, group) if can_manage rules << :update_group_member diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb new file mode 100644 index 00000000000..97ff6233968 --- /dev/null +++ b/app/policies/group_policy.rb @@ -0,0 +1,45 @@ +class GroupPolicy < BasePolicy + def rules + can! :read_group if @subject.public? + return unless @user + + globally_viewable = @subject.public? || (@subject.internal? && !@user.external?) + member = @subject.users.include?(@user) + owner = @user.admin? || @subject.has_owner?(@user) + master = owner || @subject.has_master?(@user) + + can_read = false + can_read ||= globally_viewable + can_read ||= member + can_read ||= @user.admin? + can_read ||= GroupProjectsFinder.new(@subject).execute(@user).any? + can! :read_group if can_read + + # Only group masters and group owners can create new projects + if master + can! :create_projects + can! :admin_milestones + end + + # Only group owner and administrators can admin group + if owner + can! :admin_group + can! :admin_namespace + can! :admin_group_member + can! :change_visibility_level + end + + if globally_viewable && @subject.request_access_enabled && !member + can! :request_access + end + end + + def can_read_group? + return true if @subject.public? + return true if @user.admin? + return true if @subject.internal? && !@user.external? + return true if @subject.users.include?(@user) + + GroupProjectsFinder.new(@subject).execute(@user).any? + end +end -- cgit v1.2.1 From 2944022835d872b472d8691082ef67aa3057d2b4 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 16:29:10 -0700 Subject: trim more dead code --- app/models/ability.rb | 53 +-------------------------------------------------- 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 2360bf3d46c..794fb1223e3 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -96,57 +96,6 @@ class Ability end end - def anonymous_project_abilities(subject) - project = if subject.is_a?(Project) - subject - else - subject.project - end - - if project && project.public? - rules = [ - :read_project, - :read_board, - :read_list, - :read_wiki, - :read_label, - :read_milestone, - :read_project_snippet, - :read_project_member, - :read_merge_request, - :read_note, - :read_pipeline, - :read_commit_status, - :read_container_image, - :download_code - ] - - # Allow to read builds by anonymous user if guests are allowed - rules << :read_build if project.public_builds? - - # Allow to read issues by anonymous user if issue is not confidential - rules << :read_issue unless subject.is_a?(Issue) && subject.confidential? - - rules - project_disabled_features_rules(project) - else - [] - end - end - - def anonymous_group_abilities(subject) - rules = [] - - group = if subject.is_a?(Group) - subject - else - subject.group - end - - rules << :read_group if group.public? - - rules - end - def anonymous_user_abilities [:read_user] unless restricted_public_level? end @@ -211,7 +160,7 @@ class Ability project = subject.project unless target_user == project.owner - can_manage = project_abilities(user, project).include?(:admin_project_member) + can_manage = allowed?(user, :admin_project_member, project) if can_manage rules << :update_project_member -- cgit v1.2.1 From 9a0ea1350131368b9b723f1a9581bbfffe7c43f8 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 16:29:19 -0700 Subject: factor in global permissions --- app/policies/base_policy.rb | 4 ++-- app/policies/global_policy.rb | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 app/policies/global_policy.rb diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 12f60d8f76e..5a5b99c81c8 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -4,6 +4,8 @@ class BasePolicy end def self.class_for(subject) + return GlobalPolicy if subject.nil? + subject.class.ancestors.each do |klass| next unless klass.name @@ -59,8 +61,6 @@ class BasePolicy private def collect_rules(&b) - return Set.new if @subject.nil? - @can = Set.new @cannot = Set.new yield diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb new file mode 100644 index 00000000000..94a2906444a --- /dev/null +++ b/app/policies/global_policy.rb @@ -0,0 +1,7 @@ +class GlobalPolicy < BasePolicy + def rules + return unless @user + can! :create_group if @user.can_create_group + can! :read_users_list + end +end -- cgit v1.2.1 From 29059c2e9c7be418d2a99a136934c6d9cca5fccd Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 16 Aug 2016 16:46:35 -0700 Subject: add personal snippets and project members --- app/policies/personal_snippet_policy.rb | 16 ++++++++++++++++ app/policies/project_member_policy.rb | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 app/policies/personal_snippet_policy.rb create mode 100644 app/policies/project_member_policy.rb diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb new file mode 100644 index 00000000000..46c5aa1a5be --- /dev/null +++ b/app/policies/personal_snippet_policy.rb @@ -0,0 +1,16 @@ +class PersonalSnippetPolicy < BasePolicy + def rules + can! :read_personal_snippet if @subject.public? + return unless @user + + if @subject.author == @user + can! :read_personal_snippet + can! :update_personal_snippet + can! :admin_personal_snippet + end + + if @subject.internal? && !@user.external? + can! :read_personal_snippet + end + end +end diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb new file mode 100644 index 00000000000..1c038dddd4b --- /dev/null +++ b/app/policies/project_member_policy.rb @@ -0,0 +1,22 @@ +class ProjectMemberPolicy < BasePolicy + def rules + # anonymous users have no abilities here + return unless @user + + target_user = @subject.user + project = @subject.project + + return if target_user == project.owner + + can_manage = Ability.allowed?(@user, :admin_project_member, project) + + if can_manage + can! :update_project_member + can! :destroy_project_member + end + + if @user == target_user + can! :destroy_project_member + end + end +end -- cgit v1.2.1 From 5019185edd7718b262eb5ae94f21763f230f0557 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 18 Aug 2016 09:52:35 -0700 Subject: port runners, namespaces, group/project_members --- app/models/ability.rb | 58 ------------------------------------- app/policies/ci/runner_policy.rb | 13 +++++++++ app/policies/group_member_policy.rb | 19 ++++++++++++ app/policies/namespace_policy.rb | 10 +++++++ 4 files changed, 42 insertions(+), 58 deletions(-) create mode 100644 app/policies/ci/runner_policy.rb create mode 100644 app/policies/group_member_policy.rb create mode 100644 app/policies/namespace_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index 794fb1223e3..7c4210f0706 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -73,12 +73,8 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject - when Namespace then namespace_abilities(user, subject) - when GroupMember then group_member_abilities(user, subject) - when ProjectMember then project_member_abilities(user, subject) when User then user_abilities when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) - when Ci::Runner then runner_abilities(user, subject) else [] end + global_abilities(user) end @@ -112,48 +108,6 @@ class Ability ProjectPolicy.abilities(user, project).to_a end - def can_read_group?(user, group) - return true if user.admin? - return true if group.public? - return true if group.internal? && !user.external? - return true if group.users.include?(user) - - GroupProjectsFinder.new(group).execute(user).any? - end - - def namespace_abilities(user, namespace) - rules = [] - - # Only namespace owner and administrators can admin it - if namespace.owner == user || user.admin? - rules += [ - :create_projects, - :admin_namespace - ] - end - - rules.flatten - end - - def group_member_abilities(user, subject) - rules = [] - target_user = subject.user - group = subject.group - - unless group.last_owner?(target_user) - can_manage = allowed?(user, :admin_group_member, group) - - if can_manage - rules << :update_group_member - rules << :destroy_group_member - elsif user == target_user - rules << :destroy_group_member - end - end - - rules - end - def project_member_abilities(user, subject) rules = [] target_user = subject.user @@ -182,18 +136,6 @@ class Ability rules end - def runner_abilities(user, runner) - if user.is_admin? - [:assign_runner] - elsif runner.is_shared? || runner.locked? - [] - elsif user.ci_authorized_runners.include?(runner) - [:assign_runner] - else - [] - end - end - def user_abilities [:read_user] end diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb new file mode 100644 index 00000000000..7edd383530d --- /dev/null +++ b/app/policies/ci/runner_policy.rb @@ -0,0 +1,13 @@ +module Ci + class RunnerPolicy < BasePolicy + def rules + return unless @user + + can! :assign_runner if @user.is_admin? + + return if @subject.is_shared? || @subject.locked? + + can! :assign_runner if @user.ci_authorized_runners.include?(@subject) + end + end +end diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb new file mode 100644 index 00000000000..62335527654 --- /dev/null +++ b/app/policies/group_member_policy.rb @@ -0,0 +1,19 @@ +class GroupMemberPolicy < BasePolicy + def rules + return unless @user + + target_user = @subject.user + group = @subject.group + + return if group.last_owner?(target_user) + + can_manage = Ability.allowed?(@user, :admin_group_member, group) + + if can_manage + can! :update_group_member + can! :destroy_group_member + elsif @user == target_user + can! :destroy_group_member + end + end +end diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb new file mode 100644 index 00000000000..29bb357e00a --- /dev/null +++ b/app/policies/namespace_policy.rb @@ -0,0 +1,10 @@ +class NamespacePolicy < BasePolicy + def rules + return unless @user + + if @subject.owner == @user || @user.admin? + can! :create_projects + can! :admin_namespace + end + end +end -- cgit v1.2.1 From a340829c42617b40696408c3097d6476970e8b87 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 18 Aug 2016 09:59:17 -0700 Subject: port UserPolicy --- app/models/ability.rb | 11 ----------- app/policies/user_policy.rb | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 app/policies/user_policy.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index 7c4210f0706..fe171cd1a8b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -73,7 +73,6 @@ class Ability def abilities_by_subject_class(user:, subject:) case subject - when User then user_abilities when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) else [] end + global_abilities(user) @@ -85,17 +84,11 @@ class Ability ProjectPolicy.abilities(nil, subject.project) elsif subject.respond_to?(:group) GroupPolicy.abilities(nil, subject.group) - elsif subject.is_a?(User) - anonymous_user_abilities else [] end end - def anonymous_user_abilities - [:read_user] unless restricted_public_level? - end - def global_abilities(user) rules = [] rules << :create_group if user.can_create_group @@ -136,10 +129,6 @@ class Ability rules end - def user_abilities - [:read_user] - end - def restricted_public_level? current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 00000000000..03a2499e263 --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,11 @@ +class UserPolicy < BasePolicy + include Gitlab::CurrentSettings + + def rules + can! :read_user if @user || !restricted_public_level? + end + + def restricted_public_level? + current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) + end +end -- cgit v1.2.1 From 5b7edc74b65f6855d3744ba600f3972c8cbb5894 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 18 Aug 2016 10:38:25 -0700 Subject: use the cached abilities in #delegate! --- app/policies/base_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 5a5b99c81c8..6d38e2eaa73 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -43,7 +43,7 @@ class BasePolicy end def delegate!(new_subject) - @can.merge(BasePolicy.class_for(new_subject).abilities(@user, new_subject)) + @can.merge(Ability.allowed(@user, new_subject)) end def can?(rule) -- cgit v1.2.1 From 06ba2602c59e5f6627d892ed9fdb2dafade5768b Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 18 Aug 2016 10:39:49 -0700 Subject: take the dive - only use abilities from Policies --- app/models/ability.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index fe171cd1a8b..b57ada715df 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -61,14 +61,7 @@ class Ability private def uncached_allowed(user, subject) - policy_class = BasePolicy.class_for(subject) rescue nil - return policy_class.abilities(user, subject) if policy_class - - return anonymous_abilities(subject) if user.nil? - return [] unless user.is_a?(User) - return [] if user.blocked? - - abilities_by_subject_class(user: user, subject: subject) + BasePolicy.class_for(subject).abilities(user, subject) end def abilities_by_subject_class(user:, subject:) -- cgit v1.2.1 From 2b26270ab7c489ebde4aac835f8e7307dc7a7441 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 18 Aug 2016 10:40:39 -0700 Subject: add Deployment, Environment, and ExternalIssue policies --- app/policies/deployment_policy.rb | 5 +++++ app/policies/environment_policy.rb | 5 +++++ app/policies/external_issue_policy.rb | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 app/policies/deployment_policy.rb create mode 100644 app/policies/environment_policy.rb create mode 100644 app/policies/external_issue_policy.rb diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb new file mode 100644 index 00000000000..163d070ff90 --- /dev/null +++ b/app/policies/deployment_policy.rb @@ -0,0 +1,5 @@ +class DeploymentPolicy < BasePolicy + def rules + delegate! @subject.project + end +end diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb new file mode 100644 index 00000000000..f4219569161 --- /dev/null +++ b/app/policies/environment_policy.rb @@ -0,0 +1,5 @@ +class EnvironmentPolicy < BasePolicy + def rules + delegate! @subject.project + end +end diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb new file mode 100644 index 00000000000..d9e28bd107a --- /dev/null +++ b/app/policies/external_issue_policy.rb @@ -0,0 +1,5 @@ +class ExternalIssuePolicy < BasePolicy + def rules + delegate! @subject.project + end +end -- cgit v1.2.1 From 2bdcef4d672121a387fca6da720d333dda8f7af6 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Thu, 18 Aug 2016 16:12:32 -0700 Subject: use a nil subject when we want to check global abilities --- lib/api/groups.rb | 2 +- lib/api/helpers.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 9d8b8d737a9..f981ec0dbfe 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -30,7 +30,7 @@ module API # Example Request: # POST /groups post do - authorize! :create_group, current_user + authorize! :create_group required_attributes! [:name, :path] attrs = attributes_for_keys [:name, :path, :description, :visibility_level] diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index fdb70af694d..6a20ba95a79 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -129,7 +129,7 @@ module API forbidden! unless current_user.is_admin? end - def authorize!(action, subject) + def authorize!(action, subject = nil) forbidden! unless can?(current_user, action, subject) end -- cgit v1.2.1 From 6070145bebad0a8284b4fe4bb7a1e2b97f03ab1b Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 22 Aug 2016 17:45:15 -0700 Subject: test if we can :read_group the group, not the namespace --- app/controllers/namespaces_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb index 5a94dcb0dbd..83eec1bf4a2 100644 --- a/app/controllers/namespaces_controller.rb +++ b/app/controllers/namespaces_controller.rb @@ -14,7 +14,7 @@ class NamespacesController < ApplicationController if user redirect_to user_path(user) - elsif group && can?(current_user, :read_group, namespace) + elsif group && can?(current_user, :read_group, group) redirect_to group_path(group) elsif current_user.nil? authenticate_user! -- cgit v1.2.1 From 35779223a69c22806bbb48d70086c7fb9a23f513 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Mon, 22 Aug 2016 17:45:31 -0700 Subject: special-case blocked users --- app/policies/base_policy.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 6d38e2eaa73..a6fd9786ae7 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -30,6 +30,7 @@ class BasePolicy end def abilities + return [] if @user && @user.blocked? return anonymous_abilities if @user.nil? collect_rules { rules } end -- cgit v1.2.1 From b3b7fb1fe7b876487b1464aa5779bacec7276742 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 23 Aug 2016 17:56:41 -0700 Subject: remove the rest of the dead code --- app/models/ability.rb | 74 --------------------------------------------------- 1 file changed, 74 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index b57ada715df..8ccbb9bee9c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -63,79 +63,5 @@ class Ability def uncached_allowed(user, subject) BasePolicy.class_for(subject).abilities(user, subject) end - - def abilities_by_subject_class(user:, subject:) - case subject - when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) - else [] - end + global_abilities(user) - end - - # List of possible abilities for anonymous user - def anonymous_abilities(subject) - if subject.respond_to?(:project) - ProjectPolicy.abilities(nil, subject.project) - elsif subject.respond_to?(:group) - GroupPolicy.abilities(nil, subject.group) - else - [] - end - end - - def global_abilities(user) - rules = [] - rules << :create_group if user.can_create_group - rules << :read_users_list - rules - end - - def project_abilities(user, project) - # temporary patch, deleteme before merge - ProjectPolicy.abilities(user, project).to_a - end - - def project_member_abilities(user, subject) - rules = [] - target_user = subject.user - project = subject.project - - unless target_user == project.owner - can_manage = allowed?(user, :admin_project_member, project) - - if can_manage - rules << :update_project_member - rules << :destroy_project_member - elsif user == target_user - rules << :destroy_project_member - end - end - - rules - end - - def filter_build_abilities(rules) - # If we can't read build we should also not have that - # ability when looking at this in context of commit_status - %w(read create update admin).each do |rule| - rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build") - end - rules - end - - def restricted_public_level? - current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) - end - - def filter_confidential_issues_abilities(user, issue, rules) - return rules if user.admin? || !issue.confidential? - - unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER) - rules.delete(:admin_issue) - rules.delete(:read_issue) - rules.delete(:update_issue) - end - - rules - end end end -- cgit v1.2.1 From 57def53c84091a56f3a2443d214fe80f2c026d00 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 11:09:21 -0700 Subject: factor out a RuleSet so that `delegate!` retains @cannot --- app/models/ability.rb | 2 +- app/policies/base_policy.rb | 58 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 8ccbb9bee9c..fa8f8bc3a5f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -55,7 +55,7 @@ class Ability user_key = user ? user.id : 'anonymous' subject_key = subject ? "#{subject.class.name}/#{subject.id}" : 'global' key = "/ability/#{user_key}/#{subject_key}" - RequestStore[key] ||= Set.new(uncached_allowed(user, subject)).freeze + RequestStore[key] ||= uncached_allowed(user, subject).freeze end private diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index a6fd9786ae7..6a1a7d75ee6 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -1,4 +1,47 @@ class BasePolicy + class RuleSet + attr_reader :can_set, :cannot_set + def initialize(can_set, cannot_set) + @can_set = can_set + @cannot_set = cannot_set + end + + def self.empty + new(Set.new, Set.new) + end + + def can?(ability) + @can_set.include?(ability) && !@cannot_set.include?(ability) + end + + def include?(ability) + can?(ability) + end + + def to_set + @can_set - @cannot_set + end + + def merge(other) + @can_set.merge(other.can_set) + @cannot_set.merge(other.cannot_set) + end + + def can!(*abilities) + @can_set.merge(abilities) + end + + def cannot!(*abilities) + @cannot_set.merge(abilities) + end + + def freeze + @can_set.freeze + @cannot_set.freeze + super + end + end + def self.abilities(user, subject) new(user, subject).abilities end @@ -30,7 +73,7 @@ class BasePolicy end def abilities - return [] if @user && @user.blocked? + return RuleSet.empty if @user && @user.blocked? return anonymous_abilities if @user.nil? collect_rules { rules } end @@ -44,27 +87,26 @@ class BasePolicy end def delegate!(new_subject) - @can.merge(Ability.allowed(@user, new_subject)) + @rule_set.merge(Ability.allowed(@user, new_subject)) end def can?(rule) - @can.include?(rule) && !@cannot.include?(rule) + @rule_set.can?(rule) end def can!(*rules) - @can.merge(rules) + @rule_set.can!(*rules) end def cannot!(*rules) - @cannot.merge(rules) + @rule_set.cannot!(*rules) end private def collect_rules(&b) - @can = Set.new - @cannot = Set.new + @rule_set = RuleSet.empty yield - @can - @cannot + @rule_set end end -- cgit v1.2.1 From 71765536d0c29e64eb24ce50da9d5fdfc63f9e78 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 11:10:33 -0700 Subject: move the rules method to the top #cosmetic --- app/policies/project_policy.rb | 54 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 4380b00d962..8a1148dece4 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -1,4 +1,31 @@ class ProjectPolicy < BasePolicy + def rules + team_access!(user) + + owner = user.admin? || + project.owner == user || + (project.group && project.group.has_owner?(user)) + + owner_access! if owner + + if project.public? || (project.internal? && !user.external?) + guest_access! + public_access! + + # Allow to read builds for internal projects + can! :read_build if project.public_builds? + + if project.request_access_enabled && + !(owner || project.team.member?(user) || project_group_member?(user)) + can! :request_access + end + end + + archived_access! if project.archived? + + disabled_features! + end + def project @subject end @@ -158,33 +185,6 @@ class ProjectPolicy < BasePolicy end end - def rules - team_access!(user) - - owner = user.admin? || - project.owner == user || - (project.group && project.group.has_owner?(user)) - - owner_access! if owner - - if project.public? || (project.internal? && !user.external?) - guest_access! - public_access! - - # Allow to read builds for internal projects - can! :read_build if project.public_builds? - - if project.request_access_enabled && - !(owner || project.team.member?(user) || project_group_member?(user)) - can! :request_access - end - end - - archived_access! if project.archived? - - disabled_features! - end - def anonymous_rules return unless project.public? -- cgit v1.2.1 From d7bd20099bb9df677cf272a5b211fbe9c330c619 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 11:12:09 -0700 Subject: use a more compact style for access policies --- app/policies/project_policy.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 8a1148dece4..54f5f95cd65 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -130,17 +130,10 @@ class ProjectPolicy < BasePolicy def team_access!(user) access = project.team.max_member_access(user.id) - return if access < Gitlab::Access::GUEST - guest_access! - - return if access < Gitlab::Access::REPORTER - reporter_access! - - return if access < Gitlab::Access::DEVELOPER - developer_access! - - return if access < Gitlab::Access::MASTER - master_access! + guest_access! if access >= Gitlab::Access::GUEST + reporter_access! if access >= Gitlab::Access::REPORTER + developer_access! if access >= Gitlab::Access::DEVELOPER + master_access! if access >= Gitlab::Access::MASTER end def archived_access! -- cgit v1.2.1 From fb2979260998a1b7f35d688ef788d34099322b84 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 11:13:29 -0700 Subject: use || in place of `or` --- app/policies/project_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 54f5f95cd65..0e933d00904 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -153,7 +153,7 @@ class ProjectPolicy < BasePolicy cannot!(*named_abilities(:merge_request)) end - unless project.issues_enabled or project.merge_requests_enabled + unless project.issues_enabled || project.merge_requests_enabled cannot!(*named_abilities(:label)) cannot!(*named_abilities(:milestone)) end -- cgit v1.2.1 From b7d300001319e38ddb6e57764d51d5d838689ecd Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 11:14:07 -0700 Subject: line break after guard clause --- app/policies/global_policy.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 94a2906444a..3c2fbe6b56b 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -1,6 +1,7 @@ class GlobalPolicy < BasePolicy def rules return unless @user + can! :create_group if @user.can_create_group can! :read_users_list end -- cgit v1.2.1 From 482795a90830a4a74b675ef7afc266ca292f6655 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 11:42:23 -0700 Subject: implement RuleSet#size for tests --- app/policies/base_policy.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 6a1a7d75ee6..cc82793b716 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -6,6 +6,10 @@ class BasePolicy @cannot_set = cannot_set end + def size + to_set.size + end + def self.empty new(Set.new, Set.new) end -- cgit v1.2.1 From 78eabebedc2cb849dd95e5e7e9dff9f1d24f5ebe Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 12:34:28 -0700 Subject: don't use a deprecated api in ability_spec --- spec/models/ability_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index c9e6a334c67..b05510342bc 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -222,12 +222,12 @@ describe Ability, lib: true do describe '.project_disabled_features_rules' do let(:project) { build(:project) } - subject { described_class.project_disabled_features_rules(project) } + subject { described_class.allowed(project.owner, project) } context 'wiki named abilities' do it 'disables wiki abilities if the project has no wiki' do expect(project).to receive(:has_wiki?).and_return(false) - expect(subject).to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki) + expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki) end end end -- cgit v1.2.1 From 29f818e6165e2e6d4a523270d115a491e261478a Mon Sep 17 00:00:00 2001 From: barthc Date: Tue, 30 Aug 2016 20:57:47 +0100 Subject: prevent authored awardable thumbs votes --- app/controllers/concerns/toggle_award_emoji.rb | 12 +++++++----- app/models/concerns/awardable.rb | 7 +++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index b343bb611e0..172d5344b7a 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -8,12 +8,14 @@ module ToggleAwardEmoji def toggle_award_emoji name = params.require(:name) - return render json: { ok: false } unless awardable.user_can_award?(current_user, name) + if awardable.user_can_award?(current_user, name) + awardable.toggle_award_emoji(name, current_user) + TodoService.new.new_award_emoji(to_todoable(awardable), current_user) - awardable.toggle_award_emoji(name, current_user) - TodoService.new.new_award_emoji(to_todoable(awardable), current_user) - - render json: { ok: true } + render json: { ok: true } + else + render json: { ok: false } + end end private diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index e25420c0edf..83f5bc1fa9e 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -64,8 +64,11 @@ module Awardable end def user_can_award?(current_user, name) - name = normalize_name(name) - !(self.user_authored?(current_user) && awardable_votes?(name)) + if user_authored?(current_user) + !awardable_votes?(normalize_name(name)) + else + true + end end def awarded_emoji?(emoji_name, current_user) -- cgit v1.2.1 From 400b265ce2a5473d46abcd33fc31cfd5958573cf Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 30 Aug 2016 15:31:58 -0500 Subject: Resize created icon --- app/assets/stylesheets/pages/pipelines.scss | 7 +++++++ app/views/shared/icons/_icon_status_created.svg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 0dcf61dd2dd..2d6653cd867 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -477,3 +477,10 @@ width: 60px; } } + +.ci-status-icon-created { + + svg { + fill: $table-text-gray; + } +} diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg index 4a08fd65860..1f5c3b51b03 100644 --- a/app/views/shared/icons/_icon_status_created.svg +++ b/app/views/shared/icons/_icon_status_created.svg @@ -1 +1 @@ - + -- cgit v1.2.1 From a476e6f5e53c54bcc74a482f0695564713da7dd0 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 30 Aug 2016 22:44:11 +0300 Subject: Remove default value for lock_version --- .../20160827011312_ensure_lock_version_has_no_default.rb | 16 ++++++++++++++++ db/schema.rb | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160827011312_ensure_lock_version_has_no_default.rb diff --git a/db/migrate/20160827011312_ensure_lock_version_has_no_default.rb b/db/migrate/20160827011312_ensure_lock_version_has_no_default.rb new file mode 100644 index 00000000000..7c55bc23cf2 --- /dev/null +++ b/db/migrate/20160827011312_ensure_lock_version_has_no_default.rb @@ -0,0 +1,16 @@ +class EnsureLockVersionHasNoDefault < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + change_column_default :issues, :lock_version, nil + change_column_default :merge_requests, :lock_version, nil + + execute('UPDATE issues SET lock_version = 1 WHERE lock_version = 0') + execute('UPDATE merge_requests SET lock_version = 1 WHERE lock_version = 0') + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index 227e10294e4..0cd8648da2e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160824103857) do +ActiveRecord::Schema.define(version: 20160827011312) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 9d8fbcc03847820eeda61e9d765693161f3619c5 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 24 Aug 2016 17:08:23 -0500 Subject: Added project specific enable/disable setting for LFS --- app/controllers/projects_controller.rb | 2 +- app/helpers/lfs_helper.rb | 4 + app/helpers/projects_helper.rb | 12 +++ app/models/project.rb | 4 + app/views/admin/projects/show.html.haml | 5 + app/views/projects/edit.html.haml | 8 ++ .../20160823213309_add_enable_lfs_to_projects.rb | 29 ++++++ db/schema.rb | 1 + spec/requests/lfs_http_spec.rb | 107 +++++++++++++++++++++ 9 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160823213309_add_enable_lfs_to_projects.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index fc52cd2f367..678b56b5d9b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -309,7 +309,7 @@ class ProjectsController < Projects::ApplicationController :issues_tracker_id, :default_branch, :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled + :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :enable_lfs ) end diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index eb651e3687e..5d82abfca79 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -23,10 +23,14 @@ module LfsHelper end def lfs_download_access? + return false unless project.lfs_enabled? + project.public? || ci? || (user && user.can?(:download_code, project)) end def lfs_upload_access? + return false unless project.lfs_enabled? + user && user.can?(:push_code, project) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 356f27f2d5d..a5ae9f8668e 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -187,6 +187,18 @@ module ProjectsHelper nav_tabs.flatten end + def project_lfs_status(project) + if project.lfs_enabled? + content_tag(:span, class: 'vs-private') do + 'Enabled' + end + else + content_tag(:span, class: 'vs-internal') do + 'Disabled' + end + end + end + def git_user_name if current_user current_user.name diff --git a/app/models/project.rb b/app/models/project.rb index c34064f96ce..c271448946c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -390,6 +390,10 @@ class Project < ActiveRecord::Base end end + def lfs_enabled? + (Gitlab.config.lfs.enabled && enable_lfs) || (enable_lfs.nil? && Gitlab.config.lfs.enabled) + end + def repository_storage_path Gitlab.config.repositories.storages[repository_storage] end diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index b2c607361b3..f65322cc12f 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -73,6 +73,11 @@ %span.light last commit: %strong = last_commit(@project) + + %li + %span.light LFS status: + %strong + = project_lfs_status(@project) - else %li %span.light repository: diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index b282aa52b25..8aa2db197a3 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -80,6 +80,14 @@ %strong Snippets %br %span.descr Share code pastes with others out of git repository + - if Gitlab.config.lfs.enabled && current_user.admin? + .form-group + .checkbox + = f.label :enable_lfs do + = f.check_box :enable_lfs, checked: (true if @project.enable_lfs || @project.enable_lfs.nil?) + %strong LFS + %br + %span.descr Git Large File Storage - if Gitlab.config.registry.enabled .form-group .checkbox diff --git a/db/migrate/20160823213309_add_enable_lfs_to_projects.rb b/db/migrate/20160823213309_add_enable_lfs_to_projects.rb new file mode 100644 index 00000000000..9df1a5078fa --- /dev/null +++ b/db/migrate/20160823213309_add_enable_lfs_to_projects.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddEnableLfsToProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :projects, :enable_lfs, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 227e10294e4..28711294746 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -825,6 +825,7 @@ ActiveRecord::Schema.define(version: 20160824103857) do t.string "repository_storage", default: "default", null: false t.boolean "request_access_enabled", default: true, null: false t.boolean "has_external_wiki" + t.boolean "enable_lfs" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 4c9b4a8ba42..2d39f3808d5 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -44,6 +44,113 @@ describe 'Git LFS API and storage' do end end + context 'project specific LFS settings' do + let(:project) { create(:empty_project) } + let(:body) do + { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ], + 'operation' => 'upload' + } + end + let(:authorization) { authorize_user } + + context 'with LFS disabled globally' do + before do + project.team << [user, :master] + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + end + + describe 'LFS disabled in project' do + before do + project.update_attribute(:enable_lfs, false) + end + + it 'responds with a 501 message on upload' do + post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers + + expect(response).to have_http_status(501) + end + + it 'responds with a 501 message on download' do + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + + expect(response).to have_http_status(501) + end + end + + describe 'LFS enabled in project' do + before do + project.update_attribute(:enable_lfs, true) + end + + it 'responds with a 501 message on upload' do + post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers + + expect(response).to have_http_status(501) + end + + it 'responds with a 501 message on download' do + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + + expect(response).to have_http_status(501) + end + end + end + + context 'with LFS enabled globally' do + before do + project.team << [user, :master] + enable_lfs + end + + describe 'LFS disabled in project' do + before do + project.update_attribute(:enable_lfs, false) + end + + it 'responds with a 403 message on upload' do + post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers + + expect(response).to have_http_status(403) + expect(json_response).to include('message' => 'Access forbidden. Check your access level.') + end + + it 'responds with a 403 message on download' do + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + + expect(response).to have_http_status(403) + expect(json_response).to include('message' => 'Access forbidden. Check your access level.') + end + end + + describe 'LFS enabled in project' do + before do + project.update_attribute(:enable_lfs, true) + end + + it 'responds with a 200 message on upload' do + post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers + + expect(response).to have_http_status(200) + expect(json_response['objects'].first['size']).to eq(1575078) + end + + it 'responds with a 200 message on download' do + get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers + + expect(response).to have_http_status(200) + end + end + end + end + describe 'deprecated API' do let(:project) { create(:empty_project) } -- cgit v1.2.1 From 0227e98d0db2eb7fc6a35ddfcd3a0581ab550948 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 24 Aug 2016 17:36:58 -0500 Subject: Added CHANGELOG, documentation, and API functionality --- CHANGELOG | 1 + doc/api/projects.md | 3 +++ doc/workflow/project_features.md | 8 ++++++++ lib/api/entities.rb | 2 +- lib/api/projects.rb | 8 ++++++-- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 16fefd63d41..2d0cbd2cfd8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,7 @@ v 8.12.0 (unreleased) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Fix markdown help references (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps) + - Added project specific enable/disable setting for LFS !5997 - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Add delimiter to project stars and forks count (ClemMakesApps) diff --git a/doc/api/projects.md b/doc/api/projects.md index 3136c493b48..671b4ba7a7a 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -452,6 +452,7 @@ Parameters: - `import_url` (optional) - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) +- `enable_lfs` (optional) ### Create project for user @@ -478,6 +479,7 @@ Parameters: - `import_url` (optional) - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) +- `enable_lfs` (optional) ### Edit project @@ -505,6 +507,7 @@ Parameters: - `visibility_level` (optional) - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) +- `enable_lfs` (optional) On success, method returns 200 with the updated project. If parameters are invalid, 400 is returned. diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md index a523b3facbe..6790c06f325 100644 --- a/doc/workflow/project_features.md +++ b/doc/workflow/project_features.md @@ -33,3 +33,11 @@ Snippets are little bits of code or text. This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control. For example, a specific config file that is used by > the team that is only valid for the people that work on the code. + +## LFS + +>**Note:** Project specific LFS setting was added on 8.12 and is available only to admins. + +Git Large File Storage allows you to easily manage large binary files with Git. +With this setting admins can keep better control of which projects are allowed +to use LFS, thus allowing for better storage usage control. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cbb324dd06d..61fcccf2959 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -78,7 +78,7 @@ module API expose :path, :path_with_namespace expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled expose :created_at, :last_activity_at - expose :shared_runners_enabled + expose :shared_runners_enabled, :enable_lfs expose :creator_id expose :namespace expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 71efd4f33ca..d98fb2611ff 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -105,6 +105,7 @@ module API # visibility_level (optional) - 0 by default # import_url (optional) # public_builds (optional) + # enable_lfs (optional) # Example Request # POST /projects post do @@ -124,7 +125,8 @@ module API :visibility_level, :import_url, :public_builds, - :only_allow_merge_if_build_succeeds] + :only_allow_merge_if_build_succeeds, + :enable_lfs] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(current_user, attrs).execute if @project.saved? @@ -220,6 +222,7 @@ module API # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) - visibility level of a project # public_builds (optional) + # enable_lfs (optional) # Example Request # PUT /projects/:id put ':id' do @@ -237,7 +240,8 @@ module API :public, :visibility_level, :public_builds, - :only_allow_merge_if_build_succeeds] + :only_allow_merge_if_build_succeeds, + :enable_lfs] attrs = map_public_to_visibility_level(attrs) authorize_admin_project authorize! :rename_project, user_project if attrs[:name].present? -- cgit v1.2.1 From 63a97c11928d483cfad87d11f83c7df84c29743d Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 25 Aug 2016 17:59:31 -0500 Subject: Syntax and style fixes. --- app/models/project.rb | 5 ++++- app/views/projects/edit.html.haml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index c271448946c..7a5933bfe5e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -391,7 +391,10 @@ class Project < ActiveRecord::Base end def lfs_enabled? - (Gitlab.config.lfs.enabled && enable_lfs) || (enable_lfs.nil? && Gitlab.config.lfs.enabled) + return false unless Gitlab.config.lfs.enabled + return Gitlab.config.lfs.enabled if enable_lfs.nil? + + enable_lfs end def repository_storage_path diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 8aa2db197a3..0c5ce193240 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -84,7 +84,7 @@ .form-group .checkbox = f.label :enable_lfs do - = f.check_box :enable_lfs, checked: (true if @project.enable_lfs || @project.enable_lfs.nil?) + = f.check_box :enable_lfs, checked: @project.lfs_enabled? %strong LFS %br %span.descr Git Large File Storage -- cgit v1.2.1 From 03d9e2458845c35f3912fca5b1585d1346920453 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 30 Aug 2016 14:26:14 -0700 Subject: Fix CHANGELOG Remove duplicate 8.11.4 entries and mark 8.11.3 as released. [ci skip] --- CHANGELOG | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 16fefd63d41..c07e194358d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -51,14 +51,10 @@ v 8.12.0 (unreleased) v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Creating an issue through our API now emails label subscribers !5720 - -v 8.11.4 (unreleased) - Fix resolving conflicts on forks - -v 8.11.4 (unreleased) - Fix diff commenting on merge requests created prior to 8.10 -v 8.11.3 (unreleased) +v 8.11.3 - Do not enforce using hash with hidden key in CI configuration. !6079 - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label -- cgit v1.2.1 From cf37d623e197dae5cc7efb021c1b1d85ca9674ee Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 30 Aug 2016 17:17:45 -0500 Subject: Renamed `enable_lfs` to `lfs_enabled` for the Project field, and related fixes. --- app/assets/stylesheets/pages/projects.scss | 8 ++++++ app/controllers/projects_controller.rb | 2 +- app/helpers/projects_helper.rb | 4 +-- app/models/project.rb | 4 +-- app/views/admin/projects/show.html.haml | 3 ++- app/views/projects/edit.html.haml | 8 +++--- .../20160823213309_add_enable_lfs_to_projects.rb | 29 ---------------------- .../20160823213309_add_lfs_enabled_to_projects.rb | 29 ++++++++++++++++++++++ db/schema.rb | 2 +- doc/api/projects.md | 6 ++--- doc/workflow/project_features.md | 10 ++++---- lib/api/entities.rb | 2 +- lib/api/projects.rb | 12 +++++---- spec/requests/lfs_http_spec.rb | 8 +++--- 14 files changed, 70 insertions(+), 57 deletions(-) delete mode 100644 db/migrate/20160823213309_add_enable_lfs_to_projects.rb create mode 100644 db/migrate/20160823213309_add_lfs_enabled_to_projects.rb diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 83500a687bb..f2db373da52 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -311,6 +311,14 @@ a.deploy-project-label { color: $gl-success; } +.lfs-enabled { + color: $gl-success; +} + +.lfs-disabled { + color: $gl-warning; +} + .breadcrumb.repo-breadcrumb { padding: 0; background: transparent; diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 678b56b5d9b..84d6b106cd7 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -309,7 +309,7 @@ class ProjectsController < Projects::ApplicationController :issues_tracker_id, :default_branch, :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :enable_lfs + :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :lfs_enabled ) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a5ae9f8668e..f07077bd133 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -189,11 +189,11 @@ module ProjectsHelper def project_lfs_status(project) if project.lfs_enabled? - content_tag(:span, class: 'vs-private') do + content_tag(:span, class: 'lfs-enabled') do 'Enabled' end else - content_tag(:span, class: 'vs-internal') do + content_tag(:span, class: 'lfs-disabled') do 'Disabled' end end diff --git a/app/models/project.rb b/app/models/project.rb index 7a5933bfe5e..e5027af4a0e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -392,9 +392,9 @@ class Project < ActiveRecord::Base def lfs_enabled? return false unless Gitlab.config.lfs.enabled - return Gitlab.config.lfs.enabled if enable_lfs.nil? + return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil? - enable_lfs + self[:lfs_enabled] end def repository_storage_path diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index f65322cc12f..6c7c3c48604 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -75,9 +75,10 @@ = last_commit(@project) %li - %span.light LFS status: + %span.light Git LFS status: %strong = project_lfs_status(@project) + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - else %li %span.light repository: diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 0c5ce193240..836c6d7b83f 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -83,11 +83,13 @@ - if Gitlab.config.lfs.enabled && current_user.admin? .form-group .checkbox - = f.label :enable_lfs do - = f.check_box :enable_lfs, checked: @project.lfs_enabled? + = f.label :lfs_enabled do + = f.check_box :lfs_enabled, checked: @project.lfs_enabled? %strong LFS %br - %span.descr Git Large File Storage + %span.descr + Git Large File Storage + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - if Gitlab.config.registry.enabled .form-group .checkbox diff --git a/db/migrate/20160823213309_add_enable_lfs_to_projects.rb b/db/migrate/20160823213309_add_enable_lfs_to_projects.rb deleted file mode 100644 index 9df1a5078fa..00000000000 --- a/db/migrate/20160823213309_add_enable_lfs_to_projects.rb +++ /dev/null @@ -1,29 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddEnableLfsToProjects < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - - def change - add_column :projects, :enable_lfs, :boolean - end -end diff --git a/db/migrate/20160823213309_add_lfs_enabled_to_projects.rb b/db/migrate/20160823213309_add_lfs_enabled_to_projects.rb new file mode 100644 index 00000000000..c169084e976 --- /dev/null +++ b/db/migrate/20160823213309_add_lfs_enabled_to_projects.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddLfsEnabledToProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :projects, :lfs_enabled, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 28711294746..bacc134a98d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -825,7 +825,7 @@ ActiveRecord::Schema.define(version: 20160824103857) do t.string "repository_storage", default: "default", null: false t.boolean "request_access_enabled", default: true, null: false t.boolean "has_external_wiki" - t.boolean "enable_lfs" + t.boolean "lfs_enabled" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree diff --git a/doc/api/projects.md b/doc/api/projects.md index 671b4ba7a7a..0d5aa61aa74 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -452,7 +452,7 @@ Parameters: - `import_url` (optional) - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) -- `enable_lfs` (optional) +- `lfs_enabled` (optional) ### Create project for user @@ -479,7 +479,7 @@ Parameters: - `import_url` (optional) - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) -- `enable_lfs` (optional) +- `lfs_enabled` (optional) ### Edit project @@ -507,7 +507,7 @@ Parameters: - `visibility_level` (optional) - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) -- `enable_lfs` (optional) +- `lfs_enabled` (optional) On success, method returns 200 with the updated project. If parameters are invalid, 400 is returned. diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md index 6790c06f325..f19e7df8c9a 100644 --- a/doc/workflow/project_features.md +++ b/doc/workflow/project_features.md @@ -32,12 +32,12 @@ Snippets are little bits of code or text. This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control. -For example, a specific config file that is used by > the team that is only valid for the people that work on the code. +For example, a specific config file that is used by the team that is only valid for the people that work on the code. -## LFS +## Git LFS ->**Note:** Project specific LFS setting was added on 8.12 and is available only to admins. +>**Note:** Project-specific LFS setting was added on 8.12 and is available only to admins. Git Large File Storage allows you to easily manage large binary files with Git. -With this setting admins can keep better control of which projects are allowed -to use LFS, thus allowing for better storage usage control. +With this setting admins can better control which projects are allowed to use +LFS. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 61fcccf2959..4335e3055ef 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -78,7 +78,7 @@ module API expose :path, :path_with_namespace expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled expose :created_at, :last_activity_at - expose :shared_runners_enabled, :enable_lfs + expose :shared_runners_enabled, :lfs_enabled expose :creator_id expose :namespace expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } diff --git a/lib/api/projects.rb b/lib/api/projects.rb index d98fb2611ff..f8979a1cc29 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -105,7 +105,7 @@ module API # visibility_level (optional) - 0 by default # import_url (optional) # public_builds (optional) - # enable_lfs (optional) + # lfs_enabled (optional) # Example Request # POST /projects post do @@ -126,7 +126,7 @@ module API :import_url, :public_builds, :only_allow_merge_if_build_succeeds, - :enable_lfs] + :lfs_enabled] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(current_user, attrs).execute if @project.saved? @@ -158,6 +158,7 @@ module API # visibility_level (optional) # import_url (optional) # public_builds (optional) + # lfs_enabled (optional) # Example Request # POST /projects/user/:user_id post "user/:user_id" do @@ -176,7 +177,8 @@ module API :visibility_level, :import_url, :public_builds, - :only_allow_merge_if_build_succeeds] + :only_allow_merge_if_build_succeeds, + :lfs_enabled] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(user, attrs).execute if @project.saved? @@ -222,7 +224,7 @@ module API # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) - visibility level of a project # public_builds (optional) - # enable_lfs (optional) + # lfs_enabled (optional) # Example Request # PUT /projects/:id put ':id' do @@ -241,7 +243,7 @@ module API :visibility_level, :public_builds, :only_allow_merge_if_build_succeeds, - :enable_lfs] + :lfs_enabled] attrs = map_public_to_visibility_level(attrs) authorize_admin_project authorize! :rename_project, user_project if attrs[:name].present? diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 2d39f3808d5..fcd6521317a 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -69,7 +69,7 @@ describe 'Git LFS API and storage' do describe 'LFS disabled in project' do before do - project.update_attribute(:enable_lfs, false) + project.update_attribute(:lfs_enabled, false) end it 'responds with a 501 message on upload' do @@ -87,7 +87,7 @@ describe 'Git LFS API and storage' do describe 'LFS enabled in project' do before do - project.update_attribute(:enable_lfs, true) + project.update_attribute(:lfs_enabled, true) end it 'responds with a 501 message on upload' do @@ -112,7 +112,7 @@ describe 'Git LFS API and storage' do describe 'LFS disabled in project' do before do - project.update_attribute(:enable_lfs, false) + project.update_attribute(:lfs_enabled, false) end it 'responds with a 403 message on upload' do @@ -132,7 +132,7 @@ describe 'Git LFS API and storage' do describe 'LFS enabled in project' do before do - project.update_attribute(:enable_lfs, true) + project.update_attribute(:lfs_enabled, true) end it 'responds with a 200 message on upload' do -- cgit v1.2.1 From bc0a513f624f8e06839b4c3baa5fe10f71284ef2 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 15:55:28 -0700 Subject: s/NB:/NOTE:/ --- app/policies/base_policy.rb | 2 +- app/policies/project_policy.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index cc82793b716..118c100ca11 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -59,7 +59,7 @@ class BasePolicy begin policy_class = "#{klass.name}Policy".constantize - # NB: the < operator here tests whether policy_class + # NOTE: the < operator here tests whether policy_class # inherits from BasePolicy return policy_class if policy_class < BasePolicy rescue NameError diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 0e933d00904..15a9f2f0dca 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -196,7 +196,7 @@ class ProjectPolicy < BasePolicy can! :read_container_image can! :download_code - # NB: may be overridden by IssuePolicy + # NOTE: may be overridden by IssuePolicy can! :read_issue # Allow to read builds by anonymous user if guests are allowed -- cgit v1.2.1 From b105dc791df07bab0d5349c63cb73c7b3ee8212c Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Tue, 30 Aug 2016 15:55:37 -0700 Subject: newline before default return --- app/policies/issue_policy.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb index 08538861364..bd1811a3c54 100644 --- a/app/policies/issue_policy.rb +++ b/app/policies/issue_policy.rb @@ -22,6 +22,7 @@ class IssuePolicy < IssuablePolicy return true if @subject.author == @user return true if @subject.assignee == @user return true if @subject.project.team.member?(@user, Gitlab::Access::REPORTER) + false end end -- cgit v1.2.1 From cc3b6f12cca88d25490ef7075b69cc9fad0f04d4 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 30 Aug 2016 16:29:11 -0700 Subject: Remove not-null constraint on lock_version column if it exists Closes #21678 --- db/migrate/20160830232601_change_lock_version_not_null.rb | 13 +++++++++++++ db/schema.rb | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160830232601_change_lock_version_not_null.rb diff --git a/db/migrate/20160830232601_change_lock_version_not_null.rb b/db/migrate/20160830232601_change_lock_version_not_null.rb new file mode 100644 index 00000000000..01c58ed5bdc --- /dev/null +++ b/db/migrate/20160830232601_change_lock_version_not_null.rb @@ -0,0 +1,13 @@ +class ChangeLockVersionNotNull < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + change_column_null :issues, :lock_version, true + change_column_null :merge_requests, :lock_version, true + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index e1ce7085c62..963d528d170 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160827011312) do +ActiveRecord::Schema.define(version: 20160830232601) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 0272d968538e0b66fbfd90d4a4ad51f6ef4cd7c6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 26 Aug 2016 17:06:09 -0500 Subject: Remove redundant pipeline tooltips --- CHANGELOG | 1 + app/views/projects/commit/_pipelines_list.haml | 5 ++++- app/views/projects/pipelines/index.html.haml | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 18efe057299..b6ced804b13 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.12.0 (unreleased) - Fix markdown help references (ClemMakesApps) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 + - Remove redundant pipeline tooltips (ClemMakesApps) - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps) diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 29f4ef8f49e..f41a11a056d 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -10,7 +10,10 @@ %th Commit - pipelines.stages.each do |stage| %th.stage - %span.has-tooltip{ title: "#{stage.titleize}" } + - if stage.titleize.length > 12 + %span.has-tooltip{ title: "#{stage.titleize}" } + = stage.titleize + - else = stage.titleize %th %th diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 5f466bdbac2..4d957e0d890 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -49,7 +49,10 @@ %th Commit - stages.each do |stage| %th.stage - %span.has-tooltip{ title: "#{stage.titleize}" } + - if stage.titleize.length > 12 + %span.has-tooltip{ title: "#{stage.titleize}" } + = stage.titleize + - else = stage.titleize %th %th -- cgit v1.2.1 From 727dff3f158b9ef852b2b014d4efe0abd69a23d0 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 26 Aug 2016 09:37:57 +0530 Subject: Don't expose a user's private token in the `/api/v3/user` API. - This would allow anyone with a personal access token (even a read-only token, once scopes are implemented) to escalate their access by obtaining the private token. --- doc/api/users.md | 3 +-- lib/api/users.rb | 2 +- spec/requests/api/users_spec.rb | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 7e848586dbd..54f7a2a2ace 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -310,8 +310,7 @@ GET /user "can_create_group": true, "can_create_project": true, "two_factor_enabled": true, - "external": false, - "private_token": "dd34asd13as" + "external": false } ``` diff --git a/lib/api/users.rb b/lib/api/users.rb index 8a376d3c2a3..c440305ff0f 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -327,7 +327,7 @@ module API # Example Request: # GET /user get do - present @current_user, with: Entities::UserLogin + present @current_user, with: Entities::UserFull end # Get currently authenticated user's keys diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 0bbba64a6d5..ef73778efa9 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -605,6 +605,7 @@ describe API::API, api: true do expect(json_response['can_create_project']).to eq(user.can_create_project?) expect(json_response['can_create_group']).to eq(user.can_create_group?) expect(json_response['projects_limit']).to eq(user.projects_limit) + expect(json_response['private_token']).to be_blank end it "returns 401 error if user is unauthenticated" do -- cgit v1.2.1 From bcdd3d8ecef407f25a34ae0b7da421f1ace8ad37 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 26 Aug 2016 09:43:53 +0530 Subject: Update CHANGELOG. --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 4e963702b8e..48c9d7bd862 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.12.0 (unreleased) - Fix markdown help references (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps) - Added project specific enable/disable setting for LFS !5997 + - Don't expose a user's token in the `/api/v3/user` API (!6047) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Add delimiter to project stars and forks count (ClemMakesApps) -- cgit v1.2.1 From 036cc8c27e8340a3eed63444bd3f42f86037f350 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 15 Jul 2016 16:21:53 +0200 Subject: API: Expose issue#confidential --- CHANGELOG | 1 + doc/api/issues.md | 32 +++++++++++++------- lib/api/entities.rb | 1 + lib/api/issues.rb | 14 +++++++-- spec/requests/api/issues_spec.rb | 65 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 100 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4e963702b8e..ec76455e837 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.12.0 (unreleased) - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) - Add white background for no readme container (ClemMakesApps) + - API: Expose issue confidentiality flag. (Robert Schilling) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import diff --git a/doc/api/issues.md b/doc/api/issues.md index b194799ccbf..eed0d2fce51 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -80,7 +80,8 @@ Example response: "subscribed" : false, "user_notes_count": 1, "due_date": "2016-07-22", - "web_url": "http://example.com/example/example/issues/6" + "web_url": "http://example.com/example/example/issues/6", + "confidential": false } ] ``` @@ -158,7 +159,8 @@ Example response: "subscribed" : false, "user_notes_count": 1, "due_date": null, - "web_url": "http://example.com/example/example/issues/1" + "web_url": "http://example.com/example/example/issues/1", + "confidential": false } ] ``` @@ -238,7 +240,8 @@ Example response: "subscribed" : false, "user_notes_count": 1, "due_date": "2016-07-22", - "web_url": "http://example.com/example/example/issues/1" + "web_url": "http://example.com/example/example/issues/1", + "confidential": false } ] ``` @@ -303,7 +306,8 @@ Example response: "subscribed": false, "user_notes_count": 1, "due_date": null, - "web_url": "http://example.com/example/example/issues/1" + "web_url": "http://example.com/example/example/issues/1", + "confidential": false } ``` @@ -324,6 +328,7 @@ POST /projects/:id/issues | `id` | integer | yes | The ID of a project | | `title` | string | yes | The title of an issue | | `description` | string | no | The description of an issue | +| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. | | `assignee_id` | integer | no | The ID of a user to assign issue | | `milestone_id` | integer | no | The ID of a milestone to assign issue | | `labels` | string | no | Comma-separated label names for an issue | @@ -362,7 +367,8 @@ Example response: "subscribed" : true, "user_notes_count": 0, "due_date": null, - "web_url": "http://example.com/example/example/issues/14" + "web_url": "http://example.com/example/example/issues/14", + "confidential": false } ``` @@ -385,6 +391,7 @@ PUT /projects/:id/issues/:issue_id | `issue_id` | integer | yes | The ID of a project's issue | | `title` | string | no | The title of an issue | | `description` | string | no | The description of an issue | +| `confidential` | boolean | no | Updates an issue to be confidential | | `assignee_id` | integer | no | The ID of a user to assign the issue to | | `milestone_id` | integer | no | The ID of a milestone to assign the issue to | | `labels` | string | no | Comma-separated label names for an issue | @@ -424,7 +431,8 @@ Example response: "subscribed" : true, "user_notes_count": 0, "due_date": "2016-07-22", - "web_url": "http://example.com/example/example/issues/15" + "web_url": "http://example.com/example/example/issues/15", + "confidential": false } ``` @@ -503,7 +511,8 @@ Example response: "web_url": "https://gitlab.example.com/u/solon.cremin" }, "due_date": null, - "web_url": "http://example.com/example/example/issues/11" + "web_url": "http://example.com/example/example/issues/11", + "confidential": false } ``` @@ -559,7 +568,8 @@ Example response: "web_url": "https://gitlab.example.com/u/solon.cremin" }, "due_date": null, - "web_url": "http://example.com/example/example/issues/11" + "web_url": "http://example.com/example/example/issues/11", + "confidential": false } ``` @@ -616,7 +626,8 @@ Example response: }, "subscribed": false, "due_date": null, - "web_url": "http://example.com/example/example/issues/12" + "web_url": "http://example.com/example/example/issues/12", + "confidential": false } ``` @@ -704,7 +715,8 @@ Example response: "upvotes": 0, "downvotes": 0, "due_date": null, - "web_url": "http://example.com/example/example/issues/110" + "web_url": "http://example.com/example/example/issues/110", + "confidential": false }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10", "body": "Vel voluptas atque dicta mollitia adipisci qui at.", diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4335e3055ef..e3a8ff6de80 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -211,6 +211,7 @@ module API expose :user_notes_count expose :upvotes, :downvotes expose :due_date + expose :confidential expose :web_url do |issue, options| Gitlab::UrlBuilder.build(issue) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index d0bc7243e54..556684187d8 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -140,12 +140,13 @@ module API # labels (optional) - The labels of an issue # created_at (optional) - Date time string, ISO 8601 formatted # due_date (optional) - Date time string in the format YEAR-MONTH-DAY + # confidential (optional) - Boolean parameter if the issue should be confidential # Example Request: # POST /projects/:id/issues post ':id/issues' do required_attributes! [:title] - keys = [:title, :description, :assignee_id, :milestone_id, :due_date] + keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential] keys << :created_at if current_user.admin? || user_project.owner == current_user attrs = attributes_for_keys(keys) @@ -156,6 +157,10 @@ module API attrs[:labels] = params[:labels] if params[:labels] + # Convert and filter out invalid confidential flags + attrs['confidential'] = to_boolean(attrs['confidential']) + attrs.delete('confidential') if attrs['confidential'].nil? + issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute if issue.spam? @@ -182,12 +187,13 @@ module API # state_event (optional) - The state event of an issue (close|reopen) # updated_at (optional) - Date time string, ISO 8601 formatted # due_date (optional) - Date time string in the format YEAR-MONTH-DAY + # confidential (optional) - Boolean parameter if the issue should be confidential # Example Request: # PUT /projects/:id/issues/:issue_id put ':id/issues/:issue_id' do issue = user_project.issues.find(params[:issue_id]) authorize! :update_issue, issue - keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date] + keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date, :confidential] keys << :updated_at if current_user.admin? || user_project.owner == current_user attrs = attributes_for_keys(keys) @@ -198,6 +204,10 @@ module API attrs[:labels] = params[:labels] if params[:labels] + # Convert and filter out invalid confidential flags + attrs['confidential'] = to_boolean(attrs['confidential']) + attrs.delete('confidential') if attrs['confidential'].nil? + issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) if issue.valid? diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 3362a88d798..47344a13b5e 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -405,6 +405,7 @@ describe API::API, api: true do expect(json_response['milestone']).to be_a Hash expect(json_response['assignee']).to be_a Hash expect(json_response['author']).to be_a Hash + expect(json_response['confidential']).to be_falsy end it "returns a project issue by id" do @@ -470,13 +471,51 @@ describe API::API, api: true do end describe "POST /projects/:id/issues" do - it "creates a new project issue" do + it 'creates a new project issue' do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2' + expect(response).to have_http_status(201) expect(json_response['title']).to eq('new issue') expect(json_response['description']).to be_nil expect(json_response['labels']).to eq(['label', 'label2']) + expect(json_response['confidential']).to be_falsy + end + + it 'creates a new confidential project issue' do + post api("/projects/#{project.id}/issues", user), + title: 'new issue', confidential: true + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['confidential']).to be_truthy + end + + it 'creates a new confidential project issue with a different param' do + post api("/projects/#{project.id}/issues", user), + title: 'new issue', confidential: 'y' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['confidential']).to be_truthy + end + + it 'creates a public issue when confidential param is false' do + post api("/projects/#{project.id}/issues", user), + title: 'new issue', confidential: false + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['confidential']).to be_falsy + end + + it 'creates a public issue when confidential param is invalid' do + post api("/projects/#{project.id}/issues", user), + title: 'new issue', confidential: 'foo' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['confidential']).to be_falsy end it "sends notifications for subscribers of newly added labels" do @@ -632,6 +671,30 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end + + it 'sets an issue to confidential' do + put api("/projects/#{project.id}/issues/#{issue.id}", user), + confidential: true + + expect(response).to have_http_status(200) + expect(json_response['confidential']).to be_truthy + end + + it 'makes a confidential issue public' do + put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), + confidential: false + + expect(response).to have_http_status(200) + expect(json_response['confidential']).to be_falsy + end + + it 'does not update a confidential issue with wrong confidential flag' do + put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user), + confidential: 'foo' + + expect(response).to have_http_status(200) + expect(json_response['confidential']).to be_truthy + end end end -- cgit v1.2.1 From 3a575d1479512fc25ec963af0b01f1f24fd64181 Mon Sep 17 00:00:00 2001 From: zzjin Date: Wed, 31 Aug 2016 07:57:56 +0000 Subject: Update toggler_behavior.js to toggle ajax loaded contents like `diffs` page. --- app/assets/javascripts/behaviors/toggler_behavior.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 8ac1ba7665e..5467e3edc69 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,6 +1,6 @@ (function(w) { $(function() { - $('.js-toggle-button').on('click', function(e) { + $('body').on('click', '.js-toggle-button', function(e) { e.preventDefault(); $(this) .find('.fa') -- cgit v1.2.1 From 246bb231c377e2b8a6696811a200d6ac8072cebe Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 19 Aug 2016 12:17:46 -0300 Subject: Returns the total number of issues in the JSON response --- app/controllers/projects/boards/issues_controller.rb | 15 +++++++++------ spec/fixtures/api/schemas/issues.json | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 1a4f6b50e8f..9404612a993 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -8,12 +8,15 @@ module Projects issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute issues = issues.page(params[:page]) - render json: issues.as_json( - only: [:iid, :title, :confidential], - include: { - assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, - labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } - }) + render json: { + issues: issues.as_json( + only: [:iid, :title, :confidential], + include: { + assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, + labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } + }), + size: issues.total_count + } end def update diff --git a/spec/fixtures/api/schemas/issues.json b/spec/fixtures/api/schemas/issues.json index 0d2067f704a..70771b21c96 100644 --- a/spec/fixtures/api/schemas/issues.json +++ b/spec/fixtures/api/schemas/issues.json @@ -1,4 +1,15 @@ { - "type": "array", - "items": { "$ref": "issue.json" } + "type": "object", + "required" : [ + "issues", + "size" + ], + "properties" : { + "issues": { + "type": "array", + "items": { "$ref": "issue.json" } + }, + "size": { "type": "integer" } + }, + "additionalProperties": false } -- cgit v1.2.1 From 4f341ed85b5fb37250799ec13948bbe88074d376 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 19 Aug 2016 17:19:20 +0100 Subject: Changed frontend to use issue count from backend --- app/assets/javascripts/boards/models/list.js.es6 | 6 +++++- app/views/projects/boards/components/_board.html.haml | 2 +- spec/features/boards/boards_spec.rb | 6 ++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 816fa49516c..296a033071e 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -11,6 +11,7 @@ class List { this.loading = true; this.loadingMore = false; this.issues = []; + this.issuesSize = 0; if (obj.label) { this.label = new ListLabel(obj.label); @@ -76,12 +77,13 @@ class List { .then((resp) => { const data = resp.json(); this.loading = false; + this.issuesSize = data.size; if (emptyIssues) { this.issues = []; } - this.createIssues(data); + this.createIssues(data.issues); }); } @@ -99,6 +101,7 @@ class List { } if (listFrom) { + this.issuesSize++; gl.boardService.moveIssue(issue.id, listFrom.id, this.id); } } @@ -112,6 +115,7 @@ class List { const matchesRemove = removeIssue.id === issue.id; if (matchesRemove) { + this.issuesSize--; issue.removeLabel(this.label); } diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 6b4bfe0c354..069f8b805bc 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -13,7 +13,7 @@ %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } {{ list.title }} %span.pull-right{ "v-if" => "list.type !== 'blank'" } - {{ list.issues.length }} + {{ list.issuesSize }} - if can?(current_user, :admin_list, @project) %board-delete{ "inline-template" => true, ":list" => "list", diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 55e5dc15428..e150610b3eb 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -182,13 +182,12 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('20') + expect(page.find('.board-header')).to have_content('56') expect(page).to have_selector('.card', count: 20) evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") wait_for_vue_resource(spinner: false) - expect(page.find('.board-header')).to have_content('40') expect(page).to have_selector('.card', count: 40) end end @@ -479,12 +478,11 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('20') + expect(page.find('.board-header')).to have_content('51') expect(page).to have_selector('.card', count: 20) evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") - expect(page.find('.board-header')).to have_content('40') expect(page).to have_selector('.card', count: 40) end end -- cgit v1.2.1 From 105c80b6be3ae0bb550f455d0fcb1d4fe34462b5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 19 Aug 2016 20:23:56 +0100 Subject: Shows count at bottom of list Only visible when scrollable area is larger than height --- .../javascripts/boards/components/board_list.js.es6 | 13 ++++++++++++- app/assets/stylesheets/pages/boards.scss | 15 ++++++++++----- app/views/projects/boards/components/_board.html.haml | 7 ++++++- spec/features/boards/boards_spec.rb | 15 +++++++++++++++ spec/javascripts/boards/mock_data.js.es6 | 15 +++++++++------ 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index a6644e9eb8c..50fc11d7737 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -20,7 +20,8 @@ data () { return { scrollOffset: 250, - filters: Store.state.filters + filters: Store.state.filters, + showCount: false }; }, watch: { @@ -30,6 +31,15 @@ this.$els.list.scrollTop = 0; }, deep: true + }, + issues () { + this.$nextTick(() => { + if (this.scrollHeight() > this.listHeight()) { + this.showCount = true; + } else { + this.showCount = false; + } + }); } }, methods: { @@ -58,6 +68,7 @@ group: 'issues', sort: false, disabled: this.disabled, + filter: '.board-list-count', onStart: (e) => { const card = this.$refs.issue[e.oldIndex]; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index d91558bc672..037278bb083 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -142,11 +142,6 @@ } } -.board-header-loading-spinner { - margin-right: 10px; - color: $gray-darkest; -} - .board-inner-container { border-bottom: 1px solid $border-color; padding: $gl-padding; @@ -279,3 +274,13 @@ width: 210px; } } + +.board-list-count { + padding: 10px 0; + color: $gl-placeholder-color; + font-size: 13px; + + > .fa { + margin-right: 5px; + } +} diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 069f8b805bc..73066150fb3 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -20,7 +20,6 @@ "v-if" => "!list.preset && list.id" } %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" } = icon("trash") - = icon("spinner spin", class: "board-header-loading-spinner pull-right", "v-show" => "list.loadingMore") %board-list{ "inline-template" => true, "v-if" => "list.type !== 'blank'", ":list" => "list", @@ -34,5 +33,11 @@ "v-show" => "!loading", ":data-board" => "list.id" } = render "projects/boards/components/card" + %li.board-list-count.text-center{ "v-if" => "showCount" } + = icon("spinner spin", "v-show" => "list.loadingMore" ) + %span{ "v-if" => "list.issues.length === list.issuesSize" } + Showing all issues + %span{ "v-else" => true } + Showing {{ list.issues.length }} of {{ list.issuesSize }} issues - if can?(current_user, :admin_list, @project) = render "projects/boards/components/blank_state" diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index e150610b3eb..c6c2e2095df 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -184,11 +184,19 @@ describe 'Issue Boards', feature: true, js: true do page.within(find('.board', match: :first)) do expect(page.find('.board-header')).to have_content('56') expect(page).to have_selector('.card', count: 20) + expect(page).to have_content('Showing 20 of 56 issues') evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") wait_for_vue_resource(spinner: false) expect(page).to have_selector('.card', count: 40) + expect(page).to have_content('Showing 40 of 56 issues') + + evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") + wait_for_vue_resource(spinner: false) + + expect(page).to have_selector('.card', count: 56) + expect(page).to have_content('Showing all issues') end end @@ -480,10 +488,17 @@ describe 'Issue Boards', feature: true, js: true do page.within(find('.board', match: :first)) do expect(page.find('.board-header')).to have_content('51') expect(page).to have_selector('.card', count: 20) + expect(page).to have_content('Showing 20 of 51 issues') evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") expect(page).to have_selector('.card', count: 40) + expect(page).to have_content('Showing 40 of 51 issues') + + evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") + + expect(page).to have_selector('.card', count: 51) + expect(page).to have_content('Showing all issues') end end diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index 0c37ec8354f..f3797ed44d4 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -26,12 +26,15 @@ const listObjDuplicate = { const BoardsMockData = { 'GET': { - '/test/issue-boards/board/lists{/id}/issues': [{ - title: 'Testing', - iid: 1, - confidential: false, - labels: [] - }] + '/test/issue-boards/board/lists{/id}/issues': { + issues: [{ + title: 'Testing', + iid: 1, + confidential: false, + labels: [] + }], + size: 1 + } }, 'POST': { '/test/issue-boards/board/lists{/id}': listObj -- cgit v1.2.1 From 97d6f5b6ded829d1f7e792c59ae5eb4b2aae7c70 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 31 Aug 2016 11:41:16 +0100 Subject: Fixed escaping issue with labels filter Closes #15552 --- app/assets/javascripts/gl_dropdown.js | 2 +- app/assets/javascripts/labels_select.js | 2 +- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- spec/features/issues/filter_issues_spec.rb | 10 ++++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 5a2a8523d9f..77b2082cba0 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -556,7 +556,7 @@ if (isInput) { field = $(this.el); } else { - field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); + field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']"); } if (el.hasClass(ACTIVE_CLASS)) { el.removeClass(ACTIVE_CLASS); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 565dbeacdb3..bab23ff5ac0 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -164,7 +164,7 @@ instance.addInput(this.fieldName, label.id); } } - if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + (this.id(label)) + "']").length) { + if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) { selectedClass.push('is-active'); } if ($dropdown.hasClass('js-multiselect') && removesAll) { diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index d34d28f6736..24a1a616919 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -12,7 +12,7 @@ - if params[:label_name].present? - if params[:label_name].respond_to?('any?') - params[:label_name].each do |label| - = hidden_field_tag "label_name[]", label, id: nil + = hidden_field_tag "label_name[]", u(label), id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index e262f285868..0e9f814044e 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -8,6 +8,7 @@ describe 'Filter issues', feature: true do let!(:milestone) { create(:milestone, project: project) } let!(:label) { create(:label, project: project) } let!(:issue1) { create(:issue, project: project) } + let!(:wontfix) { create(:label, project: project, title: "Won't fix") } before do project.team << [user, :master] @@ -107,6 +108,15 @@ describe 'Filter issues', feature: true do end expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) end + + it 'filters by wont fix labels' do + find('.dropdown-menu-labels a', text: label.title).click + page.within '.labels-filter' do + expect(page).to have_content wontfix.title + click_link wontfix.title + end + expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title) + end end describe 'Filter issues for assignee and label from issues#index' do -- cgit v1.2.1 From 6c9c33f43e489d9e225bc64342c7d7d0a558b1e7 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Wed, 31 Aug 2016 11:39:35 +0100 Subject: filters tags by name --- CHANGELOG | 1 + app/controllers/projects/tags_controller.rb | 3 ++- app/helpers/tags_helper.rb | 10 ++++++++++ app/views/projects/tags/index.html.haml | 14 ++++++++------ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4e963702b8e..d82cf74f322 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Filter tags by name !6121 - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 8592579abbd..3e5de2e0d3e 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -1,4 +1,5 @@ class Projects::TagsController < Projects::ApplicationController + include SortingHelper # Authorize before_action :require_non_empty_project before_action :authorize_download_code! @@ -7,7 +8,7 @@ class Projects::TagsController < Projects::ApplicationController def index @sort = params[:sort] || 'name' - @tags = @repository.tags_sorted_by(@sort) + @tags = TagsFinder.new(@repository, params).execute @tags = Kaminari.paginate_array(@tags).page(params[:page]) @releases = project.releases.where(tag: @tags.map(&:name)) diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index fb85544df2d..c0ec1634cdb 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -3,6 +3,16 @@ module TagsHelper "/tags/#{tag}" end + def filter_tags_path(options = {}) + exist_opts = { + search: params[:search], + sort: params[:sort] + } + + options = exist_opts.merge(options) + namespace_project_tags_path(@project.namespace, @project, @id, options) + end + def tag_list(project) html = '' project.tag_list.each do |tag| diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 368231e73fe..31a023f24cf 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -8,21 +8,23 @@ Tags give the ability to mark specific points in history as being important .nav-controls - - if can? current_user, :push_code, @project - = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do - New tag + = form_tag(filter_tags_path, method: :get) do + = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } %span.light= @sort.humanize %b.caret %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to namespace_project_tags_path(sort: nil) do + = link_to filter_tags_path(sort: nil) do Name - = link_to namespace_project_tags_path(sort: sort_value_recently_updated) do + = link_to filter_tags_path(sort: sort_value_recently_updated) do = sort_title_recently_updated - = link_to namespace_project_tags_path(sort: sort_value_oldest_updated) do + = link_to filter_tags_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated + - if can? current_user, :push_code, @project + = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do + New tag .tags - if @tags.any? -- cgit v1.2.1 From 39d789eaeeffb2667d95d05da76d3a7b72ae3b13 Mon Sep 17 00:00:00 2001 From: Vitaly Baev Date: Wed, 31 Aug 2016 15:11:36 +0300 Subject: Fixed invisible scroll controlls on build page on iPhone --- CHANGELOG | 1 + app/assets/stylesheets/pages/builds.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 7941a29d4ed..6a4fdad8755 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,6 +50,7 @@ v 8.12.0 (unreleased) - Adds response mime type to transaction metric action when it's not HTML - Fix hover leading space bug in pipeline graph !5980 - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 + - Fixed invisible scroll controls on build page on iPhone v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 8c33e7d9a2e..cee198691c2 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -36,6 +36,7 @@ &.affix { right: 30px; bottom: 15px; + z-index: 1; @media (min-width: $screen-md-min) { right: 26%; -- cgit v1.2.1 From 52ab33a3fa1bd31e8e01ea8b9c89d8c08dbeff82 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 31 Aug 2016 15:04:51 +0100 Subject: Expire commit view partial after a day We rarely use Russian-doll caching in views, and when we do, it's typically with a naturally-invalidating key. In the case of the commit partial, though, the author lookup isn't part of the cache key - because we're not going to use the state of the users table - and so a new email address can take up to two weeks to show against the commits list. Limiting this to a day still caches the partial for a healthy amount of time, without as bad a worst case scenario. --- CHANGELOG | 1 + app/views/projects/commits/_commit.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7941a29d4ed..f36ab3b2b21 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.12.0 (unreleased) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Remove redundant pipeline tooltips (ClemMakesApps) + - Expire commit info views after one day, instead of two weeks, to allow for user email updates - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index fd888f41b1e..389477d0927 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -7,7 +7,7 @@ - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] - cache_key.push(commit.status) if commit.status -= cache(cache_key) do += cache(cache_key, expires_in: 1.day) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } = author_avatar(commit, size: 36) -- cgit v1.2.1 From 5ebc787ca02e018b0344bd4e4fc2a6ac9f6f1677 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 31 Aug 2016 16:42:36 +0200 Subject: Final piece of search-and-replace --- doc/update/8.11-to-8.12.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 544a80662d3..aac19897f9e 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -185,7 +185,7 @@ If all items are green, then congratulations, the upgrade is complete! ### 1. Revert the code to the previous version -Follow the [upgrade guide from 8.9 to 8.11](8.9-to-8.11.md), except for the +Follow the [upgrade guide from 8.10 to 8.11](8.10-to-8.11.md), except for the database migration (the backup is already migrated to the previous version). ### 2. Restore from the backup -- cgit v1.2.1 From 9c3db830c033750c19197fe6a3f15a7705e2aa3b Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 13:29:59 -0400 Subject: entities: expose {,merge_commit_}sha in MergeRequest Fixes #20456. --- CHANGELOG | 1 + doc/api/merge_requests.md | 24 ++++++++++++++++++++++-- lib/api/entities.rb | 2 ++ spec/requests/api/merge_requests_spec.rb | 9 ++++++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7941a29d4ed..2484f29cff1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) + - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index f4760ceac7c..494040a1ce8 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -68,6 +68,8 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : false, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null, "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, @@ -135,6 +137,8 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": "9999999999999999999999999999999999999999", "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, @@ -238,6 +242,8 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null, "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, @@ -322,6 +328,8 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null, "user_notes_count": 0, "should_remove_source_branch": true, "force_remove_source_branch": false, @@ -397,6 +405,8 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null, "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, @@ -499,6 +509,8 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": "9999999999999999999999999999999999999999", "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, @@ -569,6 +581,8 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null, "user_notes_count": 1, "should_remove_source_branch": true, "force_remove_source_branch": false, @@ -724,7 +738,9 @@ Example response: }, "merge_when_build_succeeds": false, "merge_status": "cannot_be_merged", - "subscribed": true + "subscribed": true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null } ``` @@ -798,7 +814,9 @@ Example response: }, "merge_when_build_succeeds": false, "merge_status": "cannot_be_merged", - "subscribed": false + "subscribed": false, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null } ``` @@ -891,6 +909,8 @@ Example response: "merge_when_build_succeeds": false, "merge_status": "unchecked", "subscribed": true, + "sha": "8888888888888888888888888888888888888888", + "merge_commit_sha": null, "user_notes_count": 7, "should_remove_source_branch": true, "force_remove_source_branch": false, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4335e3055ef..3b05f0487ff 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -232,6 +232,8 @@ module API expose :milestone, using: Entities::Milestone expose :merge_when_build_succeeds expose :merge_status + expose :diff_head_sha, as: :sha + expose :merge_commit_sha expose :subscribed do |merge_request, options| merge_request.subscribed?(options[:current_user]) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index baff872e28e..a7930c59df9 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -9,7 +9,7 @@ describe API::API, api: true do let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) } let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } - let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) } + let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } let(:milestone) { create(:milestone, title: '1.0.0', project: project) } @@ -34,6 +34,13 @@ describe API::API, api: true do expect(json_response.length).to eq(3) expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last).to have_key('web_url') + expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) + expect(json_response.last['merge_commit_sha']).to be_nil + expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha) + expect(json_response.first['title']).to eq(merge_request_merged.title) + expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) + expect(json_response.first['merge_commit_sha']).not_to be_nil + expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) end it "returns an array of all merge_requests" do -- cgit v1.2.1 From 90c0fdfc9da36c29fe5093b78e0581c19c2279b5 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 31 Aug 2016 17:12:37 +0200 Subject: More changes suggested by Robert --- doc/install/installation.md | 1 - doc/update/8.11-to-8.12.md | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index f012c9cd51b..2d0932d4f04 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -334,7 +334,6 @@ sudo usermod -aG redis git # Enable packfile bitmaps sudo -u git -H git config --global repack.writeBitmaps true - # Configure Redis connection settings sudo -u git -H cp config/resque.yml.example config/resque.yml diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index aac19897f9e..953e9d7e74c 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -105,7 +105,6 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production # Clean up assets and cache sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production - ``` ### 8. Update configuration files @@ -120,8 +119,10 @@ git diff origin/8-11-stable:config/gitlab.yml.example origin/8-12-stable:config/ #### Git configuration +Configure Git to generate packfile bitmaps (introduced in Git 2.0) on +the GitLab server during `git gc`. + ```sh -# Enable packfile bitmaps sudo -u git -H git config --global repack.writeBitmaps true ``` -- cgit v1.2.1 From f1d37cd4ba7dcb2e70ac5c221c08db0d8aa6b8f1 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 31 Aug 2016 17:14:16 +0200 Subject: CHANGELOG roulette --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 393c1e2a960..80635e59e47 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.12.0 (unreleased) - Add two-factor recovery endpoint to internal API !5510 - Add font color contrast to external label in admin area (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps) + - Instructions for enabling Git packfile bitmaps !6104 - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) @@ -40,7 +41,6 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML - Fix hover leading space bug in pipeline graph !5980 - - Instructions for enabling Git packfile bitmaps !6104 v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable -- cgit v1.2.1 From fd4efde5aa0693c04cc2679b550b271ea40eea39 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 29 Aug 2016 16:47:31 +0200 Subject: Block concurrent pipeline processings --- CHANGELOG | 4 +--- app/services/ci/process_pipeline_service.rb | 14 ++++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7941a29d4ed..d1e5c65ac31 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -54,13 +54,11 @@ v 8.12.0 (unreleased) v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Creating an issue through our API now emails label subscribers !5720 + - Block concurrent updates for Pipeline - Fix resolving conflicts on forks - Fix diff commenting on merge requests created prior to 8.10 - -v 8.11.4 (unreleased) - Fix issue boards leak private label names and descriptions -v 8.11.3 (unreleased) v 8.11.3 - Do not enforce using hash with hidden key in CI configuration. !6079 - Allow system info page to handle case where info is unavailable diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index f049ed628db..de48a50774e 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -10,13 +10,15 @@ module Ci create_builds! end - new_builds = - stage_indexes_of_created_builds.map do |index| - process_stage(index) - end + @pipeline.with_lock do + new_builds = + stage_indexes_of_created_builds.map do |index| + process_stage(index) + end - # Return a flag if a when builds got enqueued - new_builds.flatten.any? + # Return a flag if a when builds got enqueued + new_builds.flatten.any? + end end private -- cgit v1.2.1 From e18d034b20c8b0f1a1900e75c9bc0bce51c75438 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 31 Aug 2016 15:23:31 +0000 Subject: Fix grammar (those issue -> those issues) --- PROCESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PROCESS.md b/PROCESS.md index 8e1a3f7360f..8af660fbdd1 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -50,7 +50,7 @@ etc.). The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help -on those issue. Please select someone with relevant experience from +on those issues. Please select someone with relevant experience from [GitLab core team][core-team]. If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a -- cgit v1.2.1 From 632899826bbb4d50f8c003b2c1771fa17d89d022 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 30 Aug 2016 14:20:31 -0500 Subject: Fix bug where pagination is still displayed despite all todos marked as done --- CHANGELOG | 1 + app/assets/javascripts/todos.js | 2 +- app/views/dashboard/todos/index.html.haml | 2 +- features/steps/dashboard/todos.rb | 1 + spec/features/todos/todos_spec.rb | 14 ++++++++++++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 18efe057299..c1d989f1739 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.12.0 (unreleased) - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) + - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index 6e677fa8cc6..06605320a35 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -66,7 +66,7 @@ success: (function(_this) { return function(data) { $this.remove(); - $('.js-todos-list').remove(); + $('.prepend-top-default').html('
              You\'re all done!
              '); return _this.updateBadges(data); }; })(this) diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index d320d3bcc1e..6bcc37042ea 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -66,7 +66,7 @@ - if @todos.any? .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } - @todos.group_by(&:project).each do |group| - .panel.panel-default.panel-small.js-todos-list + .panel.panel-default.panel-small - project = group[0] .panel-heading = link_to project.name_with_namespace, namespace_project_path(project.namespace, project) diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb index 60152d3da55..0607086c166 100644 --- a/features/steps/dashboard/todos.rb +++ b/features/steps/dashboard/todos.rb @@ -54,6 +54,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps page.within('.todos-pending-count') { expect(page).to have_content '0' } expect(page).to have_content 'To do 0' expect(page).to have_content 'Done 4' + expect(page).to have_content "You're all done!" expect(page).not_to have_link project.name_with_namespace should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}" diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 32544f3f538..fc555a74f30 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -118,6 +118,20 @@ describe 'Dashboard Todos', feature: true do expect(page).to have_css("#todo_#{Todo.first.id}") end end + + describe 'mark all as done', js: true do + before do + visit dashboard_todos_path + click_link('Mark all as done') + end + + it 'shows "All done" message!' do + within('.todos-pending-count') { expect(page).to have_content '0' } + expect(page).to have_content 'To do 0' + expect(page).to have_content "You're all done!" + expect(page).not_to have_selector('.gl-pagination') + end + end end context 'User has a Todo in a project pending deletion' do -- cgit v1.2.1 From 636dbc85e24600849de9dd09c4854edf4adfe807 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 31 Aug 2016 17:40:54 +0200 Subject: The ID of a project can be also a string [ci skip] --- doc/api/commits.md | 14 +++++++------- doc/api/projects.md | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/api/commits.md b/doc/api/commits.md index 55d0de7afd9..682151d4b1d 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -10,7 +10,7 @@ GET /projects/:id/repository/commits | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | | `since` | string | no | Only commits after or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | | `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | @@ -58,7 +58,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash @@ -102,7 +102,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash @@ -138,7 +138,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit hash or name of a repository branch or tag | ```bash @@ -187,7 +187,7 @@ POST /projects/:id/repository/commits/:sha/comments | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit SHA or name of a repository branch or tag | | `note` | string | yes | The text of the comment | | `path` | string | no | The file path relative to the repository | @@ -232,7 +232,7 @@ GET /projects/:id/repository/commits/:sha/statuses | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit SHA | `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch | `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test` @@ -306,7 +306,7 @@ POST /projects/:id/statuses/:sha | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user | `sha` | string | yes | The commit SHA | `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled` | `ref` | string | no | The `ref` (branch or tag) to which the status refers diff --git a/doc/api/projects.md b/doc/api/projects.md index 0d5aa61aa74..a62aaee14d7 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -535,7 +535,7 @@ POST /projects/:id/star | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" @@ -602,7 +602,7 @@ DELETE /projects/:id/star | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" @@ -673,7 +673,7 @@ POST /projects/:id/archive | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive" @@ -760,7 +760,7 @@ POST /projects/:id/unarchive | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive" -- cgit v1.2.1 From 325de662ce79ea75348c303e05b2f0045835193e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 29 Aug 2016 18:17:11 -0300 Subject: Don't create groups for unallowed users when importing projects --- app/controllers/import/base_controller.rb | 6 +++- app/controllers/import/bitbucket_controller.rb | 13 +++---- app/controllers/import/github_controller.rb | 7 +--- app/controllers/import/gitlab_controller.rb | 9 ++--- app/helpers/import_helper.rb | 5 +++ app/views/import/bitbucket/status.html.haml | 2 +- app/views/import/github/status.html.haml | 2 +- app/views/import/gitlab/status.html.haml | 2 +- .../import/bitbucket_controller_spec.rb | 41 ++++++++++++++++------ spec/controllers/import/github_controller_spec.rb | 41 ++++++++++++++++------ spec/controllers/import/gitlab_controller_spec.rb | 41 ++++++++++++++++------ spec/helpers/import_helper_spec.rb | 24 +++++++++++++ 12 files changed, 137 insertions(+), 56 deletions(-) diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 7e8597a5eb3..1ca33bc5d22 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -1,12 +1,16 @@ class Import::BaseController < ApplicationController private - def get_or_create_namespace + def find_or_create_namespace(name, owner) begin + @target_namespace = params[:new_namespace].presence || name + @target_namespace = current_user.namespace_path if name == owner || !current_user.can_create_group? + namespace = Group.create!(name: @target_namespace, path: @target_namespace, owner: current_user) namespace.add_owner(current_user) rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid namespace = Namespace.find_by_path_or_name(@target_namespace) + unless current_user.can?(:create_projects, namespace) @already_been_taken = true return false diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 944c73d139a..94e213b8743 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -35,15 +35,10 @@ class Import::BitbucketController < Import::BaseController end def create - @repo_id = params[:repo_id] || "" - repo = client.project(@repo_id.gsub("___", "/")) - @project_name = repo["slug"] - - repo_owner = repo["owner"] - repo_owner = current_user.username if repo_owner == client.user["user"]["username"] - @target_namespace = params[:new_namespace].presence || repo_owner - - namespace = get_or_create_namespace || (render and return) + @repo_id = params[:repo_id].to_s + repo = client.project(@repo_id.gsub('___', '/')) + @project_name = repo['slug'] + namespace = find_or_create_namespace(repo['owner'], client.user['user']['username']) || (render and return) unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute @access_denied = true diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 9c1b0eb20f4..4047e62efa2 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -41,12 +41,7 @@ class Import::GithubController < Import::BaseController @repo_id = params[:repo_id].to_i repo = client.repo(@repo_id) @project_name = repo.name - - repo_owner = repo.owner.login - repo_owner = current_user.username if repo_owner == client.user.login - @target_namespace = params[:new_namespace].presence || repo_owner - - namespace = get_or_create_namespace || (render and return) + namespace = find_or_create_namespace(repo.owner.login, client.user.login) || (render and return) @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute end diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 08130ee8176..bc967e55ab1 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -26,13 +26,8 @@ class Import::GitlabController < Import::BaseController def create @repo_id = params[:repo_id].to_i repo = client.project(@repo_id) - @project_name = repo["name"] - - repo_owner = repo["namespace"]["path"] - repo_owner = current_user.username if repo_owner == client.user["username"] - @target_namespace = params[:new_namespace].presence || repo_owner - - namespace = get_or_create_namespace || (render and return) + @project_name = repo['name'] + namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username']) || (render and return) @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute end diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index 109bc1a02d1..021d2b14718 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -1,4 +1,9 @@ module ImportHelper + def import_project_target(owner, name) + namespace = current_user.can_create_group? ? owner : current_user.namespace_path + "#{namespace}/#{name}" + end + def github_project_link(path_with_namespace) link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank' end diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 15dd98077c8..f8b4b107513 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -51,7 +51,7 @@ %td = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank" %td.import-target - = "#{repo["owner"]}/#{repo["slug"]}" + = import_project_target(repo['owner'], repo['slug']) %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 54ff1d27c67..bd3be20c4f8 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -45,7 +45,7 @@ %td = github_project_link(repo.full_name) %td.import-target - = repo.full_name + = import_project_target(repo.owner.login, repo.name) %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index fcfc6fd37f4..d31fc2e6adb 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -45,7 +45,7 @@ %td = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" %td.import-target - = repo["path_with_namespace"] + = import_project_target(repo['namespace']['path'], repo['name']) %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do Import diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index 07bf8d2d1c3..1d3c9fbbe2f 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -146,21 +146,42 @@ describe Import::BitbucketController do end context "when a namespace with the Bitbucket user's username doesn't exist" do - it "creates the namespace" do - expect(Gitlab::BitbucketImport::ProjectCreator). - to receive(:new).and_return(double(execute: true)) + context "when current user can create namespaces" do + it "creates the namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) - post :create, format: :js + expect { post :create, format: :js }.to change(Namespace, :count).by(1) + end + + it "takes the new namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, an_instance_of(Group), user, access_params). + and_return(double(execute: true)) - expect(Namespace.where(name: other_username).first).not_to be_nil + post :create, format: :js + end end - it "takes the new namespace" do - expect(Gitlab::BitbucketImport::ProjectCreator). - to receive(:new).with(bitbucket_repo, an_instance_of(Group), user, access_params). - and_return(double(execute: true)) + context "when current user can't create namespaces" do + before do + user.update_attribute(:can_create_group, false) + end - post :create, format: :js + it "doesn't create the namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + expect { post :create, format: :js }.not_to change(Namespace, :count) + end + + it "takes the current user's namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, user.namespace, user, access_params). + and_return(double(execute: true)) + + post :create, format: :js + end end end end diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index 51d59526854..ebfbf54182b 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -181,21 +181,42 @@ describe Import::GithubController do end context "when a namespace with the GitHub user's username doesn't exist" do - it "creates the namespace" do - expect(Gitlab::GithubImport::ProjectCreator). - to receive(:new).and_return(double(execute: true)) + context "when current user can create namespaces" do + it "creates the namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) - post :create, format: :js + expect { post :create, format: :js }.to change(Namespace, :count).by(1) + end + + it "takes the new namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, an_instance_of(Group), user, access_params). + and_return(double(execute: true)) - expect(Namespace.where(name: other_username).first).not_to be_nil + post :create, format: :js + end end - it "takes the new namespace" do - expect(Gitlab::GithubImport::ProjectCreator). - to receive(:new).with(github_repo, an_instance_of(Group), user, access_params). - and_return(double(execute: true)) + context "when current user can't create namespaces" do + before do + user.update_attribute(:can_create_group, false) + end - post :create, format: :js + it "doesn't create the namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + expect { post :create, format: :js }.not_to change(Namespace, :count) + end + + it "takes the current user's namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, user.namespace, user, access_params). + and_return(double(execute: true)) + + post :create, format: :js + end end end end diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index e8cf6aa7767..6f75ebb16c8 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -136,21 +136,42 @@ describe Import::GitlabController do end context "when a namespace with the GitLab.com user's username doesn't exist" do - it "creates the namespace" do - expect(Gitlab::GitlabImport::ProjectCreator). - to receive(:new).and_return(double(execute: true)) + context "when current user can create namespaces" do + it "creates the namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) - post :create, format: :js + expect { post :create, format: :js }.to change(Namespace, :count).by(1) + end + + it "takes the new namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params). + and_return(double(execute: true)) - expect(Namespace.where(name: other_username).first).not_to be_nil + post :create, format: :js + end end - it "takes the new namespace" do - expect(Gitlab::GitlabImport::ProjectCreator). - to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params). - and_return(double(execute: true)) + context "when current user can't create namespaces" do + before do + user.update_attribute(:can_create_group, false) + end - post :create, format: :js + it "doesn't create the namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + expect { post :create, format: :js }.not_to change(Namespace, :count) + end + + it "takes the current user's namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, user.namespace, user, access_params). + and_return(double(execute: true)) + + post :create, format: :js + end end end end diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb index 3391234e9f5..187b891b927 100644 --- a/spec/helpers/import_helper_spec.rb +++ b/spec/helpers/import_helper_spec.rb @@ -1,6 +1,30 @@ require 'rails_helper' describe ImportHelper do + describe '#import_project_target' do + let(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + context 'when current user can create namespaces' do + it 'returns project namespace' do + user.update_attribute(:can_create_group, true) + + expect(helper.import_project_target('asd', 'vim')).to eq 'asd/vim' + end + end + + context 'when current user can not create namespaces' do + it "takes the current user's namespace" do + user.update_attribute(:can_create_group, false) + + expect(helper.import_project_target('asd', 'vim')).to eq "#{user.namespace_path}/vim" + end + end + end + describe '#github_project_link' do context 'when provider does not specify a custom URL' do it 'uses default GitHub URL' do -- cgit v1.2.1 From ddc5b9f7236f8932c32ec9ec1d91338aab22a9e0 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 31 Aug 2016 18:08:52 +0200 Subject: Revert "Add last commit time to repo view" This reverts commit 8581e152ef8fa27b6670760d39b7f06dab5f796b. This creates really difficult conflicts with another MR that was worked on simulataneously. That MR mostly encompassed the aims of this commit. See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5308/commits --- app/assets/stylesheets/pages/tree.scss | 10 ---------- app/views/projects/tree/_tree_content.html.haml | 11 +++++------ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 538f211c65b..9da40fe2b09 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -11,16 +11,6 @@ } } - .last-commit { - max-width: 506px; - - .last-commit-content { - @include str-truncated; - width: calc(100% - 140px); - margin-left: 3px; - } - } - .tree-table { margin-bottom: 0; diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index ca5d2d7722a..558e6146ae9 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -5,17 +5,16 @@ %tr %th Name %th Last Update - %th.hidden-xs.last-commit - Last Commit - .last-commit-content.hidden-sm + %th.hidden-xs + .pull-left Last Commit + .last-commit.hidden-sm.pull-left +   %i.fa.fa-angle-right   %small.light = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" – - = time_ago_with_tooltip(@commit.committed_date) - – - = @commit.full_title + = truncate(@commit.title, length: 50) = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right' - if @path.present? -- cgit v1.2.1 From 52daddc0b7166fc50949db042f4f05488f0b9eae Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 16:20:56 -0300 Subject: Use updated_at as the last updated date when importing from GitHub --- lib/gitlab/github_import/issue_formatter.rb | 6 +--- lib/gitlab/github_import/milestone_formatter.rb | 36 ++++------------------ lib/gitlab/github_import/pull_request_formatter.rb | 11 +------ .../gitlab/github_import/issue_formatter_spec.rb | 5 ++- .../github_import/milestone_formatter_spec.rb | 5 ++- .../github_import/pull_request_formatter_spec.rb | 7 ++--- 6 files changed, 15 insertions(+), 55 deletions(-) diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb index 835ec858b35..07edbe37a13 100644 --- a/lib/gitlab/github_import/issue_formatter.rb +++ b/lib/gitlab/github_import/issue_formatter.rb @@ -12,7 +12,7 @@ module Gitlab author_id: author_id, assignee_id: assignee_id, created_at: raw_data.created_at, - updated_at: updated_at + updated_at: raw_data.updated_at } end @@ -69,10 +69,6 @@ module Gitlab def state raw_data.state == 'closed' ? 'closed' : 'opened' end - - def updated_at - state == 'closed' ? raw_data.closed_at : raw_data.updated_at - end end end end diff --git a/lib/gitlab/github_import/milestone_formatter.rb b/lib/gitlab/github_import/milestone_formatter.rb index 53d4b3102d1..b2fa524cf5b 100644 --- a/lib/gitlab/github_import/milestone_formatter.rb +++ b/lib/gitlab/github_import/milestone_formatter.rb @@ -3,14 +3,14 @@ module Gitlab class MilestoneFormatter < BaseFormatter def attributes { - iid: number, + iid: raw_data.number, project: project, - title: title, - description: description, - due_date: due_date, + title: raw_data.title, + description: raw_data.description, + due_date: raw_data.due_on, state: state, - created_at: created_at, - updated_at: updated_at + created_at: raw_data.created_at, + updated_at: raw_data.updated_at } end @@ -20,33 +20,9 @@ module Gitlab private - def number - raw_data.number - end - - def title - raw_data.title - end - - def description - raw_data.description - end - - def due_date - raw_data.due_on - end - def state raw_data.state == 'closed' ? 'closed' : 'active' end - - def created_at - raw_data.created_at - end - - def updated_at - state == 'closed' ? raw_data.closed_at : raw_data.updated_at - end end end end diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index 04aa3664f64..d9d436d7490 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -20,7 +20,7 @@ module Gitlab author_id: author_id, assignee_id: assignee_id, created_at: raw_data.created_at, - updated_at: updated_at + updated_at: raw_data.updated_at } end @@ -103,15 +103,6 @@ module Gitlab 'opened' end end - - def updated_at - case state - when 'merged' then raw_data.merged_at - when 'closed' then raw_data.closed_at - else - raw_data.updated_at - end - end end end end diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index 0e7ffbe9b8e..d60c4111e99 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -48,8 +48,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do end context 'when issue is closed' do - let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') } - let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) } + let(:raw_data) { double(base_data.merge(state: 'closed')) } it 'returns formatted attributes' do expected = { @@ -62,7 +61,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do author_id: project.creator_id, assignee_id: nil, created_at: created_at, - updated_at: closed_at + updated_at: updated_at } expect(issue.attributes).to eq(expected) diff --git a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb index 5a421e50581..09337c99a07 100644 --- a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb @@ -40,8 +40,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do end context 'when milestone is closed' do - let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') } - let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) } + let(:raw_data) { double(base_data.merge(state: 'closed')) } it 'returns formatted attributes' do expected = { @@ -52,7 +51,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do state: 'closed', due_date: nil, created_at: created_at, - updated_at: closed_at + updated_at: updated_at } expect(formatter.attributes).to eq(expected) diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index b667abf063d..edfc6ad81c6 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -62,8 +62,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do end context 'when pull request is closed' do - let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') } - let(:raw_data) { double(base_data.merge(state: 'closed', closed_at: closed_at)) } + let(:raw_data) { double(base_data.merge(state: 'closed')) } it 'returns formatted attributes' do expected = { @@ -81,7 +80,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do author_id: project.creator_id, assignee_id: nil, created_at: created_at, - updated_at: closed_at + updated_at: updated_at } expect(pull_request.attributes).to eq(expected) @@ -108,7 +107,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do author_id: project.creator_id, assignee_id: nil, created_at: created_at, - updated_at: merged_at + updated_at: updated_at } expect(pull_request.attributes).to eq(expected) -- cgit v1.2.1 From 80c4f1093747797cafaa832130e9781e3774722e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 16:25:28 -0300 Subject: Don't touch Issue/Merge Request when importing GitHub comments --- lib/gitlab/github_import/importer.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 02ffb43d89b..1b2a5eb8f52 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -152,12 +152,14 @@ module Gitlab end def create_comments(issuable, comments) - comments.each do |raw| - begin - comment = CommentFormatter.new(project, raw) - issuable.notes.create!(comment.attributes) - rescue => e - errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + ActiveRecord::Base.no_touching do + comments.each do |raw| + begin + comment = CommentFormatter.new(project, raw) + issuable.notes.create!(comment.attributes) + rescue => e + errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end end end end -- cgit v1.2.1 From 9ef743d1e4b669f15f51ca440b1172856e1d7fff Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 16:34:20 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 08562b1a5a0..65cbcd6059a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,6 +57,7 @@ v 8.12.0 (unreleased) v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) + - Fix sorting issues by "last updated" doesn't work after import from GitHub - Creating an issue through our API now emails label subscribers !5720 - Block concurrent updates for Pipeline - Fix resolving conflicts on forks -- cgit v1.2.1 From eba024366bdaa2d2645857fcb404ad0468b782fa Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 18 Aug 2016 11:18:25 -0500 Subject: Remove redundant js-timeago-pending from user activity log --- CHANGELOG | 1 + app/assets/javascripts/activities.js | 2 +- app/views/events/_event.html.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 08562b1a5a0..40a7237333f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.12.0 (unreleased) - Add last commit time to repo view (ClemMakesApps) - Added project specific enable/disable setting for LFS !5997 - Don't expose a user's token in the `/api/v3/user` API (!6047) + - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Remove redundant pipeline tooltips (ClemMakesApps) diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index 5ea6086ab77..d5e11e22be5 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -12,7 +12,7 @@ } Activities.prototype.updateTooltips = function() { - return gl.utils.localTimeAgo($('.js-timeago', '#activity')); + return gl.utils.localTimeAgo($('.js-timeago', '.content_list')); }; Activities.prototype.reloadActivities = function() { diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 5c318cd3b8b..31fdcc5e21b 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -1,7 +1,7 @@ - if event.visible_to_user?(current_user) .event-item{ class: event_row_class(event) } .event-item-timestamp - #{time_ago_with_tooltip(event.created_at)} + #{time_ago_with_tooltip(event.created_at, skip_js: true)} = cache [event, current_application_settings, "v2.2"] do = author_avatar(event, size: 40) -- cgit v1.2.1 From db5244ab9054610ae13b44a0d508a339971bdcca Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 31 Aug 2016 18:14:09 +0200 Subject: Fix repo page ui issues, show full last commit message in repo view. Integrates changes from ClemMakesApps' commit: 8581e152, although it was reverted to avoid frustrating conflicts. See MR: !5530 (diffs) --- CHANGELOG | 1 + app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/tree.scss | 21 ++++++++++++++++++--- app/views/projects/refs/logs_tree.js.haml | 4 ++-- app/views/projects/tree/_blob_item.html.haml | 6 +++--- app/views/projects/tree/_tree_content.html.haml | 9 +++++---- app/views/projects/tree/_tree_item.html.haml | 6 +++--- 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6b09dfa8d74..f094a707bfa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,7 @@ v 8.12.0 (unreleased) - Adds response mime type to transaction metric action when it's not HTML - Fix hover leading space bug in pipeline graph !5980 - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 + - Fix repository page ui issues v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 5da390118c6..abfae2e00fb 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -35,6 +35,7 @@ $gl-icon-color: $gl-placeholder-color; $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-gray-dark: #313236; +$gl-gray-light: $gl-placeholder-color; $gl-header-color: $gl-title-color; /* diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 9da40fe2b09..e330300f9a1 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -22,6 +22,15 @@ line-height: 21px; } + .last-commit { + @include str-truncated(60%); + } + + .commit-history-link-spacer { + margin: 0 10px; + color: $table-border-color; + } + &:hover { td { background-color: $row-hover; @@ -77,11 +86,17 @@ } } - .tree_commit { - color: $gl-gray; + .tree-time-ago { + min-width: 135px; + color: $gl-gray-light; + } + + .tree-commit { + max-width: 320px; + color: $gl-gray-light; .tree-commit-link { - color: $gl-gray; + color: $gl-gray-light; &:hover { text-decoration: underline; diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index 8ee2aef0e61..1141168f037 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -5,8 +5,8 @@ :plain var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}"); - row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}'); - row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}'); + row.find("td.tree-time-ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}'); + row.find("td.tree-commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}'); - if @more_log_url :plain diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index a3a4dba3fa4..ee417b58cbf 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -4,6 +4,6 @@ - file_name = blob_item.name = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do %span.str-truncated= file_name - %td.tree_time_ago.cgray - = render 'projects/tree/spinner' - %td.hidden-xs.tree_commit + %td.hidden-xs.tree-commit + %td.tree-time-ago.cgray.text-right + = render 'projects/tree/spinner' \ No newline at end of file diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 558e6146ae9..0f7d629ab98 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -4,7 +4,6 @@ %thead %tr %th Name - %th Last Update %th.hidden-xs .pull-left Last Commit .last-commit.hidden-sm.pull-left @@ -14,9 +13,11 @@ %small.light = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" – - = truncate(@commit.title, length: 50) - = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right' - + = time_ago_with_tooltip(@commit.committed_date) + = @commit.full_title + %small.commit-history-link-spacer | + = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'commit-history-link' + %th.text-right Last Update - if @path.present? %tr.tree-item %td.tree-item-file-name diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index 9577696fc0d..1ccef6d52ab 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -4,6 +4,6 @@ - path = flatten_tree(tree_item) = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do %span.str-truncated= path - %td.tree_time_ago.cgray - = render 'projects/tree/spinner' - %td.hidden-xs.tree_commit + %td.hidden-xs.tree-commit + %td.tree-time-ago.text-right + = render 'projects/tree/spinner' \ No newline at end of file -- cgit v1.2.1 From 7b09a27e9b27c4f62cfffdc0371a01e5f191980f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 29 Aug 2016 13:21:37 -0500 Subject: Remove prefixes from transition CSS property --- CHANGELOG | 1 + app/assets/stylesheets/framework/mixins.scss | 8 -------- app/assets/stylesheets/framework/selects.scss | 2 +- app/assets/stylesheets/pages/search.scss | 4 ++-- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fec019e5abb..c213ca18bfb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.12.0 (unreleased) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import - Fix inconsistent background color for filter input field (ClemMakesApps) + - Remove prefixes from transition CSS property (ClemMakesApps) - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Remove unused mixins (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 396a37bab6e..62dc2cb3fdc 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -9,14 +9,6 @@ border-radius: $radius; } -@mixin transition($transition) { - -webkit-transition: $transition; - -moz-transition: $transition; - -ms-transition: $transition; - -o-transition: $transition; - transition: $transition; -} - /** * Prefilled mixins * Mixins with fixed values diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index b2e22b60440..c75dacf95d9 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -151,7 +151,7 @@ background-position: right 0 bottom 6px; border: 1px solid $input-border; @include border-radius($border-radius-default); - @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s); + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; &:focus { border-color: $input-border-focus; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index c9d436d72ba..436fb00ba2e 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -80,7 +80,7 @@ .search-icon { @extend .fa-search; - @include transition(color .15s); + transition: color 0.15s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -125,7 +125,7 @@ } .location-badge { - @include transition(all .15s); + transition: all 0.15s; background-color: $location-badge-active-bg; color: $white-light; } -- cgit v1.2.1 From 97b69862ad50936ad0700b23c485a38b637469a7 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Wed, 31 Aug 2016 12:35:23 +0100 Subject: add specs for tags finder --- app/controllers/projects/tags_controller.rb | 5 +- app/finders/tags_finder.rb | 29 +++++++++++ app/views/projects/tags/index.html.haml | 5 +- spec/finders/tags_finder_spec.rb | 79 +++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 app/finders/tags_finder.rb create mode 100644 spec/finders/tags_finder_spec.rb diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 3e5de2e0d3e..6ea8ee62bc5 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -1,5 +1,6 @@ class Projects::TagsController < Projects::ApplicationController include SortingHelper + # Authorize before_action :require_non_empty_project before_action :authorize_download_code! @@ -7,7 +8,9 @@ class Projects::TagsController < Projects::ApplicationController before_action :authorize_admin_project!, only: [:destroy] def index - @sort = params[:sort] || 'name' + params[:sort] = params[:sort].presence || 'name' + + @sort = params[:sort] @tags = TagsFinder.new(@repository, params).execute @tags = Kaminari.paginate_array(@tags).page(params[:page]) diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb new file mode 100644 index 00000000000..b474f0805dc --- /dev/null +++ b/app/finders/tags_finder.rb @@ -0,0 +1,29 @@ +class TagsFinder + def initialize(repository, params) + @repository = repository + @params = params + end + + def execute + tags = @repository.tags_sorted_by(sort) + filter_by_name(tags) + end + + private + + def sort + @params[:sort].presence + end + + def search + @params[:search].presence + end + + def filter_by_name(tags) + if search + tags.select { |tag| tag.name.include?(search) } + else + tags + end + end +end diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 31a023f24cf..6adbe9351dc 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -12,7 +12,8 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } - %span.light= @sort.humanize + %span.light + = @sort.humanize %b.caret %ul.dropdown-menu.dropdown-menu-align-right %li @@ -22,7 +23,7 @@ = sort_title_recently_updated = link_to filter_tags_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated - - if can? current_user, :push_code, @project + - if can?(current_user, :push_code, @project) = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do New tag diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb new file mode 100644 index 00000000000..2ac810e478a --- /dev/null +++ b/spec/finders/tags_finder_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe TagsFinder do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:repository) { project.repository } + + describe '#execute' do + context 'sort only' do + it 'sorts by name' do + tags_finder = described_class.new(repository, {}) + + result = tags_finder.execute + + expect(result.first.name).to eq("v1.0.0") + end + + it 'sorts by recently_updated' do + tags_finder = described_class.new(repository, { sort: 'updated_desc' }) + + result = tags_finder.execute + recently_updated_tag = repository.tags.max do |a, b| + repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date + end + + expect(result.first.name).to eq(recently_updated_tag.name) + end + + it 'sorts by last_updated' do + tags_finder = described_class.new(repository, { sort: 'updated_asc' }) + + result = tags_finder.execute + + expect(result.first.name).to eq('v1.0.0') + end + end + + context 'filter only' do + it 'filters tags by name' do + tags_finder = described_class.new(repository, { search: '1.0.0' }) + + result = tags_finder.execute + + expect(result.first.name).to eq('v1.0.0') + expect(result.count).to eq(1) + end + + it 'does not find any tags with that name' do + tags_finder = described_class.new(repository, { search: 'hey' }) + + result = tags_finder.execute + + expect(result.count).to eq(0) + end + end + + context 'filter and sort' do + it 'filters tags by name and sorts by recently_updated' do + params = { sort: 'updated_desc', search: 'v1' } + tags_finder = described_class.new(repository, params) + + result = tags_finder.execute + + expect(result.first.name).to eq('v1.1.0') + expect(result.count).to eq(2) + end + + it 'filters tags by name and sorts by last_updated' do + params = { sort: 'updated_asc', search: 'v1' } + tags_finder = described_class.new(repository, params) + + result = tags_finder.execute + + expect(result.first.name).to eq('v1.0.0') + expect(result.count).to eq(2) + end + end + end +end -- cgit v1.2.1 From e293ffd48fb16c8ad15c066cfbbe1dcead7c52e0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 14:34:37 -0300 Subject: Refactoring Import::BaseController#find_or_create_namespace --- app/assets/javascripts/importer_status.js | 15 +++++++++------ app/controllers/import/base_controller.rb | 19 +++++++------------ app/controllers/import/bitbucket_controller.rb | 12 +++++++----- app/controllers/import/github_controller.rb | 8 ++++++-- app/controllers/import/gitlab_controller.rb | 8 ++++++-- app/views/import/base/create.js.haml | 21 +-------------------- app/views/import/base/unauthorized.js.haml | 14 ++++++++++++++ app/views/import/bitbucket/deploy_key.js.haml | 3 +++ 8 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 app/views/import/base/unauthorized.js.haml create mode 100644 app/views/import/bitbucket/deploy_key.js.haml diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 0f840821f53..9efad1ce943 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -10,21 +10,24 @@ ImporterStatus.prototype.initStatusPage = function() { $('.js-add-to-import').off('click').on('click', (function(_this) { return function(e) { - var $btn, $namespace_input, $target_field, $tr, id, new_namespace; + var $btn, $namespace_input, $target_field, $tr, id, target_namespace; $btn = $(e.currentTarget); $tr = $btn.closest('tr'); $target_field = $tr.find('.import-target'); $namespace_input = $target_field.find('input'); id = $tr.attr('id').replace('repo_', ''); - new_namespace = null; + target_namespace = null; + if ($namespace_input.length > 0) { - new_namespace = $namespace_input.prop('value'); - $target_field.empty().append(new_namespace + "/" + ($target_field.data('project_name'))); + target_namespace = $namespace_input.prop('value'); + $target_field.empty().append(target_namespace + "/" + ($target_field.data('project_name'))); } + $btn.disable().addClass('is-loading'); + return $.post(_this.import_url, { repo_id: id, - new_namespace: new_namespace + target_namespace: target_namespace }, { dataType: 'script' }); @@ -70,7 +73,7 @@ if ($('.js-importer-status').length) { var jobsImportPath = $('.js-importer-status').data('jobs-import-path'); var importPath = $('.js-importer-status').data('import-path'); - + new ImporterStatus(jobsImportPath, importPath); } }); diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index 1ca33bc5d22..256c41e6145 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -2,21 +2,16 @@ class Import::BaseController < ApplicationController private def find_or_create_namespace(name, owner) - begin - @target_namespace = params[:new_namespace].presence || name - @target_namespace = current_user.namespace_path if name == owner || !current_user.can_create_group? + return current_user.namespace if name == owner + return current_user.namespace unless current_user.can_create_group? - namespace = Group.create!(name: @target_namespace, path: @target_namespace, owner: current_user) + begin + name = params[:target_namespace].presence || name + namespace = Group.create!(name: name, path: name, owner: current_user) namespace.add_owner(current_user) + namespace rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid - namespace = Namespace.find_by_path_or_name(@target_namespace) - - unless current_user.can?(:create_projects, namespace) - @already_been_taken = true - return false - end + Namespace.find_by_path_or_name(name) end - - namespace end end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 94e213b8743..6ea54744da8 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -38,15 +38,17 @@ class Import::BitbucketController < Import::BaseController @repo_id = params[:repo_id].to_s repo = client.project(@repo_id.gsub('___', '/')) @project_name = repo['slug'] - namespace = find_or_create_namespace(repo['owner'], client.user['user']['username']) || (render and return) + @target_namespace = find_or_create_namespace(repo['owner'], client.user['user']['username']) unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute - @access_denied = true - render - return + render 'deploy_key' and return end - @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute + if current_user.can?(:create_projects, @target_namespace) + @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute + else + render 'unauthorized' + end end private diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 4047e62efa2..8c6bdd16383 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -41,9 +41,13 @@ class Import::GithubController < Import::BaseController @repo_id = params[:repo_id].to_i repo = client.repo(@repo_id) @project_name = repo.name - namespace = find_or_create_namespace(repo.owner.login, client.user.login) || (render and return) + @target_namespace = find_or_create_namespace(repo.owner.login, client.user.login) - @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute + if current_user.can?(:create_projects, @target_namespace) + @project = Gitlab::GithubImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute + else + render 'unauthorized' + end end private diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index bc967e55ab1..73837ffbe67 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -27,9 +27,13 @@ class Import::GitlabController < Import::BaseController @repo_id = params[:repo_id].to_i repo = client.project(@repo_id) @project_name = repo['name'] - namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username']) || (render and return) + @target_namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username']) - @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute + if current_user.can?(:create_projects, @target_namespace) + @project = Gitlab::GitlabImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute + else + render 'unauthorized' + end end private diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index 804ad88468f..8e929538351 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -1,23 +1,4 @@ -- if @already_been_taken - :plain - tr = $("tr#repo_#{@repo_id}") - target_field = tr.find(".import-target") - import_button = tr.find(".btn-import") - origin_target = target_field.text() - project_name = "#{@project_name}" - origin_namespace = "#{@target_namespace}" - target_field.empty() - target_field.append("

              This namespace already been taken! Please choose another one

              ") - target_field.append("") - target_field.append("/" + project_name) - target_field.data("project_name", project_name) - target_field.find('input').prop("value", origin_namespace) - import_button.enable().removeClass('is-loading') -- elsif @access_denied - :plain - job = $("tr#repo_#{@repo_id}") - job.find(".import-actions").html("

              Access denied! Please verify you can add deploy keys to this repository.

              ") -- elsif @project.persisted? +- if @project.persisted? :plain job = $("tr#repo_#{@repo_id}") job.attr("id", "project_#{@project.id}") diff --git a/app/views/import/base/unauthorized.js.haml b/app/views/import/base/unauthorized.js.haml new file mode 100644 index 00000000000..36f8069c1f7 --- /dev/null +++ b/app/views/import/base/unauthorized.js.haml @@ -0,0 +1,14 @@ +:plain + tr = $("tr#repo_#{@repo_id}") + target_field = tr.find(".import-target") + import_button = tr.find(".btn-import") + origin_target = target_field.text() + project_name = "#{@project_name}" + origin_namespace = "#{@target_namespace.path}" + target_field.empty() + target_field.append("

              This namespace has already been taken! Please choose another one.

              ") + target_field.append("") + target_field.append("/" + project_name) + target_field.data("project_name", project_name) + target_field.find('input').prop("value", origin_namespace) + import_button.enable().removeClass('is-loading') diff --git a/app/views/import/bitbucket/deploy_key.js.haml b/app/views/import/bitbucket/deploy_key.js.haml new file mode 100644 index 00000000000..81b34ab5c9d --- /dev/null +++ b/app/views/import/bitbucket/deploy_key.js.haml @@ -0,0 +1,3 @@ +:plain + job = $("tr#repo_#{@repo_id}") + job.find(".import-actions").html("

              Access denied! Please verify you can add deploy keys to this repository.

              ") -- cgit v1.2.1 From 03fe9de99ce62fe8328a749040b3910a55c69184 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 19:30:48 -0300 Subject: Update CHANGELOG --- CHANGELOG | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 427b40121ea..95d1504e982 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -55,11 +55,9 @@ v 8.11.4 (unreleased) - Creating an issue through our API now emails label subscribers !5720 - Fix resolving conflicts on forks - Fix diff commenting on merge requests created prior to 8.10 - -v 8.11.4 (unreleased) + - Don't create groups for unallowed users when importing projects - Fix issue boards leak private label names and descriptions -v 8.11.3 (unreleased) v 8.11.3 - Do not enforce using hash with hidden key in CI configuration. !6079 - Allow system info page to handle case where info is unavailable -- cgit v1.2.1 From 6a58af3a4a1c3122d57238505898f61c40bf7b54 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 25 Aug 2016 18:34:01 -0500 Subject: Add BroadcastMessage API implementation --- CHANGELOG | 1 + lib/api/api.rb | 1 + lib/api/broadcast_messages.rb | 99 +++++++++++++++ lib/api/entities.rb | 5 + spec/requests/api/broadcast_messages_spec.rb | 180 +++++++++++++++++++++++++++ 5 files changed, 286 insertions(+) create mode 100644 lib/api/broadcast_messages.rb create mode 100644 spec/requests/api/broadcast_messages_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 2b727491760..e3935038517 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.12.0 (unreleased) - Remove Gitorious import - Fix inconsistent background color for filter input field (ClemMakesApps) - Add Sentry logging to API calls + - Add BroadcastMessage API - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Remove unused mixins (ClemMakesApps) - Add search to all issue board lists diff --git a/lib/api/api.rb b/lib/api/api.rb index 4602e627fdb..e14464c1b0d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -31,6 +31,7 @@ module API mount ::API::AccessRequests mount ::API::AwardEmoji mount ::API::Branches + mount ::API::BroadcastMessages mount ::API::Builds mount ::API::CommitStatuses mount ::API::Commits diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb new file mode 100644 index 00000000000..fb2a4148011 --- /dev/null +++ b/lib/api/broadcast_messages.rb @@ -0,0 +1,99 @@ +module API + class BroadcastMessages < Grape::API + before { authenticate! } + before { authenticated_as_admin! } + + resource :broadcast_messages do + helpers do + def find_message + BroadcastMessage.find(params[:id]) + end + end + + desc 'Get all broadcast messages' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::BroadcastMessage + end + params do + optional :page, type: Integer, desc: 'Current page number' + optional :per_page, type: Integer, desc: 'Number of messages per page' + end + get do + messages = BroadcastMessage.all + + present paginate(messages), with: Entities::BroadcastMessage + end + + desc 'Create a broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::BroadcastMessage + end + params do + requires :message, type: String, desc: 'Message to display' + optional :starts_at, type: DateTime, desc: 'Starting time', default: -> { Time.zone.now } + optional :ends_at, type: DateTime, desc: 'Ending time', default: -> { 1.hour.from_now } + optional :color, type: String, desc: 'Background color' + optional :font, type: String, desc: 'Foreground color' + end + post do + create_params = declared(params, include_missing: false).to_h + message = BroadcastMessage.create(create_params) + + if message.persisted? + present message, with: Entities::BroadcastMessage + else + render_validation_error!(message) + end + end + + desc 'Get a specific broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::BroadcastMessage + end + params do + requires :id, type: Integer, desc: 'Broadcast message ID' + end + get ':id' do + message = find_message + + present message, with: Entities::BroadcastMessage + end + + desc 'Update a broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::BroadcastMessage + end + params do + requires :id, type: Integer, desc: 'Broadcast message ID' + optional :message, type: String, desc: 'Message to display' + optional :starts_at, type: DateTime, desc: 'Starting time' + optional :ends_at, type: DateTime, desc: 'Ending time' + optional :color, type: String, desc: 'Background color' + optional :font, type: String, desc: 'Foreground color' + end + put ':id' do + message = find_message + update_params = declared(params, include_missing: false).to_h + + if message.update(update_params) + present message, with: Entities::BroadcastMessage + else + render_validation_error!(message) + end + end + + desc 'Delete a broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::BroadcastMessage + end + params do + requires :id, type: Integer, desc: 'Broadcast message ID' + end + delete ':id' do + message = find_message + + present message.destroy, with: Entities::BroadcastMessage + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e3a8ff6de80..fe7468dd681 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -575,5 +575,10 @@ module API class Template < Grape::Entity expose :name, :content end + + class BroadcastMessage < Grape::Entity + expose :id, :message, :starts_at, :ends_at, :color, :font + expose :active?, as: :active + end end end diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb new file mode 100644 index 00000000000..7c9078b2864 --- /dev/null +++ b/spec/requests/api/broadcast_messages_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' + +describe API::BroadcastMessages, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:admin) { create(:admin) } + + describe 'GET /broadcast_messages' do + it 'returns a 401 for anonymous users' do + get api('/broadcast_messages') + + expect(response).to have_http_status(401) + end + + it 'returns a 403 for users' do + get api('/broadcast_messages', user) + + expect(response).to have_http_status(403) + end + + it 'returns an Array of BroadcastMessages for admins' do + create(:broadcast_message) + + get api('/broadcast_messages', admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_kind_of(Array) + expect(json_response.first.keys) + .to match_array(%w(id message starts_at ends_at color font active)) + end + end + + describe 'GET /broadcast_messages/:id' do + let!(:message) { create(:broadcast_message) } + + it 'returns a 401 for anonymous users' do + get api("/broadcast_messages/#{message.id}") + + expect(response).to have_http_status(401) + end + + it 'returns a 403 for users' do + get api("/broadcast_messages/#{message.id}", user) + + expect(response).to have_http_status(403) + end + + it 'returns the specified message for admins' do + get api("/broadcast_messages/#{message.id}", admin) + + expect(response).to have_http_status(200) + expect(json_response['id']).to eq message.id + expect(json_response.keys) + .to match_array(%w(id message starts_at ends_at color font active)) + end + end + + describe 'POST /broadcast_messages' do + it 'returns a 401 for anonymous users' do + post api('/broadcast_messages'), attributes_for(:broadcast_message) + + expect(response).to have_http_status(401) + end + + it 'returns a 403 for users' do + post api('/broadcast_messages', user), attributes_for(:broadcast_message) + + expect(response).to have_http_status(403) + end + + context 'as an admin' do + it 'requires the `message` parameter' do + attrs = attributes_for(:broadcast_message) + attrs.delete(:message) + + post api('/broadcast_messages', admin), attrs + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq 'message is missing' + end + + it 'defines sane default start and end times' do + time = Time.zone.parse('2016-07-02 10:11:12') + travel_to(time) do + post api('/broadcast_messages', admin), message: 'Test message' + + expect(response).to have_http_status(201) + expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z' + expect(json_response['ends_at']).to eq '2016-07-02T11:11:12.000Z' + end + end + + it 'accepts a custom background and foreground color' do + attrs = attributes_for(:broadcast_message, color: '#000000', font: '#cecece') + + post api('/broadcast_messages', admin), attrs + + expect(response).to have_http_status(201) + expect(json_response['color']).to eq attrs[:color] + expect(json_response['font']).to eq attrs[:font] + end + end + end + + describe 'PUT /broadcast_messages/:id' do + let!(:message) { create(:broadcast_message) } + + it 'returns a 401 for anonymous users' do + put api("/broadcast_messages/#{message.id}"), + attributes_for(:broadcast_message) + + expect(response).to have_http_status(401) + end + + it 'returns a 403 for users' do + put api("/broadcast_messages/#{message.id}", user), + attributes_for(:broadcast_message) + + expect(response).to have_http_status(403) + end + + context 'as an admin' do + it 'accepts new background and foreground colors' do + attrs = { color: '#000000', font: '#cecece' } + + put api("/broadcast_messages/#{message.id}", admin), attrs + + expect(response).to have_http_status(200) + expect(json_response['color']).to eq attrs[:color] + expect(json_response['font']).to eq attrs[:font] + end + + it 'accepts new start and end times' do + time = Time.zone.parse('2016-07-02 10:11:12') + travel_to(time) do + attrs = { starts_at: Time.zone.now, ends_at: 3.hours.from_now } + + put api("/broadcast_messages/#{message.id}", admin), attrs + + expect(response).to have_http_status(200) + expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z' + expect(json_response['ends_at']).to eq '2016-07-02T13:11:12.000Z' + end + end + + it 'accepts a new message' do + attrs = { message: 'new message' } + + put api("/broadcast_messages/#{message.id}", admin), attrs + + expect(response).to have_http_status(200) + expect { message.reload }.to change { message.message }.to('new message') + end + end + end + + describe 'DELETE /broadcast_messages/:id' do + let!(:message) { create(:broadcast_message) } + + it 'returns a 401 for anonymous users' do + delete api("/broadcast_messages/#{message.id}"), + attributes_for(:broadcast_message) + + expect(response).to have_http_status(401) + end + + it 'returns a 403 for users' do + delete api("/broadcast_messages/#{message.id}", user), + attributes_for(:broadcast_message) + + expect(response).to have_http_status(403) + end + + it 'deletes the broadcast message for admins' do + expect { delete api("/broadcast_messages/#{message.id}", admin) } + .to change { BroadcastMessage.count }.by(-1) + end + end +end -- cgit v1.2.1 From 9d4af1b14068aa77848b982372ddf9e11567ae5f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 30 Aug 2016 16:47:44 -0300 Subject: Add BroadcastMessage API documentation --- doc/api/broadcast_messages.md | 158 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 doc/api/broadcast_messages.md diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md new file mode 100644 index 00000000000..c3a9207a3ae --- /dev/null +++ b/doc/api/broadcast_messages.md @@ -0,0 +1,158 @@ +# Broadcast Messages + +> **Note:** This feature was introduced in GitLab 8.12. + +The broadcast message API is only accessible to administrators. All requests by +guests will respond with `401 Unauthorized`, and all requests by normal users +will respond with `403 Forbidden`. + +## Get all broadcast messages + +``` +GET /broadcast_messages +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages +``` + +Example response: + +```json +[ + { + "message":"Example broadcast message", + "starts_at":"2016-08-24T23:21:16.078Z", + "ends_at":"2016-08-26T23:21:16.080Z", + "color":"#E75E40", + "font":"#FFFFFF", + "id":1, + "active": false + } +] +``` + +## Get a specific broadcast message + +``` +GET /broadcast_messages/:id +``` + +| Attribute | Type | Required | Description | +| ----------- | -------- | -------- | ------------------------- | +| `id` | integer | yes | Broadcast message ID | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1 +``` + +Example response: + +```json +{ + "message":"Deploy in progress", + "starts_at":"2016-08-24T23:21:16.078Z", + "ends_at":"2016-08-26T23:21:16.080Z", + "color":"#cecece", + "font":"#FFFFFF", + "id":1, + "active":false +} +``` + +## Create a broadcast message + +Responds with `400 Bad request` when the `message` parameter is missing or the +`color` or `font` values are invalid, and `201 Created` when the broadcast +message was successfully created. + +``` +POST /broadcast_messages +``` + +| Attribute | Type | Required | Description | +| ----------- | -------- | -------- | ---------------------------------------------------- | +| `message` | string | yes | Message to display | +| `starts_at` | datetime | no | Starting time (defaults to current time) | +| `ends_at` | datetime | no | Ending time (defaults to one hour from current time) | +| `color` | string | no | Background color hex code | +| `font` | string | no | Foreground color hex code | + +```bash +curl --data "message=Deploy in progress&color=#cecece" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages +``` + +Example response: + +```json +{ + "message":"Deploy in progress", + "starts_at":"2016-08-26T00:41:35.060Z", + "ends_at":"2016-08-26T01:41:35.060Z", + "color":"#cecece", + "font":"#FFFFFF", + "id":1, + "active": true +} +``` + +## Update a broadcast message + +``` +PUT /broadcast_messages/:id +``` + +| Attribute | Type | Required | Description | +| ----------- | -------- | -------- | ------------------------- | +| `id` | integer | yes | Broadcast message ID | +| `message` | string | no | Message to display | +| `starts_at` | datetime | no | Starting time | +| `ends_at` | datetime | no | Ending time | +| `color` | string | no | Background color hex code | +| `font` | string | no | Foreground color hex code | + +```bash +curl --request PUT --data "message=Update message&color=#000" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1 +``` + +Example response: + +```json +{ + "message":"Update message", + "starts_at":"2016-08-26T00:41:35.060Z", + "ends_at":"2016-08-26T01:41:35.060Z", + "color":"#000", + "font":"#FFFFFF", + "id":1, + "active": true +} +``` + +## Delete a broadcast message + +``` +DELETE /broadcast_messages/:id +``` + +| Attribute | Type | Required | Description | +| ----------- | -------- | -------- | ------------------------- | +| `id` | integer | yes | Broadcast message ID | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/broadcast_messages/1 +``` + +Example response: + +```json +{ + "message":"Update message", + "starts_at":"2016-08-26T00:41:35.060Z", + "ends_at":"2016-08-26T01:41:35.060Z", + "color":"#000", + "font":"#FFFFFF", + "id":1, + "active": true +} +``` -- cgit v1.2.1 From 9b376e772edda214e189752c13d831bae7f1088d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 25 Aug 2016 11:16:28 -0300 Subject: GitHub importer use default project visibility for non-private projects --- lib/gitlab/github_import/project_creator.rb | 2 +- .../gitlab/github_import/project_creator_spec.rb | 54 ++++++++++++++++------ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index f4221003db5..8237fe104d6 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -17,7 +17,7 @@ module Gitlab path: repo.name, description: repo.description, namespace_id: namespace.id, - visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, + visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility, import_type: "github", import_source: repo.full_name, import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"), diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index 0f363b8b0aa..014ee462e5c 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -2,33 +2,59 @@ require 'spec_helper' describe Gitlab::GithubImport::ProjectCreator, lib: true do let(:user) { create(:user) } + let(:namespace) { create(:group, owner: user) } + let(:repo) do OpenStruct.new( login: 'vim', name: 'vim', - private: true, full_name: 'asd/vim', - clone_url: "https://gitlab.com/asd/vim.git", - owner: OpenStruct.new(login: "john") + clone_url: 'https://gitlab.com/asd/vim.git' ) end - let(:namespace) { create(:group, owner: user) } - let(:token) { "asdffg" } - let(:access_params) { { github_access_token: token } } + + subject(:service) { described_class.new(repo, namespace, user, github_access_token: 'asdffg') } before do namespace.add_owner(user) + allow_any_instance_of(Project).to receive(:add_import_job) end - it 'creates project' do - allow_any_instance_of(Project).to receive(:add_import_job) + describe '#execute' do + it 'creates a project' do + expect { service.execute }.to change(Project, :count).by(1) + end + + it 'handle GitHub credentials' do + project = service.execute + + expect(project.import_url).to eq('https://asdffg@gitlab.com/asd/vim.git') + expect(project.safe_import_url).to eq('https://*****@gitlab.com/asd/vim.git') + expect(project.import_data.credentials).to eq(user: 'asdffg', password: nil) + end + + context 'when Github project is private' do + it 'sets project visibility to private' do + repo.private = true + + project = service.execute + + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when Github project is public' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL) + end + + it 'sets project visibility to the default project visibility' do + repo.private = false - project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user, access_params) - project = project_creator.execute + project = service.execute - expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") - expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git") - expect(project.import_data.credentials).to eq(user: "asdffg", password: nil) - expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end end end -- cgit v1.2.1 From a41b5e5bdc3ec5e8c44db5824665593799c530b6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 31 Aug 2016 18:29:00 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 13ec1bb885f..9837b2edb9d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -63,6 +63,7 @@ v 8.12.0 (unreleased) v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Fix sorting issues by "last updated" doesn't work after import from GitHub + - GitHub importer use default project visibility for non-private projects - Creating an issue through our API now emails label subscribers !5720 - Block concurrent updates for Pipeline - Fix resolving conflicts on forks -- cgit v1.2.1 From 44340fede26278a7f9d70e0fb063df9673cfc551 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 12 Aug 2016 16:05:10 -0300 Subject: Fix confidential issues should not be passed to Webhooks --- app/services/issues/base_service.rb | 2 ++ spec/services/issues/close_service_spec.rb | 17 +++++++++-- spec/services/issues/create_service_spec.rb | 9 ++++++ spec/services/issues/reopen_service_spec.rb | 26 ++++++++++++----- spec/services/issues/update_service_spec.rb | 45 +++++++++++++++++++++-------- 5 files changed, 77 insertions(+), 22 deletions(-) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 089b0f527e2..241efc44d36 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -14,6 +14,8 @@ module Issues end def execute_hooks(issue, action = 'open') + return if issue.confidential? + issue_data = hook_data(issue, action) issue.project.execute_hooks(issue_data, :issue_hooks) issue.project.execute_services(issue_data, :issue_hooks) diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index aff022a573e..229c8eb5b5b 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -18,7 +18,7 @@ describe Issues::CloseService, services: true do context "valid params" do before do perform_enqueued_jobs do - @issue = described_class.new(project, user, {}).execute(issue) + @issue = described_class.new(project, user).execute(issue) end end @@ -53,10 +53,21 @@ describe Issues::CloseService, services: true do end end - context "external issue tracker" do + context 'when issue is confidential' do + it 'does not execute hooks' do + issue = create(:issue, :confidential, project: project) + + expect(project).not_to receive(:execute_hooks) + expect(project).not_to receive(:execute_services) + + described_class.new(project, user).execute(issue) + end + end + + context 'external issue tracker' do before do allow(project).to receive(:default_issues_tracker?).and_return(false) - @issue = described_class.new(project, user, {}).execute(issue) + @issue = described_class.new(project, user).execute(issue) end it { expect(@issue).to be_valid } diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index fcc3c0a00bd..81beca47738 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -72,6 +72,15 @@ describe Issues::CreateService, services: true do expect(issue.milestone).not_to eq milestone end end + + it 'does not execute hooks when issue is confidential' do + opts = { title: 'Title', description: 'Description', confidential: true } + + expect(project).not_to receive(:execute_hooks) + expect(project).not_to receive(:execute_services) + + described_class.new(project, user, opts).execute + end end it_behaves_like 'new issuable record that supports slash commands' diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 34a89fcd4e1..83c72e64c87 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -1,17 +1,15 @@ require 'spec_helper' describe Issues::ReopenService, services: true do - let(:guest) { create(:user) } - let(:issue) { create(:issue, :closed) } - let(:project) { issue.project } - - before do - project.team << [guest, :guest] - end + let(:project) { create(:empty_project) } + let(:issue) { create(:issue, :closed, project: project) } describe '#execute' do context 'current user is not authorized to reopen issue' do before do + guest = create(:user) + project.team << [guest, :guest] + perform_enqueued_jobs do @issue = described_class.new(project, guest).execute(issue) end @@ -21,5 +19,19 @@ describe Issues::ReopenService, services: true do expect(@issue).to be_closed end end + + context 'when issue is confidential' do + it 'does not execute hooks' do + user = create(:user) + project.team << [user, :master] + + issue = create(:issue, :confidential, :closed, project: project) + + expect(project).not_to receive(:execute_hooks) + expect(project).not_to receive(:execute_services) + + described_class.new(project, user).execute(issue) + end + end end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 0313f424463..a4bd19d317b 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -28,6 +28,11 @@ describe Issues::UpdateService, services: true do end end + def update_issue(opts) + @issue = described_class.new(project, user, opts).execute(issue) + @issue.reload + end + context "valid params" do before do opts = { @@ -35,12 +40,11 @@ describe Issues::UpdateService, services: true do description: 'Also please fix', assignee_id: user2.id, state_event: 'close', - label_ids: [label.id], - confidential: true + label_ids: [label.id] } perform_enqueued_jobs do - @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue = described_class.new(project, user, opts).execute(issue) end @issue.reload @@ -81,18 +85,35 @@ describe Issues::UpdateService, services: true do expect(note).not_to be_nil expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**' end + end + + context 'when issue turns confidential' do + let(:opts) do + { + title: 'New title', + description: 'Also please fix', + assignee_id: user2.id, + state_event: 'close', + label_ids: [label.id], + confidential: true + } + end it 'creates system note about confidentiality change' do + update_issue({ confidential: true }) + note = find_note('Made the issue confidential') expect(note).not_to be_nil expect(note.note).to eq 'Made the issue confidential' end - end - def update_issue(opts) - @issue = Issues::UpdateService.new(project, user, opts).execute(issue) - @issue.reload + it 'does not execute hooks' do + expect(project).not_to receive(:execute_hooks) + expect(project).not_to receive(:execute_services) + + update_issue({ confidential: true }) + end end context 'todos' do @@ -176,7 +197,7 @@ describe Issues::UpdateService, services: true do opts = { label_ids: [label.id] } perform_enqueued_jobs do - @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue = described_class.new(project, user, opts).execute(issue) end should_email(subscriber) @@ -190,7 +211,7 @@ describe Issues::UpdateService, services: true do opts = { label_ids: [label.id, label2.id] } perform_enqueued_jobs do - @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue = described_class.new(project, user, opts).execute(issue) end should_not_email(subscriber) @@ -201,7 +222,7 @@ describe Issues::UpdateService, services: true do opts = { label_ids: [label2.id] } perform_enqueued_jobs do - @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue = described_class.new(project, user, opts).execute(issue) end should_not_email(subscriber) @@ -210,7 +231,7 @@ describe Issues::UpdateService, services: true do end end - context 'when Issue has tasks' do + context 'when issue has tasks' do before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) } it { expect(@issue.tasks?).to eq(true) } @@ -277,7 +298,7 @@ describe Issues::UpdateService, services: true do context 'updating labels' do let(:label3) { create(:label, project: project) } - let(:result) { Issues::UpdateService.new(project, user, params).execute(issue).reload } + let(:result) { described_class.new(project, user, params).execute(issue).reload } context 'when add_label_ids and label_ids are passed' do let(:params) { { label_ids: [label.id], add_label_ids: [label3.id] } } -- cgit v1.2.1 From debb65b5c81094765b2aa515a20e964cd8a14b6f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 29 Aug 2016 16:08:40 -0300 Subject: Update service specs to avoid instance variables --- spec/services/issues/close_service_spec.rb | 18 ++++----- spec/services/issues/reopen_service_spec.rb | 4 +- spec/services/issues/update_service_spec.rb | 58 ++++++++++++++--------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 229c8eb5b5b..4df99e41987 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -18,12 +18,12 @@ describe Issues::CloseService, services: true do context "valid params" do before do perform_enqueued_jobs do - @issue = described_class.new(project, user).execute(issue) + described_class.new(project, user).execute(issue) end end - it { expect(@issue).to be_valid } - it { expect(@issue).to be_closed } + it { expect(issue).to be_valid } + it { expect(issue).to be_closed } it 'sends email to user2 about assign of new issue' do email = ActionMailer::Base.deliveries.last @@ -32,7 +32,7 @@ describe Issues::CloseService, services: true do end it 'creates system note about issue reassign' do - note = @issue.notes.last + note = issue.notes.last expect(note.note).to include "Status changed to closed" end @@ -44,12 +44,12 @@ describe Issues::CloseService, services: true do context 'current user is not authorized to close issue' do before do perform_enqueued_jobs do - @issue = described_class.new(project, guest).execute(issue) + described_class.new(project, guest).execute(issue) end end it 'does not close the issue' do - expect(@issue).to be_open + expect(issue).to be_open end end @@ -67,11 +67,11 @@ describe Issues::CloseService, services: true do context 'external issue tracker' do before do allow(project).to receive(:default_issues_tracker?).and_return(false) - @issue = described_class.new(project, user).execute(issue) + described_class.new(project, user).execute(issue) end - it { expect(@issue).to be_valid } - it { expect(@issue).to be_opened } + it { expect(issue).to be_valid } + it { expect(issue).to be_opened } it { expect(todo.reload).to be_pending } end end diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 83c72e64c87..4549e2b395b 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -11,12 +11,12 @@ describe Issues::ReopenService, services: true do project.team << [guest, :guest] perform_enqueued_jobs do - @issue = described_class.new(project, guest).execute(issue) + described_class.new(project, guest).execute(issue) end end it 'does not reopen the issue' do - expect(@issue).to be_closed + expect(issue).to be_closed end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a4bd19d317b..3c3a861b3af 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -23,14 +23,13 @@ describe Issues::UpdateService, services: true do describe 'execute' do def find_note(starting_with) - @issue.notes.find do |note| + issue.notes.find do |note| note && note.note.start_with?(starting_with) end end def update_issue(opts) - @issue = described_class.new(project, user, opts).execute(issue) - @issue.reload + described_class.new(project, user, opts).execute(issue) end context "valid params" do @@ -44,18 +43,16 @@ describe Issues::UpdateService, services: true do } perform_enqueued_jobs do - @issue = described_class.new(project, user, opts).execute(issue) + update_issue(opts) end - - @issue.reload end - it { expect(@issue).to be_valid } - it { expect(@issue.title).to eq('New title') } - it { expect(@issue.assignee).to eq(user2) } - it { expect(@issue).to be_closed } - it { expect(@issue.labels.count).to eq(1) } - it { expect(@issue.labels.first.title).to eq(label.name) } + it { expect(issue).to be_valid } + it { expect(issue.title).to eq('New title') } + it { expect(issue.assignee).to eq(user2) } + it { expect(issue).to be_closed } + it { expect(issue.labels.count).to eq(1) } + it { expect(issue.labels.first.title).to eq(label.name) } it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do deliveries = ActionMailer::Base.deliveries @@ -100,7 +97,7 @@ describe Issues::UpdateService, services: true do end it 'creates system note about confidentiality change' do - update_issue({ confidential: true }) + update_issue(confidential: true) note = find_note('Made the issue confidential') @@ -112,7 +109,7 @@ describe Issues::UpdateService, services: true do expect(project).not_to receive(:execute_hooks) expect(project).not_to receive(:execute_services) - update_issue({ confidential: true }) + update_issue(confidential: true) end end @@ -121,7 +118,7 @@ describe Issues::UpdateService, services: true do context 'when the title change' do before do - update_issue({ title: 'New title' }) + update_issue(title: 'New title') end it 'marks pending todos as done' do @@ -131,7 +128,7 @@ describe Issues::UpdateService, services: true do context 'when the description change' do before do - update_issue({ description: 'Also please fix' }) + update_issue(description: 'Also please fix') end it 'marks todos as done' do @@ -141,7 +138,7 @@ describe Issues::UpdateService, services: true do context 'when is reassigned' do before do - update_issue({ assignee: user2 }) + update_issue(assignee: user2) end it 'marks previous assignee todos as done' do @@ -165,7 +162,7 @@ describe Issues::UpdateService, services: true do context 'when the milestone change' do before do - update_issue({ milestone: create(:milestone) }) + update_issue(milestone: create(:milestone)) end it 'marks todos as done' do @@ -175,7 +172,7 @@ describe Issues::UpdateService, services: true do context 'when the labels change' do before do - update_issue({ label_ids: [label.id] }) + update_issue(label_ids: [label.id]) end it 'marks todos as done' do @@ -186,6 +183,7 @@ describe Issues::UpdateService, services: true do context 'when the issue is relabeled' do let!(:non_subscriber) { create(:user) } + let!(:subscriber) do create(:user).tap do |u| label.toggle_subscription(u) @@ -232,12 +230,14 @@ describe Issues::UpdateService, services: true do end context 'when issue has tasks' do - before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) } + before do + update_issue(description: "- [ ] Task 1\n- [ ] Task 2") + end - it { expect(@issue.tasks?).to eq(true) } + it { expect(issue.tasks?).to eq(true) } context 'when tasks are marked as completed' do - before { update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) } + before { update_issue(description: "- [x] Task 1\n- [X] Task 2") } it 'creates system note about task status change' do note1 = find_note('Marked the task **Task 1** as completed') @@ -250,8 +250,8 @@ describe Issues::UpdateService, services: true do context 'when tasks are marked as incomplete' do before do - update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) - update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) + update_issue(description: "- [x] Task 1\n- [X] Task 2") + update_issue(description: "- [ ] Task 1\n- [ ] Task 2") end it 'creates system note about task status change' do @@ -265,8 +265,8 @@ describe Issues::UpdateService, services: true do context 'when tasks position has been modified' do before do - update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) - update_issue({ description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2" }) + update_issue(description: "- [x] Task 1\n- [X] Task 2") + update_issue(description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2") end it 'does not create a system note' do @@ -278,8 +278,8 @@ describe Issues::UpdateService, services: true do context 'when a Task list with a completed item is totally replaced' do before do - update_issue({ description: "- [ ] Task 1\n- [X] Task 2" }) - update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) + update_issue(description: "- [ ] Task 1\n- [X] Task 2") + update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three") end it 'does not create a system note referencing the position the old item' do @@ -290,7 +290,7 @@ describe Issues::UpdateService, services: true do it 'does not generate a new note at all' do expect do - update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) + update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three") end.not_to change { Note.count } end end -- cgit v1.2.1 From a103a5d9cc2f496854d75492fe3f759fad0a913f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 17:56:26 -0300 Subject: Add option to confidential issues events to trigger Webhooks --- app/controllers/projects/hooks_controller.rb | 1 + app/models/hooks/web_hook.rb | 1 + app/views/projects/hooks/_project_hook.html.haml | 2 +- app/views/shared/web_hooks/_form.html.haml | 7 +++++++ ..._add_confidential_issues_events_to_web_hooks.rb | 15 ++++++++++++++ db/schema.rb | 23 +++++++++++----------- 6 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 db/migrate/20160830203109_add_confidential_issues_events_to_web_hooks.rb diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index b5624046387..0ae8ff98009 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -59,6 +59,7 @@ class Projects::HooksController < Projects::ApplicationController :pipeline_events, :enable_ssl_verification, :issues_events, + :confidential_issues_events, :merge_requests_events, :note_events, :push_events, diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index f365dee3141..595602e80fe 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base default_value_for :push_events, true default_value_for :issues_events, false + default_value_for :confidential_issues_events, false default_value_for :note_events, false default_value_for :merge_requests_events, false default_value_for :tag_push_events, false diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/hooks/_project_hook.html.haml index 3fcf1692e09..ceabe2eab3d 100644 --- a/app/views/projects/hooks/_project_hook.html.haml +++ b/app/views/projects/hooks/_project_hook.html.haml @@ -3,7 +3,7 @@ .col-md-8.col-lg-7 %strong.light-header= hook.url %div - - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger| + - %w(push_events tag_push_events issues_events confidential_issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger| - if hook.send(trigger) %span.label.label-gray.deploy-project-label= trigger.titleize .col-md-4.col-lg-5.text-right-lg.prepend-top-5 diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index d2ec6c3ddef..5d659eb83a9 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -51,6 +51,13 @@ %strong Issues events %p.light This URL will be triggered when an issue is created/updated/merged + %li + = f.check_box :confidential_issues_events, class: 'pull-left' + .prepend-left-20 + = f.label :confidential_issues_events, class: 'list-label' do + %strong Confidential Issues events + %p.light + This URL will be triggered when a confidential issue is created/updated/merged %li = f.check_box :merge_requests_events, class: 'pull-left' .prepend-left-20 diff --git a/db/migrate/20160830203109_add_confidential_issues_events_to_web_hooks.rb b/db/migrate/20160830203109_add_confidential_issues_events_to_web_hooks.rb new file mode 100644 index 00000000000..a27947212f6 --- /dev/null +++ b/db/migrate/20160830203109_add_confidential_issues_events_to_web_hooks.rb @@ -0,0 +1,15 @@ +class AddConfidentialIssuesEventsToWebHooks < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default :web_hooks, :confidential_issues_events, :boolean, default: false, allow_null: false + end + + def down + remove_column :web_hooks, :confidential_issues_events + end +end diff --git a/db/schema.rb b/db/schema.rb index 963d528d170..a972f89a3a9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1113,22 +1113,23 @@ ActiveRecord::Schema.define(version: 20160830232601) do add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree create_table "web_hooks", force: :cascade do |t| - t.string "url", limit: 2000 + t.string "url", limit: 2000 t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" + t.string "type", default: "ProjectHook" t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false - t.boolean "tag_push_events", default: false - t.boolean "note_events", default: false, null: false - t.boolean "enable_ssl_verification", default: true - t.boolean "build_events", default: false, null: false - t.boolean "wiki_page_events", default: false, null: false + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false + t.boolean "tag_push_events", default: false + t.boolean "note_events", default: false, null: false + t.boolean "enable_ssl_verification", default: true + t.boolean "build_events", default: false, null: false + t.boolean "wiki_page_events", default: false, null: false t.string "token" - t.boolean "pipeline_events", default: false, null: false + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: false, null: false end add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree -- cgit v1.2.1 From dd64f8aaf867516043dbbf559595a0ed9671ab3b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 18:39:25 -0300 Subject: Add option to confidential issues events to trigger services --- app/controllers/concerns/service_params.rb | 2 +- app/helpers/services_helper.rb | 6 ++++-- app/models/project_services/hipchat_service.rb | 2 +- app/models/project_services/slack_service.rb | 2 +- app/models/service.rb | 3 ++- ...2_add_confidential_issues_events_to_services.rb | 15 +++++++++++++ db/schema.rb | 25 +++++++++++----------- 7 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 db/migrate/20160830211132_add_confidential_issues_events_to_services.rb diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index a69877edfd4..4cb3be41064 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -13,7 +13,7 @@ module ServiceParams # `issue_events` and `merge_request_events` (singular!) # See app/helpers/services_helper.rb for how we # make those event names plural as special case. - :issues_events, :merge_requests_events, + :issues_events, :confidential_issues_events, :merge_requests_events, :notify_only_broken_builds, :notify_only_broken_pipelines, :add_pusher, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 2dd0bf5d71e..3d4abf76419 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -8,7 +8,9 @@ module ServicesHelper when "note" "Event will be triggered when someone adds a comment" when "issue" - "Event will be triggered when an issue is created/updated/merged" + "Event will be triggered when an issue is created/updated/closed" + when "confidential_issue" + "Event will be triggered when a confidential issue is created/updated/closed" when "merge_request" "Event will be triggered when a merge request is created/updated/merged" when "build" @@ -19,7 +21,7 @@ module ServicesHelper end def service_event_field_name(event) - event = event.pluralize if %w[merge_request issue].include?(event) + event = event.pluralize if %w[merge_request issue confidential_issue].include?(event) "#{event}_events" end end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index d7c986c1a91..afebd3b6a12 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -39,7 +39,7 @@ class HipchatService < Service end def supported_events - %w(push issue merge_request note tag_push build) + %w(push issue confidential_issue merge_request note tag_push build) end def execute(data) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index abbc780dc1a..e6c943db2bf 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -44,7 +44,7 @@ class SlackService < Service end def supported_events - %w(push issue merge_request note tag_push build wiki_page) + %w(push issue confidential_issue merge_request note tag_push build wiki_page) end def execute(data) diff --git a/app/models/service.rb b/app/models/service.rb index 09b4717a523..7333f8d381b 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -7,6 +7,7 @@ class Service < ActiveRecord::Base default_value_for :active, false default_value_for :push_events, true default_value_for :issues_events, true + default_value_for :confidential_issues_events, true default_value_for :merge_requests_events, true default_value_for :tag_push_events, true default_value_for :note_events, true @@ -100,7 +101,7 @@ class Service < ActiveRecord::Base end def supported_events - %w(push tag_push issue merge_request wiki_page) + %w(push tag_push issue confidential_issue merge_request wiki_page) end def execute(data) diff --git a/db/migrate/20160830211132_add_confidential_issues_events_to_services.rb b/db/migrate/20160830211132_add_confidential_issues_events_to_services.rb new file mode 100644 index 00000000000..030e7c39350 --- /dev/null +++ b/db/migrate/20160830211132_add_confidential_issues_events_to_services.rb @@ -0,0 +1,15 @@ +class AddConfidentialIssuesEventsToServices < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default :services, :confidential_issues_events, :boolean, default: true, allow_null: false + end + + def down + remove_column :services, :confidential_issues_events + end +end diff --git a/db/schema.rb b/db/schema.rb index a972f89a3a9..c9560abeb92 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -901,19 +901,20 @@ ActiveRecord::Schema.define(version: 20160830232601) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.boolean "active", default: false, null: false t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false - t.boolean "wiki_page_events", default: true - t.boolean "pipeline_events", default: false, null: false + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false + t.boolean "wiki_page_events", default: true + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: true, null: false end add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree -- cgit v1.2.1 From 21f10af0956c69b6a5bad6b36b7d6e12e60e7867 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 30 Aug 2016 19:11:46 -0300 Subject: Scope hooks thal will run for confidential issues --- app/models/hooks/project_hook.rb | 1 + app/models/service.rb | 1 + app/services/issues/base_service.rb | 9 ++++----- spec/services/issues/close_service_spec.rb | 15 ++++++++++++--- spec/services/issues/create_service_spec.rb | 15 ++++++++++++--- spec/services/issues/reopen_service_spec.rb | 30 +++++++++++++++++++++-------- spec/services/issues/update_service_spec.rb | 6 +++--- 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index 836a75b0608..c631e7a7df5 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -2,6 +2,7 @@ class ProjectHook < WebHook belongs_to :project scope :issue_hooks, -> { where(issues_events: true) } + scope :confidential_issue_hooks, -> { where(confidential_issues_events: true) } scope :note_hooks, -> { where(note_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :build_hooks, -> { where(build_events: true) } diff --git a/app/models/service.rb b/app/models/service.rb index 7333f8d381b..198e7247838 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -34,6 +34,7 @@ class Service < ActiveRecord::Base scope :push_hooks, -> { where(push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :issue_hooks, -> { where(issues_events: true, active: true) } + scope :confidential_issue_hooks, -> { where(confidential_issues_events: true, active: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) } scope :build_hooks, -> { where(build_events: true, active: true) } diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 241efc44d36..9ea3ce084ba 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -14,11 +14,10 @@ module Issues end def execute_hooks(issue, action = 'open') - return if issue.confidential? - - issue_data = hook_data(issue, action) - issue.project.execute_hooks(issue_data, :issue_hooks) - issue.project.execute_services(issue_data, :issue_hooks) + issue_data = hook_data(issue, action) + hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks + issue.project.execute_hooks(issue_data, hooks_scope) + issue.project.execute_services(issue_data, hooks_scope) end end end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 4df99e41987..5dfb33f4b28 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -53,12 +53,21 @@ describe Issues::CloseService, services: true do end end + context 'when issue is not confidential' do + it 'executes issue hooks' do + expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) + expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks) + + described_class.new(project, user).execute(issue) + end + end + context 'when issue is confidential' do - it 'does not execute hooks' do + it 'executes confidential issue hooks' do issue = create(:issue, :confidential, project: project) - expect(project).not_to receive(:execute_hooks) - expect(project).not_to receive(:execute_services) + expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks) described_class.new(project, user).execute(issue) end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 81beca47738..58569ba96c3 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -73,11 +73,20 @@ describe Issues::CreateService, services: true do end end - it 'does not execute hooks when issue is confidential' do + it 'executes issue hooks when issue is not confidential' do + opts = { title: 'Title', description: 'Description', confidential: false } + + expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) + expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks) + + described_class.new(project, user, opts).execute + end + + it 'executes confidential issue hooks when issue is confidential' do opts = { title: 'Title', description: 'Description', confidential: true } - expect(project).not_to receive(:execute_hooks) - expect(project).not_to receive(:execute_services) + expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks) described_class.new(project, user, opts).execute end diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 4549e2b395b..93a8270fd16 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -5,7 +5,7 @@ describe Issues::ReopenService, services: true do let(:issue) { create(:issue, :closed, project: project) } describe '#execute' do - context 'current user is not authorized to reopen issue' do + context 'when user is not authorized to reopen issue' do before do guest = create(:user) project.team << [guest, :guest] @@ -20,17 +20,31 @@ describe Issues::ReopenService, services: true do end end - context 'when issue is confidential' do - it 'does not execute hooks' do - user = create(:user) + context 'when user is authrized to reopen issue' do + let(:user) { create(:user) } + + before do project.team << [user, :master] + end + + context 'when issue is not confidential' do + it 'executes issue hooks' do + expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) + expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks) - issue = create(:issue, :confidential, :closed, project: project) + described_class.new(project, user).execute(issue) + end + end - expect(project).not_to receive(:execute_hooks) - expect(project).not_to receive(:execute_services) + context 'when issue is confidential' do + it 'executes confidential issue hooks' do + issue = create(:issue, :confidential, :closed, project: project) - described_class.new(project, user).execute(issue) + expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks) + + described_class.new(project, user).execute(issue) + end end end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 3c3a861b3af..4f5375a3583 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -105,9 +105,9 @@ describe Issues::UpdateService, services: true do expect(note.note).to eq 'Made the issue confidential' end - it 'does not execute hooks' do - expect(project).not_to receive(:execute_hooks) - expect(project).not_to receive(:execute_services) + it 'executes confidential issue hooks' do + expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks) update_issue(confidential: true) end -- cgit v1.2.1 From 1af4989c6f448eccf554b817dc55f515165a7eee Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 31 Aug 2016 12:40:02 -0500 Subject: Center build stage columns in pipeline overview --- CHANGELOG | 1 + app/assets/stylesheets/pages/pipelines.scss | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9837b2edb9d..89142211495 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.12.0 (unreleased) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) + - Center build stage columns in pipeline overview (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 2d6653cd867..b56c0727660 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -2,6 +2,7 @@ .stage { max-width: 90px; width: 90px; + text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -146,6 +147,7 @@ } .stage-cell { + text-align: center; svg { height: 18px; @@ -153,10 +155,6 @@ vertical-align: middle; overflow: visible; } - - .light { - width: 3px; - } } .duration, -- cgit v1.2.1 From 443573ad936f792737fbd5f1902aa4fb87134231 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 31 Aug 2016 19:24:21 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 95d1504e982..f4b81bb4907 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,7 @@ v 8.11.4 (unreleased) - Fix resolving conflicts on forks - Fix diff commenting on merge requests created prior to 8.10 - Don't create groups for unallowed users when importing projects + - Scope webhooks/services that will run for confidential issues - Fix issue boards leak private label names and descriptions v 8.11.3 -- cgit v1.2.1 From e71df3cdd20d5c9bcf4ecc64771a1d88babfddf8 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Tue, 30 Aug 2016 00:01:00 +1000 Subject: Order award tooltips by their created_at date --- CHANGELOG | 1 + app/models/concerns/awardable.rb | 2 +- spec/models/concerns/awardable_spec.rb | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d06fc24d40a..eeb37dbc55a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.12.0 (unreleased) - Fix branch title trailing space on hover (ClemMakesApps) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) + - Order award emoji tooltips in order they were added (EspadaV8) - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - Update merge_requests.md with a simpler way to check out a merge request. !5944 - Fix button missing type (ClemMakesApps) diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb index 800a16ab246..e6feaf7df4f 100644 --- a/app/models/concerns/awardable.rb +++ b/app/models/concerns/awardable.rb @@ -2,7 +2,7 @@ module Awardable extend ActiveSupport::Concern included do - has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy + has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy if self < Participable # By default we always load award_emoji user association diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb index a371c4a18a9..de791abdf3d 100644 --- a/spec/models/concerns/awardable_spec.rb +++ b/spec/models/concerns/awardable_spec.rb @@ -45,4 +45,14 @@ describe Issue, "Awardable" do expect { issue.toggle_award_emoji("thumbsdown", award_emoji.user) }.to change { AwardEmoji.count }.by(-1) end end + + describe 'querying award_emoji on an Awardable' do + let(:issue) { create(:issue) } + + it 'sorts in ascending fashion' do + create_list(:award_emoji, 3, awardable: issue) + + expect(issue.award_emoji).to eq issue.award_emoji.sort_by(&:id) + end + end end -- cgit v1.2.1 From a926f1f03e8516302d08195a705fbad2e953bcbb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 31 Aug 2016 19:54:06 -0300 Subject: Fix suggested colors options for new labels in the admin area --- app/assets/javascripts/dispatcher.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ba64d2bcf0b..38cdc7b9fba 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -199,6 +199,7 @@ break; case 'labels': switch (path[2]) { + case 'new': case 'edit': new Labels(); } -- cgit v1.2.1 From fdbab40d94b690bdc1890f027a89653924fa6710 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 31 Aug 2016 19:54:11 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 13ec1bb885f..16e25c436d9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -64,6 +64,7 @@ v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Fix sorting issues by "last updated" doesn't work after import from GitHub - Creating an issue through our API now emails label subscribers !5720 + - Fix suggested colors options for new labels in the admin area - Block concurrent updates for Pipeline - Fix resolving conflicts on forks - Fix diff commenting on merge requests created prior to 8.10 -- cgit v1.2.1 From 0d8352973bfbd3d1857657fce35284fcaf241cf7 Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 17 Jul 2016 02:03:56 +0200 Subject: Use JavaScript tooltips for mentions (!5301) --- CHANGELOG | 1 + app/models/commit.rb | 9 --------- app/models/commit_range.rb | 7 ------- lib/banzai/filter/abstract_reference_filter.rb | 10 +--------- lib/banzai/filter/commit_range_reference_filter.rb | 2 +- lib/banzai/filter/commit_reference_filter.rb | 4 ---- lib/banzai/filter/label_reference_filter.rb | 5 +++++ lib/banzai/filter/milestone_reference_filter.rb | 4 ++++ lib/banzai/filter/reference_filter.rb | 2 +- spec/lib/banzai/filter/commit_range_reference_filter_spec.rb | 6 +++--- spec/lib/banzai/filter/commit_reference_filter_spec.rb | 4 ++-- spec/lib/banzai/filter/external_issue_reference_filter_spec.rb | 2 +- spec/lib/banzai/filter/issue_reference_filter_spec.rb | 4 ++-- spec/lib/banzai/filter/label_reference_filter_spec.rb | 2 +- spec/lib/banzai/filter/merge_request_reference_filter_spec.rb | 4 ++-- spec/lib/banzai/filter/milestone_reference_filter_spec.rb | 2 +- spec/lib/banzai/filter/snippet_reference_filter_spec.rb | 4 ++-- spec/lib/banzai/filter/user_reference_filter_spec.rb | 2 +- spec/models/commit_range_spec.rb | 10 ---------- 19 files changed, 28 insertions(+), 56 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 13ec1bb885f..504d16b5de7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.12.0 (unreleased) - Add search to all issue board lists - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) + - Use JavaScript tooltips for mentions !5301 (winniehell) - Fix markdown help references (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps) - Added project specific enable/disable setting for LFS !5997 diff --git a/app/models/commit.rb b/app/models/commit.rb index 817d063e4a2..e64fd1e0c1b 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -108,15 +108,6 @@ class Commit @diff_line_count end - # Returns a string describing the commit for use in a link title - # - # Example - # - # "Commit: Alex Denisov - Project git clone panel" - def link_title - "Commit: #{author_name} - #{title}" - end - # Returns the commits title. # # Usually, the commit title is the first line of the commit message. diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 630ee9601e0..656a242c265 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -4,12 +4,10 @@ # # range = CommitRange.new('f3f85602...e86e1013', project) # range.exclude_start? # => false -# range.reference_title # => "Commits f3f85602 through e86e1013" # range.to_s # => "f3f85602...e86e1013" # # range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project) # range.exclude_start? # => true -# range.reference_title # => "Commits f3f85602^ through e86e1013" # range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} # range.to_s # => "f3f85602..e86e1013" # @@ -109,11 +107,6 @@ class CommitRange reference end - # Returns a String for use in a link's title attribute - def reference_title - "Commits #{sha_start} through #{sha_to}" - end - # Return a Hash of parameters for passing to a URL helper # # See `namespace_project_compare_url` diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index d77a5e3ff09..16cd774c81a 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -18,10 +18,6 @@ module Banzai @object_sym ||= object_name.to_sym end - def self.object_class_title - @object_title ||= object_class.name.titleize - end - # Public: Find references in text (like `!123` for merge requests) # # AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches| @@ -49,10 +45,6 @@ module Banzai self.class.object_sym end - def object_class_title - self.class.object_class_title - end - def references_in(*args, &block) self.class.references_in(*args, &block) end @@ -198,7 +190,7 @@ module Banzai end def object_link_title(object) - "#{object_class_title}: #{object.title}" + object.title end def object_link_text(object, matches) diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb index bbb88c979cc..4358bf45549 100644 --- a/lib/banzai/filter/commit_range_reference_filter.rb +++ b/lib/banzai/filter/commit_range_reference_filter.rb @@ -35,7 +35,7 @@ module Banzai end def object_link_title(range) - range.reference_title + nil end end end diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index 2ce1816672b..a26dd09c25a 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -28,10 +28,6 @@ module Banzai only_path: context[:only_path]) end - def object_link_title(commit) - commit.link_title - end - def object_link_text_extras(object, matches) extras = super diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index e258dc8e2bf..8f262ef3d8d 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -70,6 +70,11 @@ module Banzai def unescape_html_entities(text) CGI.unescapeHTML(text.to_s) end + + def object_link_title(object) + # use title of wrapped element instead + nil + end end end end diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index ca686c87d97..58fff496d00 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -59,6 +59,10 @@ module Banzai html_safe end end + + def object_link_title(object) + nil + end end end end diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb index bf058241cda..2d221290f7e 100644 --- a/lib/banzai/filter/reference_filter.rb +++ b/lib/banzai/filter/reference_filter.rb @@ -52,7 +52,7 @@ module Banzai end def reference_class(type) - "gfm gfm-#{type}" + "gfm gfm-#{type} has-tooltip" end # Ensure that a :project key exists in context diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index 593bd6d5cac..e6c90ad87ee 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -65,14 +65,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do expect(reference_filter(act).to_html).to eq exp end - it 'includes a title attribute' do + it 'includes no title attribute' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('title')).to eq range.reference_title + expect(doc.css('a').first.attr('title')).to eq "" end it 'includes default classes' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range has-tooltip' end it 'includes a data-project attribute' do diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index d46d3f1489e..e0f08282551 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -55,7 +55,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do it 'includes a title attribute' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('title')).to eq commit.link_title + expect(doc.css('a').first.attr('title')).to eq commit.title end it 'escapes the title attribute' do @@ -67,7 +67,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do it 'includes default classes' do doc = reference_filter("See #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit has-tooltip' end it 'includes a data-project attribute' do diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index 953466679e4..7116c09fb21 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -64,7 +64,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do it 'includes default classes' do doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip' end it 'supports an :only_path context' do diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index a005b4990e7..fce86a9b6ad 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -54,7 +54,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do it 'includes a title attribute' do doc = reference_filter("Issue #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}" + expect(doc.css('a').first.attr('title')).to eq issue.title end it 'escapes the title attribute' do @@ -66,7 +66,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do it 'includes default classes' do doc = reference_filter("Issue #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip' end it 'includes a data-project attribute' do diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 9276a154007..908ccebbf87 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -21,7 +21,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do it 'includes default classes' do doc = reference_filter("Label #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label has-tooltip' end it 'includes a data-project attribute' do diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 805acf1c8b3..274258a045c 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -46,7 +46,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do it 'includes a title attribute' do doc = reference_filter("Merge #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}" + expect(doc.css('a').first.attr('title')).to eq merge.title end it 'escapes the title attribute' do @@ -58,7 +58,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do it 'includes default classes' do doc = reference_filter("Merge #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip' end it 'includes a data-project attribute' do diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 9424f2363e1..7419863d848 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -20,7 +20,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do it 'includes default classes' do doc = reference_filter("Milestone #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip' end it 'includes a data-project attribute' do diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb index 5068ddd7faa..9b92d1a3926 100644 --- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb @@ -39,7 +39,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do it 'includes a title attribute' do doc = reference_filter("Snippet #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}" + expect(doc.css('a').first.attr('title')).to eq snippet.title end it 'escapes the title attribute' do @@ -51,7 +51,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do it 'includes default classes' do doc = reference_filter("Snippet #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet has-tooltip' end it 'includes a data-project attribute' do diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 108b36a97cc..fdbdb21eac1 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -104,7 +104,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do it 'includes default classes' do doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip' end it 'supports an :only_path context' do diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 384a38ebc69..c41359b55a3 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -76,16 +76,6 @@ describe CommitRange, models: true do end end - describe '#reference_title' do - it 'returns the correct String for three-dot ranges' do - expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}" - end - - it 'returns the correct String for two-dot ranges' do - expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}" - end - end - describe '#to_param' do it 'includes the correct keys' do expect(range.to_param.keys).to eq %i(from to) -- cgit v1.2.1 From 8b6154c145b22d34146fc08c49d2e2d1569d44a0 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Wed, 24 Aug 2016 19:58:05 -0500 Subject: Minor edits to two_factor_recovery_codes API error catching --- lib/api/internal.rb | 14 +++++++++----- spec/requests/api/internal_spec.rb | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 5b54c11ef62..6e6efece7c4 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -105,15 +105,19 @@ module API post '/two_factor_recovery_codes' do status 200 - key = Key.find(params[:key_id]) - user = key.user + key = Key.find_by(id: params[:key_id]) + + unless key + return { 'success' => false, 'message' => 'Could not find the given key' } + end - # Make sure this isn't a deploy key - unless key.type.nil? + if key.is_a?(DeployKey) return { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } end - unless user.present? + user = key.user + + unless user return { success: false, message: 'Could not find a user for the given key' } end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 5d06abcfeb3..46d1b868782 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -44,8 +44,8 @@ describe API::API, api: true do secret_token: secret_token, key_id: 12345 - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Not found') + expect(json_response['success']).to be_falsey + expect(json_response['message']).to eq('Could not find the given key') end it 'returns an error message when the key is a deploy key' do -- cgit v1.2.1 From 91c215f9a7c5f1bd33eee4d8587bc80a92863071 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 31 Aug 2016 20:58:58 -0500 Subject: make projects dropdown accessible --- app/assets/stylesheets/framework/header.scss | 4 ++++ app/helpers/projects_helper.rb | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 2b4fc0fb068..e981de241ce 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -160,11 +160,15 @@ header { } .dropdown-toggle-caret { + color: $gl-text-color; + border: transparent; + background: transparent; position: relative; top: -2px; width: 12px; line-height: 12px; margin-left: 5px; + padding: 0; font-size: 10px; text-align: center; cursor: pointer; diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f07077bd133..7a90a0bb465 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -61,7 +61,9 @@ module ProjectsHelper project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } if current_user - project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) + project_link << button_tag(type: 'button', class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) do + icon("chevron-down") + end end full_title = "#{namespace_link} / #{project_link}".html_safe -- cgit v1.2.1 From 523002e40f79ad85ffa6ce74b8ff405c17429cb6 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 31 Aug 2016 21:16:04 -0500 Subject: prevent project dropdown chevron from disappearing in small viewports --- CHANGELOG | 1 + app/assets/stylesheets/framework/header.scss | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9837b2edb9d..78c430a60af 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.12.0 (unreleased) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Fix markdown help references (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps) + - Fix accessibility and visibility of project list dropdown button !6140 - Added project specific enable/disable setting for LFS !5997 - Don't expose a user's token in the `/api/v3/user` API (!6047) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index e981de241ce..d3e787937e6 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -136,6 +136,8 @@ header { } .title { + position: relative; + padding-right: 20px; margin: 0; font-size: 19px; max-width: 400px; @@ -163,11 +165,11 @@ header { color: $gl-text-color; border: transparent; background: transparent; - position: relative; - top: -2px; + position: absolute; + right: 3px; width: 12px; - line-height: 12px; - margin-left: 5px; + line-height: 19px; + margin-top: (($header-height - 19) / 2); padding: 0; font-size: 10px; text-align: center; -- cgit v1.2.1 From cf1174c673ce4244236b64f25e34f1a609552ede Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 31 Aug 2016 22:55:56 -0500 Subject: add extra viewport breakpoint for project name width --- app/assets/stylesheets/framework/header.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index d3e787937e6..1036219172e 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -150,7 +150,11 @@ header { vertical-align: top; white-space: nowrap; - @media (max-width: $screen-sm-max) { + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + max-width: 300px; + } + + @media (max-width: $screen-xs-max) { max-width: 190px; } -- cgit v1.2.1 From d326d3428da89b943bb5f1d4d396f21b3e999ff7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 29 Aug 2016 07:46:30 -0700 Subject: Optimize branch lookups and force a repository reload for Repository#find_branch If `git gc` runs and `Repository` has an instance to `Rugged::Repository`, a bug in libgit2 may cause the instance to return a stale value or a missing branch. This change not only optimizes the branch lookup so we don't have to iterate through every branch, but it also works around the `git gc` issue by forcing a repository reload every time `Repository#find_branch` is called. See: https://github.com/libgit2/libgit2/issues/1534 Closes #15392, #21470 --- CHANGELOG | 3 +++ Gemfile | 2 +- Gemfile.lock | 4 ++-- app/models/repository.rb | 17 +++++++++++++++-- spec/models/repository_spec.rb | 18 ++++++++++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9837b2edb9d..9ab32684de6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -60,6 +60,9 @@ v 8.12.0 (unreleased) - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fixed invisible scroll controls on build page on iPhone +v 8.11.5 (unreleased) + - Optimize branch lookups and force a repository reload for Repository#find_branch + v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Fix sorting issues by "last updated" doesn't work after import from GitHub diff --git a/Gemfile b/Gemfile index 96841013815..620338e5997 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.4.7' +gem 'gitlab_git', '~> 10.5' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 1d0fcfd3c3a..28ede86b3ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -279,7 +279,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.4.7) + gitlab_git (10.5.0) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -858,7 +858,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.4.7) + gitlab_git (~> 10.5) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) diff --git a/app/models/repository.rb b/app/models/repository.rb index 91bdafdac99..f891e8374d2 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -120,8 +120,21 @@ class Repository commits end - def find_branch(name) - raw_repository.branches.find { |branch| branch.name == name } + def find_branch(name, fresh_repo: true) + # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may + # cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate + # a new repo here to ensure a consistent state to avoid a libgit2 bug where concurrent access (e.g. via git gc) + # may cause the branch to "disappear" erroneously or have the wrong SHA. + # + # See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392 + raw_repo = + if fresh_repo + Gitlab::Git::Repository.new(path_to_repo) + else + raw_repository + end + + raw_repo.find_branch(name) end def find_tag(name) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 1fea50ad42c..812c72c48cb 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -382,6 +382,24 @@ describe Repository, models: true do end end + describe '#find_branch' do + it 'loads a branch with a fresh repo' do + expect(Gitlab::Git::Repository).to receive(:new).twice.and_call_original + + 2.times do + expect(repository.find_branch('feature')).not_to be_nil + end + end + + it 'loads a branch with a cached repo' do + expect(Gitlab::Git::Repository).to receive(:new).once.and_call_original + + 2.times do + expect(repository.find_branch('feature', fresh_repo: false)).not_to be_nil + end + end + end + describe '#rm_branch' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:blank_sha) { '0000000000000000000000000000000000000000' } -- cgit v1.2.1 From 51c9cea86835427af983c30ddec7753949cb0bff Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 1 Sep 2016 09:42:18 +0200 Subject: Add MR Documentation description template --- .gitlab/merge_request_templates/Documentation.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gitlab/merge_request_templates/Documentation.md diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md new file mode 100644 index 00000000000..d2a1eb56423 --- /dev/null +++ b/.gitlab/merge_request_templates/Documentation.md @@ -0,0 +1,14 @@ +See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html. + +## What does this MR do? + +(briefly describe what this MR is about) + +## Moving docs to a new location? + +See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#changing-document-location + +- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location. +- [ ] Make sure internal links pointing to the document in question are not broken. +- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory. +- [ ] If working on CE, submit an MR to EE with the changes as well. -- cgit v1.2.1 From 25c84fd629e72acc753b920a35eb3cfe1cf93035 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 10:48:34 +0100 Subject: Fixed issue where moving issue & then scrolling stop new page loading --- app/assets/javascripts/boards/models/list.js.es6 | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 296a033071e..91fd620fdb3 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -52,7 +52,7 @@ class List { } nextPage () { - if (Math.floor(this.issues.length / 20) === this.page) { + if (this.issuesSize > this.issues.length) { this.page++; return this.getIssues(false); @@ -94,15 +94,20 @@ class List { } addIssue (issue, listFrom) { - this.issues.push(issue); + if (!this.findIssue(issue.id)) { + this.issues.push(issue); - if (this.label) { - issue.addLabel(this.label); - } + if (this.label) { + issue.addLabel(this.label); + } - if (listFrom) { - this.issuesSize++; - gl.boardService.moveIssue(issue.id, listFrom.id, this.id); + if (listFrom) { + this.issuesSize++; + gl.boardService.moveIssue(issue.id, listFrom.id, this.id) + .then(() => { + listFrom.getIssues(false); + }); + } } } -- cgit v1.2.1 From 195d79dce0ea424aeea78e8ad35ece83bd708641 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 1 Sep 2016 05:38:58 -0600 Subject: path const and prevent default location --- app/assets/javascripts/user.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index c934afa458a..d644a1c0bc6 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -22,12 +22,12 @@ hideProjectLimitMessage() { $('.hide-project-limit-message').on('click', e => { - const path = '/'; + e.preventDefault(); + const path = gon.relative_url_root || '/'; $.cookie('hide_project_limit_message', 'false', { path: path }); $(this).parents('.project-limit-message').remove(); - e.preventDefault(); return; }); } -- cgit v1.2.1 From 0d45972c936bc498b2e9d1f552099b4679afd607 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 1 Sep 2016 05:40:38 -0600 Subject: single quotes for 'placement': 'top' k-v --- app/assets/javascripts/user.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index d644a1c0bc6..f9cdf82f529 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -9,7 +9,7 @@ placeTop() { $('.profile-groups-avatars').tooltip({ - "placement": "top" + 'placement': 'top' }); } -- cgit v1.2.1 From ee419bf48b16b91b2a47f877f169fbf531f8ae11 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 1 Sep 2016 05:54:10 -0600 Subject: no string wraps on keys --- app/assets/javascripts/user.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index f9cdf82f529..833f35df59b 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -9,7 +9,7 @@ placeTop() { $('.profile-groups-avatars').tooltip({ - 'placement': 'top' + placement: 'top' }); } -- cgit v1.2.1 From ed51734030f94aa7e0636d8527b4bdae05c9de6b Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 21 Jun 2016 13:12:02 +0200 Subject: Handle error on trace raw download with old builds (DB stored) --- app/controllers/projects/builds_controller.rb | 2 +- app/models/ci/build.rb | 4 ++++ app/views/projects/builds/_sidebar.html.haml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 12195c3cbb8..4127337cbf2 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -78,7 +78,7 @@ class Projects::BuildsController < Projects::ApplicationController end def raw - if @build.has_trace? + if @build.has_trace_file? send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline' else render_404 diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 23c8de6f650..76142c4b2b6 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -208,6 +208,10 @@ module Ci end end + def has_trace_file? + File.exist?(path_to_trace) || (project.ci_id && File.exist?(old_path_to_trace)) + end + def has_trace? raw_trace.present? end diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 5b0b58e087b..49c8bd11634 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -100,7 +100,7 @@ - elsif @build.runner \##{@build.runner.id} .btn-group.btn-group-justified{ role: :group } - - if @build.has_trace? + - if @build.has_trace_file? = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' - if @build.active? = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post -- cgit v1.2.1 From cc365e98067408a44922d89f7f266b77a68d9cb5 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 21 Jun 2016 13:18:02 +0200 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 459cdf029a3..0f4bb7ecc3d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -74,6 +74,9 @@ v 8.11.4 (unreleased) v 8.11.3 - Do not enforce using hash with hidden key in CI configuration. !6079 + - Fix error on raw build trace download for old builds stored in database !4822 + +v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label - Don't show resolve conflicts link before MR status is updated -- cgit v1.2.1 From 56011f9f697b7ca42f786735a2249014dd3ef18d Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 25 Aug 2016 13:53:20 +0200 Subject: Refactorize CI::Build model --- app/models/ci/build.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 76142c4b2b6..8a8f848d328 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -209,7 +209,7 @@ module Ci end def has_trace_file? - File.exist?(path_to_trace) || (project.ci_id && File.exist?(old_path_to_trace)) + File.exist?(path_to_trace) || has_old_trace_file? end def has_trace? @@ -219,7 +219,7 @@ module Ci def raw_trace if File.file?(path_to_trace) File.read(path_to_trace) - elsif project.ci_id && File.file?(old_path_to_trace) + elsif has_old_trace_file? # Temporary fix for build trace data integrity File.read(old_path_to_trace) else @@ -228,6 +228,14 @@ module Ci end end + ## + # Deprecated + # + # This is a hotfix for CI build data integrity, see #4246 + def has_old_trace_file? + project.ci_id && File.exist?(old_path_to_trace) + end + def trace trace = raw_trace if project && trace.present? && project.runners_token.present? -- cgit v1.2.1 From 77295de4076835d6080a9868fe7cb0c08522e141 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 25 Aug 2016 13:53:49 +0200 Subject: Change 404 to 410 error when raw trace is unavailable --- app/controllers/application_controller.rb | 4 ++ app/controllers/projects/builds_controller.rb | 2 +- public/410.html | 65 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 public/410.html diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bd4ba384b29..78ed12db516 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -117,6 +117,10 @@ class ApplicationController < ActionController::Base render file: Rails.root.join("public", "404"), layout: false, status: "404" end + def render_410 + render file: Rails.root.join("public", "410"), layout: false, status: "410" + end + def no_cache_headers response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Pragma"] = "no-cache" diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 4127337cbf2..79d774195f8 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -81,7 +81,7 @@ class Projects::BuildsController < Projects::ApplicationController if @build.has_trace_file? send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline' else - render_404 + render_410 end end diff --git a/public/410.html b/public/410.html new file mode 100644 index 00000000000..bba436b7b7d --- /dev/null +++ b/public/410.html @@ -0,0 +1,65 @@ + + + + + The page you're looking for is gone (410) + + + + +

              + GitLab Logo
              + 410 +

              +
              +

              The page you're looking for is gone.

              +
              +

              Make sure the address is correct and that the page hasn't moved.

              +

              Please contact your GitLab administrator if you think this is a mistake.

              +
              + + -- cgit v1.2.1 From e2e8ec6074a4b7552188b169cfe9c4612b740428 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 29 Aug 2016 14:10:14 +0200 Subject: Add specs --- spec/models/ci/build_spec.rb | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 36d10636ae9..1dd26750edd 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -19,4 +19,55 @@ describe Ci::Build, models: true do expect(build.trace).to eq(test_trace) end end + + describe '#has_trace_file?' do + context 'when there is no trace' do + it { expect(build.has_trace_file?).to be_falsey } + it { expect(build.trace).to be_nil } + end + + context 'when there is a trace' do + context 'when trace is stored in file' do + before do + build.trace = test_trace + build.save + end + + it { expect(build.has_trace_file?).to be_truthy } + it { expect(build.trace).to eq(test_trace) } + end + + context 'when trace is stored in old file' do + before do + build.trace = test_trace + build.save + + build.project.ci_id = 999 + build.project.save + + FileUtils.mkdir_p(build.old_dir_to_trace) + FileUtils.mv(build.path_to_trace, build.old_path_to_trace) + end + + it { expect(build.has_trace_file?).to be_truthy } + it { expect(build.trace).to eq(test_trace) } + end + + context 'when there is stored in DB' do + class Ci::Build + def write_db_trace=(trace) + write_attribute :trace, trace + end + end + + before do + build.write_db_trace = test_trace + build.save + end + + it { expect(build.has_trace_file?).to be_falsey } + it { expect(build.trace).to eq(test_trace) } + end + end + end end -- cgit v1.2.1 From c8861da76772d781f677a76506f590edc23ba251 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 30 Aug 2016 12:47:31 +0200 Subject: Update specs - add mocks to simulate old versions --- app/controllers/application_controller.rb | 4 - app/controllers/projects/builds_controller.rb | 4 +- app/models/ci/build.rb | 10 ++- public/410.html | 65 --------------- spec/features/projects/builds_spec.rb | 114 ++++++++++++++++++++------ spec/models/ci/build_spec.rb | 55 +++++++------ 6 files changed, 133 insertions(+), 119 deletions(-) delete mode 100644 public/410.html diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 78ed12db516..bd4ba384b29 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -117,10 +117,6 @@ class ApplicationController < ActionController::Base render file: Rails.root.join("public", "404"), layout: false, status: "404" end - def render_410 - render file: Rails.root.join("public", "410"), layout: false, status: "410" - end - def no_cache_headers response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Pragma"] = "no-cache" diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 79d774195f8..77934ff9962 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -79,9 +79,9 @@ class Projects::BuildsController < Projects::ApplicationController def raw if @build.has_trace_file? - send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline' + send_file @build.trace_file_path, type: 'text/plain; charset=utf-8', disposition: 'inline' else - render_410 + render_404 end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 8a8f848d328..f219cee4a62 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -217,7 +217,7 @@ module Ci end def raw_trace - if File.file?(path_to_trace) + if File.exist?(path_to_trace) File.read(path_to_trace) elsif has_old_trace_file? # Temporary fix for build trace data integrity @@ -274,6 +274,14 @@ module Ci end end + def trace_file_path + if has_old_trace_file? + old_path_to_trace + else + path_to_trace + end + end + def dir_to_trace File.join( Settings.gitlab_ci.builds_path, diff --git a/public/410.html b/public/410.html deleted file mode 100644 index bba436b7b7d..00000000000 --- a/public/410.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - The page you're looking for is gone (410) - - - - -

              - GitLab Logo
              - 410 -

              -
              -

              The page you're looking for is gone.

              -
              -

              Make sure the address is correct and that the page hasn't moved.

              -

              Please contact your GitLab administrator if you think this is a mistake.

              -
              - - diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index 0cfeb2e57d8..9030ce9dc67 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'tempfile' describe "Builds" do let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } @@ -6,7 +7,7 @@ describe "Builds" do before do login_as(:user) @commit = FactoryGirl.create :ci_pipeline - @build = FactoryGirl.create :ci_build, pipeline: @commit + @build = FactoryGirl.create :ci_build, :trace, pipeline: @commit @build2 = FactoryGirl.create :ci_build @project = @commit.project @project.team << [@user, :developer] @@ -156,7 +157,6 @@ describe "Builds" do context 'Build raw trace' do before do @build.run! - @build.trace = 'BUILD TRACE' visit namespace_project_build_path(@project.namespace, @project, @build) end @@ -255,35 +255,101 @@ describe "Builds" do end end - describe "GET /:project/builds/:id/raw" do - context "Build from project" do - before do - Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build.run! - @build.trace = 'BUILD TRACE' - visit namespace_project_build_path(@project.namespace, @project, @build) - page.within('.js-build-sidebar') { click_link 'Raw' } + describe 'GET /:project/builds/:id/raw' do + context 'access source' do + context 'build from project' do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + page.within('.js-build-sidebar') { click_link 'Raw' } + end + + it 'sends the right headers' do + expect(page.status_code).to eq(200) + expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace) + end end - it 'sends the right headers' do - expect(page.status_code).to eq(200) - expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(page.response_headers['X-Sendfile']).to eq(@build.path_to_trace) + context 'build from other project' do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build2.run! + visit raw_namespace_project_build_path(@project.namespace, @project, @build2) + end + + it 'sends the right headers' do + expect(page.status_code).to eq(404) + end end end - context "Build from other project" do - before do - Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') - @build2.run! - @build2.trace = 'BUILD TRACE' - visit raw_namespace_project_build_path(@project.namespace, @project, @build2) - puts page.status_code - puts current_url + context 'storage form' do + let (:existing_file) { Tempfile.new('existing-trace-file').path } + let (:non_existing_file) do + file = Tempfile.new('non-existing-trace-file') + path = file.path + file.unlink + path end - it 'sends the right headers' do - expect(page.status_code).to eq(404) + context 'when build has trace in file' do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + + allow_any_instance_of(Project).to receive(:ci_id).and_return(nil) + allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(existing_file) + allow_any_instance_of(Ci::Build).to receive(:old_path_to_trace).and_return(non_existing_file) + + page.within('.js-build-sidebar') { click_link 'Raw' } + end + + it 'sends the right headers' do + expect(page.status_code).to eq(200) + expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(page.response_headers['X-Sendfile']).to eq(existing_file) + end + end + + context 'when build has trace in old file' do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + + allow_any_instance_of(Project).to receive(:ci_id).and_return(999) + allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(non_existing_file) + allow_any_instance_of(Ci::Build).to receive(:old_path_to_trace).and_return(existing_file) + + page.within('.js-build-sidebar') { click_link 'Raw' } + end + + it 'sends the right headers' do + expect(page.status_code).to eq(200) + expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(page.response_headers['X-Sendfile']).to eq(existing_file) + end + end + + context 'when build has trace in DB' do + before do + Capybara.current_session.driver.header('X-Sendfile-Type', 'X-Sendfile') + @build.run! + visit namespace_project_build_path(@project.namespace, @project, @build) + + allow_any_instance_of(Project).to receive(:ci_id).and_return(nil) + allow_any_instance_of(Ci::Build).to receive(:path_to_trace).and_return(non_existing_file) + allow_any_instance_of(Ci::Build).to receive(:old_path_to_trace).and_return(non_existing_file) + + page.within('.js-build-sidebar') { click_link 'Raw' } + end + + it 'sends the right headers' do + expect(page.status_code).to eq(404) + end end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 1dd26750edd..bce18b4e99e 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -28,41 +28,30 @@ describe Ci::Build, models: true do context 'when there is a trace' do context 'when trace is stored in file' do - before do - build.trace = test_trace - build.save - end + let(:build_with_trace) { create(:ci_build, :trace) } - it { expect(build.has_trace_file?).to be_truthy } - it { expect(build.trace).to eq(test_trace) } + it { expect(build_with_trace.has_trace_file?).to be_truthy } + it { expect(build_with_trace.trace).to eq('BUILD TRACE') } end context 'when trace is stored in old file' do before do - build.trace = test_trace - build.save - - build.project.ci_id = 999 - build.project.save - - FileUtils.mkdir_p(build.old_dir_to_trace) - FileUtils.mv(build.path_to_trace, build.old_path_to_trace) + allow(build.project).to receive(:ci_id).and_return(999) + allow(File).to receive(:exist?).with(build.path_to_trace).and_return(false) + allow(File).to receive(:exist?).with(build.old_path_to_trace).and_return(true) + allow(File).to receive(:read).with(build.old_path_to_trace).and_return(test_trace) end it { expect(build.has_trace_file?).to be_truthy } it { expect(build.trace).to eq(test_trace) } end - context 'when there is stored in DB' do - class Ci::Build - def write_db_trace=(trace) - write_attribute :trace, trace - end - end - + context 'when trace is stored in DB' do before do - build.write_db_trace = test_trace - build.save + allow(build.project).to receive(:ci_id).and_return(nil) + allow(build).to receive(:read_attribute).with(:trace).and_return(test_trace) + allow(File).to receive(:exist?).with(build.path_to_trace).and_return(false) + allow(File).to receive(:exist?).with(build.old_path_to_trace).and_return(false) end it { expect(build.has_trace_file?).to be_falsey } @@ -70,4 +59,24 @@ describe Ci::Build, models: true do end end end + + describe '#trace_file_path' do + context 'when trace is stored in file' do + before do + allow(build).to receive(:has_trace_file?).and_return(true) + allow(build).to receive(:has_old_trace_file?).and_return(false) + end + + it { expect(build.trace_file_path).to eq(build.path_to_trace) } + end + + context 'when trace is stored in old file' do + before do + allow(build).to receive(:has_trace_file?).and_return(true) + allow(build).to receive(:has_old_trace_file?).and_return(true) + end + + it { expect(build.trace_file_path).to eq(build.old_path_to_trace) } + end + end end -- cgit v1.2.1 From 65274b1db697c329c1e106c170a050599d65de72 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 31 Aug 2016 19:38:01 +0200 Subject: Fix rubocop offences --- spec/features/projects/builds_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index 9030ce9dc67..d5d571e49bc 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -286,8 +286,8 @@ describe "Builds" do end context 'storage form' do - let (:existing_file) { Tempfile.new('existing-trace-file').path } - let (:non_existing_file) do + let(:existing_file) { Tempfile.new('existing-trace-file').path } + let(:non_existing_file) do file = Tempfile.new('non-existing-trace-file') path = file.path file.unlink -- cgit v1.2.1 From beff8b9bd077fea4bbcb09881396b57ba07f568e Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sat, 27 Aug 2016 23:45:01 +0100 Subject: Swapped out author dropdown and started on swapping out project dropdown --- app/assets/javascripts/gl_dropdown.js | 1 + app/helpers/issuables_helper.rb | 13 ++++++++++++ app/helpers/todos_helper.rb | 26 ++++++++++-------------- app/views/dashboard/todos/index.html.haml | 33 ++++++++++++++++++++----------- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 77b2082cba0..ba59ce3c956 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -202,6 +202,7 @@ var ref, ref1, ref2, ref3, searchFields, selector, self; this.el = el1; this.options = options; + console.log(this.options); this.updateLabel = bind(this.updateLabel, this); this.hidden = bind(this.hidden, this); this.opened = bind(this.opened, this); diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index b9baeb1d6c4..dcf2ef6bb0a 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -49,6 +49,19 @@ module IssuablesHelper end end + def project_dropdown_label(project_id, default_label) + return default_label if project_id.nil? + return "Any project" if project_id == "0" + + project = Project.find_by(id: project_id) + + if project + project.name_with_namespace || project.name + else + default_label + end + end + def milestone_dropdown_label(milestone_title, default_label = "Milestone") if milestone_title == Milestone::Upcoming.name milestone_title = Milestone::Upcoming.title diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 0465327060e..a9f4c8b07b5 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -78,13 +78,11 @@ module TodosHelper end def todo_actions_options - actions = [ - OpenStruct.new(id: '', title: 'Any Action'), - OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'), - OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned') + [ + { id: '', text: 'Any Action' }, + { id: Todo::ASSIGNED, text: 'Assigned' }, + { id: Todo::MENTIONED, text: 'Mentioned' } ] - - options_from_collection_for_select(actions, 'id', 'title', params[:action_id]) end def todo_projects_options @@ -92,22 +90,18 @@ module TodosHelper projects = projects.includes(:namespace) projects = projects.map do |project| - OpenStruct.new(id: project.id, title: project.name_with_namespace) + { id: project.id, text: project.name_with_namespace } end - projects.unshift(OpenStruct.new(id: '', title: 'Any Project')) - - options_from_collection_for_select(projects, 'id', 'title', params[:project_id]) + projects.unshift({ id: '', text: 'Any Project' }).to_json end def todo_types_options - types = [ - OpenStruct.new(title: 'Any Type', name: ''), - OpenStruct.new(title: 'Issue', name: 'Issue'), - OpenStruct.new(title: 'Merge Request', name: 'MergeRequest') + [ + { text: 'Any Type', id: '' }, + { text: 'Issue', id: 'Issue' }, + { text: 'Merge Request', id: 'MergeRequest' } ] - - options_from_collection_for_select(types, 'name', 'title', params[:type]) end private diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 6bcc37042ea..42dc1fd0118 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -28,20 +28,23 @@ .row-content-block.second-block = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do .filter-item.inline - = select_tag('project_id', todo_projects_options, - class: 'select2 trigger-submit', include_blank: true, - data: {placeholder: 'Project'}) + - if params[:project_id].present? + = hidden_field_tag(:project_id, params[:project_id]) + = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', + placeholder: 'Search projects', data: { data: todo_projects_options, selected: params[:project_id], field_name: 'project_id', default_label: 'Project' } }) .filter-item.inline - = users_select_tag(:author_id, selected: params[:author_id], - placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true) + - if params[:author_id].present? + = hidden_field_tag(:author_id, params[:author_id]) + = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', + placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) .filter-item.inline - = select_tag('type', todo_types_options, - class: 'select2 trigger-submit', include_blank: true, - data: {placeholder: 'Type'}) + -# = select_tag('type', todo_types_options, + -# class: 'select2 trigger-submit', include_blank: true, + -# data: {placeholder: 'Type'}) .filter-item.inline.actions-filter - = select_tag('action_id', todo_actions_options, - class: 'select2 trigger-submit', include_blank: true, - data: {placeholder: 'Action'}) + -# = select_tag('action_id', todo_actions_options, + -# class: 'select2 trigger-submit', include_blank: true, + -# data: {placeholder: 'Action'}) .pull-right .dropdown.inline.prepend-left-10 @@ -80,6 +83,14 @@ :javascript new UsersSelect(); + $projectDropdown = $('.js-project-search'); + $projectDropdown.glDropdown({ + filterable: true, + selectable: true, + fieldName: 'project_id', + data: $projectDropdown.data('data') + }); + $('form.filter-form').on('submit', function (event) { event.preventDefault(); Turbolinks.visit(this.action + '&' + $(this).serialize()); -- cgit v1.2.1 From 0e2dd06f259a42e360cb73a9f17de46cc0ab31fd Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 28 Aug 2016 00:09:21 +0100 Subject: Completed project filter dropdown, still need to move it from inline to ProjectSelect.js (or different) --- app/assets/javascripts/gl_dropdown.js | 1 - app/helpers/issuables_helper.rb | 2 +- app/views/dashboard/todos/index.html.haml | 8 +++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index ba59ce3c956..77b2082cba0 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -202,7 +202,6 @@ var ref, ref1, ref2, ref3, searchFields, selector, self; this.el = el1; this.options = options; - console.log(this.options); this.updateLabel = bind(this.updateLabel, this); this.hidden = bind(this.hidden, this); this.opened = bind(this.opened, this); diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index dcf2ef6bb0a..5c04bba323f 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -56,7 +56,7 @@ module IssuablesHelper project = Project.find_by(id: project_id) if project - project.name_with_namespace || project.name + project.name_with_namespace else default_label end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 42dc1fd0118..ffdb88975a5 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -88,7 +88,13 @@ filterable: true, selectable: true, fieldName: 'project_id', - data: $projectDropdown.data('data') + data: $projectDropdown.data('data'), + clicked: function() { + if ($projectDropdown.hasClass('js-filter-submit')) { + console.log('booM!'); + return $projectDropdown.closest('form').submit(); + } + } }); $('form.filter-form').on('submit', function (event) { -- cgit v1.2.1 From f157a9e5144fde90dc31add4006b9132e1489aa1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 28 Aug 2016 00:18:48 +0100 Subject: Added type and action dropdowns, need to finalize by removing all inline and polishing off the selected dropdown states --- app/views/dashboard/todos/index.html.haml | 40 ++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index ffdb88975a5..4a8536315a5 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -38,14 +38,15 @@ = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) .filter-item.inline - -# = select_tag('type', todo_types_options, - -# class: 'select2 trigger-submit', include_blank: true, - -# data: {placeholder: 'Type'}) + - if params[:type].present? + = hidden_field_tag(:type, params[:type]) + = dropdown_tag(params[:type] || 'Type', options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', + data: { data: todo_types_options, selected: params[:type], field_name: 'type', default_label: 'Type' } }) .filter-item.inline.actions-filter - -# = select_tag('action_id', todo_actions_options, - -# class: 'select2 trigger-submit', include_blank: true, - -# data: {placeholder: 'Action'}) - + - if params[:action_id].present? + = hidden_field_tag(:action_id, params[:action_id]) + = dropdown_tag(params[:action_id] || 'Action', options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', + data: { data: todo_actions_options, selected: params[:action_id], field_name: 'action_id', default_label: 'Action' } }) .pull-right .dropdown.inline.prepend-left-10 %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} @@ -91,12 +92,35 @@ data: $projectDropdown.data('data'), clicked: function() { if ($projectDropdown.hasClass('js-filter-submit')) { - console.log('booM!'); return $projectDropdown.closest('form').submit(); } } }); + $typeDropdown = $('.js-type-search'); + $typeDropdown.glDropdown({ + selectable: true, + fieldName: 'type_id', + data: $typeDropdown.data('data'), + clicked: function() { + if ($typeDropdown.hasClass('js-filter-submit')) { + return $typeDropdown.closest('form').submit(); + } + } + }); + + $actionDropdown = $('.js-action-search'); + $actionDropdown.glDropdown({ + selectable: true, + fieldName: 'action_id', + data: $actionDropdown.data('data'), + clicked: function() { + if ($actionDropdown.hasClass('js-filter-submit')) { + return $actionDropdown.closest('form').submit(); + } + } + }); + $('form.filter-form').on('submit', function (event) { event.preventDefault(); Turbolinks.visit(this.action + '&' + $(this).serialize()); -- cgit v1.2.1 From 922b38a0bea8c5b8610120dfb5ea168db713f3e3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 28 Aug 2016 16:24:48 +0100 Subject: Removed inline JS and improved dropdown labels --- app/assets/javascripts/todos.js | 56 +++++++++++++++++++++++++++++++ app/helpers/todos_helper.rb | 16 +++++++-- app/views/dashboard/todos/index.html.haml | 53 +++-------------------------- 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index 06605320a35..ef1eadfb9c0 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -13,6 +13,7 @@ this.perPage = this.el.data('perPage'); this.clearListeners(); this.initBtnListeners(); + this.initFilters(); } Todos.prototype.clearListeners = function() { @@ -27,6 +28,61 @@ return $('.todo').on('click', this.goToTodoUrl); }; + Todos.prototype.initFilters = function() { + new UsersSelect(); + this.initProjectFilterDropdown(); + this.initTypeFilterDropdown(); + this.initActionFilterDropdown(); + + $('form.filter-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '&' + $(this).serialize()); + }); + }; + + Todos.prototype.initProjectFilterDropdown = function() { + $projectDropdown = $('.js-project-search'); + $projectDropdown.glDropdown({ + filterable: true, + selectable: true, + fieldName: 'project_id', + data: $projectDropdown.data('data'), + clicked: function() { + if ($projectDropdown.hasClass('js-filter-submit')) { + return $projectDropdown.closest('form.filter-form').submit(); + } + } + }); + }; + + Todos.prototype.initTypeFilterDropdown = function() { + $typeDropdown = $('.js-type-search'); + $typeDropdown.glDropdown({ + selectable: true, + fieldName: 'type', + data: $typeDropdown.data('data'), + clicked: function() { + if ($typeDropdown.hasClass('js-filter-submit')) { + return $typeDropdown.closest('form.filter-form').submit(); + } + } + }); + }; + + Todos.prototype.initActionFilterDropdown = function() { + $actionDropdown = $('.js-action-search'); + $actionDropdown.glDropdown({ + selectable: true, + fieldName: 'action_id', + data: $actionDropdown.data('data'), + clicked: function() { + if ($actionDropdown.hasClass('js-filter-submit')) { + return $actionDropdown.closest('form.filter-form').submit(); + } + } + }); + }; + Todos.prototype.doneClicked = function(e) { var $this; e.preventDefault(); diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index a9f4c8b07b5..1e86f648203 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -98,12 +98,22 @@ module TodosHelper def todo_types_options [ - { text: 'Any Type', id: '' }, - { text: 'Issue', id: 'Issue' }, - { text: 'Merge Request', id: 'MergeRequest' } + { id: '', text: 'Any Type' }, + { id: 'Issue', text: 'Issue' }, + { id: 'MergeRequest', text: 'Merge Request' } ] end + def todo_actions_dropdown_label(selected_action_id, default_action) + selected_action = todo_actions_options.find { |action| action[:id] == selected_action_id.to_i} + selected_action ? selected_action[:text] : default_action + end + + def todo_types_dropdown_label(selected_type, default_type) + selected_type = todo_types_options.find { |type| type[:id] == selected_type && type[:id] != '' } + selected_type ? selected_type[:text] : default_type + end + private def show_todo_state?(todo) diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 4a8536315a5..70dd4db6aaf 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -40,13 +40,13 @@ .filter-item.inline - if params[:type].present? = hidden_field_tag(:type, params[:type]) - = dropdown_tag(params[:type] || 'Type', options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', - data: { data: todo_types_options, selected: params[:type], field_name: 'type', default_label: 'Type' } }) + = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', + data: { data: todo_types_options } }) .filter-item.inline.actions-filter - if params[:action_id].present? = hidden_field_tag(:action_id, params[:action_id]) - = dropdown_tag(params[:action_id] || 'Action', options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', - data: { data: todo_actions_options, selected: params[:action_id], field_name: 'action_id', default_label: 'Action' } }) + = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', + data: { data: todo_actions_options }}) .pull-right .dropdown.inline.prepend-left-10 %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} @@ -80,48 +80,3 @@ = paginate @todos, theme: "gitlab" - else .nothing-here-block You're all done! - -:javascript - new UsersSelect(); - - $projectDropdown = $('.js-project-search'); - $projectDropdown.glDropdown({ - filterable: true, - selectable: true, - fieldName: 'project_id', - data: $projectDropdown.data('data'), - clicked: function() { - if ($projectDropdown.hasClass('js-filter-submit')) { - return $projectDropdown.closest('form').submit(); - } - } - }); - - $typeDropdown = $('.js-type-search'); - $typeDropdown.glDropdown({ - selectable: true, - fieldName: 'type_id', - data: $typeDropdown.data('data'), - clicked: function() { - if ($typeDropdown.hasClass('js-filter-submit')) { - return $typeDropdown.closest('form').submit(); - } - } - }); - - $actionDropdown = $('.js-action-search'); - $actionDropdown.glDropdown({ - selectable: true, - fieldName: 'action_id', - data: $actionDropdown.data('data'), - clicked: function() { - if ($actionDropdown.hasClass('js-filter-submit')) { - return $actionDropdown.closest('form').submit(); - } - } - }); - - $('form.filter-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '&' + $(this).serialize()); - }); -- cgit v1.2.1 From bd595eb82cd239c123818be9144258fdbaaae79c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 28 Aug 2016 17:46:01 +0100 Subject: Removed select2 from todos feature spec --- features/steps/dashboard/todos.rb | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb index 0607086c166..344b6fda9a6 100644 --- a/features/steps/dashboard/todos.rb +++ b/features/steps/dashboard/todos.rb @@ -3,7 +3,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps include SharedPaths include SharedProject include SharedUser - include Select2Helper step '"John Doe" is a developer of project "Shop"' do project.team << [john_doe, :developer] @@ -55,7 +54,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps expect(page).to have_content 'To do 0' expect(page).to have_content 'Done 4' expect(page).to have_content "You're all done!" - expect(page).not_to have_link project.name_with_namespace + expect('.prepend-top-default').not_to have_link project.name_with_namespace should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}" should_not_see_todo "John Doe assigned you issue #{issue.to_reference}" @@ -80,19 +79,31 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps end step 'I filter by "Enterprise"' do - select2(enterprise.id, from: "#project_id") + click_button 'Project' + page.within '.dropdown-menu-project' do + click_link enterprise.name_with_namespace + end end step 'I filter by "John Doe"' do - select2(john_doe.id, from: "#author_id") + click_button 'Author' + page.within '.dropdown-menu-author' do + click_link john_doe.username + end end step 'I filter by "Issue"' do - select2('Issue', from: "#type") + click_button 'Type' + page.within '.dropdown-menu-type' do + click_link 'Issue' + end end step 'I filter by "Mentioned"' do - select2("#{Todo::MENTIONED}", from: '#action_id') + click_button 'Action' + page.within '.dropdown-menu-action' do + click_link 'Mentioned' + end end step 'I should not see todos' do -- cgit v1.2.1 From 0ccdb41ced96c304658cf88b0869c5b78a4f15b7 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 30 Aug 2016 00:28:44 +0100 Subject: Review changes, simplified dropdown init --- app/assets/javascripts/todos.js | 51 ++++++------------------------- app/views/dashboard/todos/index.html.haml | 6 ++-- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index ef1eadfb9c0..a908f730954 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -30,9 +30,9 @@ Todos.prototype.initFilters = function() { new UsersSelect(); - this.initProjectFilterDropdown(); - this.initTypeFilterDropdown(); - this.initActionFilterDropdown(); + this.initFilterDropdown($('.js-project-search'), 'project_id', true); + this.initFilterDropdown($('.js-type-search'), 'type'); + this.initFilterDropdown($('.js-action-search'), 'action_id'); $('form.filter-form').on('submit', function (event) { event.preventDefault(); @@ -40,47 +40,16 @@ }); }; - Todos.prototype.initProjectFilterDropdown = function() { - $projectDropdown = $('.js-project-search'); - $projectDropdown.glDropdown({ - filterable: true, + Todos.prototype.initFilterDropdown = function($dropdown, fieldName, isFilterable) { + $dropdown.glDropdown({ selectable: true, - fieldName: 'project_id', - data: $projectDropdown.data('data'), + filterable: isFilterable, + fieldName: fieldName, + data: $dropdown.data('data'), clicked: function() { - if ($projectDropdown.hasClass('js-filter-submit')) { - return $projectDropdown.closest('form.filter-form').submit(); - } + return $dropdown.closest('form.filter-form').submit(); } - }); - }; - - Todos.prototype.initTypeFilterDropdown = function() { - $typeDropdown = $('.js-type-search'); - $typeDropdown.glDropdown({ - selectable: true, - fieldName: 'type', - data: $typeDropdown.data('data'), - clicked: function() { - if ($typeDropdown.hasClass('js-filter-submit')) { - return $typeDropdown.closest('form.filter-form').submit(); - } - } - }); - }; - - Todos.prototype.initActionFilterDropdown = function() { - $actionDropdown = $('.js-action-search'); - $actionDropdown.glDropdown({ - selectable: true, - fieldName: 'action_id', - data: $actionDropdown.data('data'), - clicked: function() { - if ($actionDropdown.hasClass('js-filter-submit')) { - return $actionDropdown.closest('form.filter-form').submit(); - } - } - }); + }) }; Todos.prototype.doneClicked = function(e) { diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 70dd4db6aaf..9d31f31c639 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -30,12 +30,12 @@ .filter-item.inline - if params[:project_id].present? = hidden_field_tag(:project_id, params[:project_id]) - = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', - placeholder: 'Search projects', data: { data: todo_projects_options, selected: params[:project_id], field_name: 'project_id', default_label: 'Project' } }) + = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', + placeholder: 'Search projects', data: { data: todo_projects_options } }) .filter-item.inline - if params[:author_id].present? = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', + = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } }) .filter-item.inline - if params[:type].present? -- cgit v1.2.1 From e67a483752310e32bc4577c03dd9042565b71c0a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 31 Aug 2016 18:33:12 +0100 Subject: Fixed project filtering --- app/assets/javascripts/todos.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index a908f730954..23eda7d44ca 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -30,7 +30,7 @@ Todos.prototype.initFilters = function() { new UsersSelect(); - this.initFilterDropdown($('.js-project-search'), 'project_id', true); + this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); this.initFilterDropdown($('.js-type-search'), 'type'); this.initFilterDropdown($('.js-action-search'), 'action_id'); @@ -40,11 +40,12 @@ }); }; - Todos.prototype.initFilterDropdown = function($dropdown, fieldName, isFilterable) { + Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) { $dropdown.glDropdown({ selectable: true, - filterable: isFilterable, + filterable: searchFields ? true : false, fieldName: fieldName, + search: { fields: searchFields }, data: $dropdown.data('data'), clicked: function() { return $dropdown.closest('form.filter-form').submit(); -- cgit v1.2.1 From 28db37de70fa94b2e7d287c9cfa31946f037125a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 1 Sep 2016 14:05:58 +0100 Subject: Added todo filter tests --- features/dashboard/todos.feature | 20 --------- spec/features/todos/todos_filtering_spec.rb | 63 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 spec/features/todos/todos_filtering_spec.rb diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature index 42f5d6d2af7..0b23bbb7951 100644 --- a/features/dashboard/todos.feature +++ b/features/dashboard/todos.feature @@ -22,26 +22,6 @@ Feature: Dashboard Todos And I mark all todos as done Then I should see all todos marked as done - @javascript - Scenario: I filter by project - Given I filter by "Enterprise" - Then I should not see todos - - @javascript - Scenario: I filter by author - Given I filter by "John Doe" - Then I should not see todos related to "Mary Jane" in the list - - @javascript - Scenario: I filter by type - Given I filter by "Issue" - Then I should not see todos related to "Merge Requests" in the list - - @javascript - Scenario: I filter by action - Given I filter by "Mentioned" - Then I should not see todos related to "Assignments" in the list - @javascript Scenario: I click on a todo row Given I click on the todo diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb new file mode 100644 index 00000000000..83cf306437d --- /dev/null +++ b/spec/features/todos/todos_filtering_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe 'Dashboard > User filters todos', feature: true, js: true do + include WaitForAjax + + let(:user_1) { create(:user, username: 'user_1', name: 'user_1') } + let(:user_2) { create(:user, username: 'user_2', name: 'user_2') } + + let(:project_1) { create(:empty_project, name: 'project_1') } + let(:project_2) { create(:empty_project, name: 'project_2') } + + let(:issue) { create(:issue, title: 'issue', project: project_1) } + + let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') } + + before do + create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1) + create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2) + + project_1.team << [user_1, :developer] + project_2.team << [user_1, :developer] + login_as(user_1) + visit dashboard_todos_path + end + + it 'filters by project' do + click_button 'Project' + within '.dropdown-menu-project' do + fill_in 'Search projects', with: project_1.name_with_namespace + click_link project_1.name_with_namespace + end + wait_for_ajax + expect('.prepend-top-default').not_to have_content project_2.name_with_namespace + end + + it 'filters by author' do + click_button 'Author' + within '.dropdown-menu-author' do + fill_in 'Search authors', with: user_1.name + click_link user_1.name + end + wait_for_ajax + expect('.prepend-top-default').not_to have_content user_2.name + end + + it 'filters by type' do + click_button 'Type' + within '.dropdown-menu-type' do + click_link 'Issue' + end + wait_for_ajax + expect('.prepend-top-default').not_to have_content ' merge request !' + end + + it 'filters by action' do + click_button 'Action' + within '.dropdown-menu-action' do + click_link 'Assigned' + end + wait_for_ajax + expect('.prepend-top-default').not_to have_content ' mentioned ' + end +end -- cgit v1.2.1 From 9a5c9b46922aeb91e5027b0511e6526c8dd34827 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 1 Sep 2016 11:23:15 -0300 Subject: Add migration to set confidential issues events on web hooks --- ...01141443_set_confidential_issues_events_on_webhooks.rb | 15 +++++++++++++++ db/schema.rb | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb diff --git a/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb b/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb new file mode 100644 index 00000000000..f1a1f001cb3 --- /dev/null +++ b/db/migrate/20160901141443_set_confidential_issues_events_on_webhooks.rb @@ -0,0 +1,15 @@ +class SetConfidentialIssuesEventsOnWebhooks < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + update_column_in_batches(:web_hooks, :confidential_issues_events, true) do |table, query| + query.where(table[:issues_events].eq(true)) + end + end + + def down + # noop + end +end diff --git a/db/schema.rb b/db/schema.rb index c9560abeb92..f5abdbfeb9c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160830232601) do +ActiveRecord::Schema.define(version: 20160901141443) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 892dea67717c0efbd6a28f7639f34535ec0a8747 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 1 Aug 2016 19:31:21 -0300 Subject: Project tools visibility level --- CHANGELOG | 1 + app/controllers/jwt_controller.rb | 2 +- app/controllers/projects/application_controller.rb | 2 +- app/controllers/projects/discussions_controller.rb | 2 +- app/controllers/projects/issues_controller.rb | 2 +- app/controllers/projects/labels_controller.rb | 2 +- .../projects/merge_requests_controller.rb | 2 +- app/controllers/projects/milestones_controller.rb | 2 +- app/controllers/projects/snippets_controller.rb | 2 +- app/controllers/projects_controller.rb | 18 ++- app/helpers/application_helper.rb | 2 +- app/helpers/compare_helper.rb | 2 +- app/helpers/projects_helper.rb | 19 ++++ .../concerns/project_features_compatibility.rb | 37 +++++++ app/models/project.rb | 27 +++-- app/models/project_feature.rb | 63 +++++++++++ app/models/user.rb | 2 +- app/policies/project_policy.rb | 12 +- app/services/ci/register_build_service.rb | 8 +- app/services/merge_requests/get_urls_service.rb | 2 +- app/services/projects/create_service.rb | 4 +- app/services/projects/fork_service.rb | 4 +- app/views/layouts/nav/_project_settings.html.haml | 2 +- app/views/projects/edit.html.haml | 98 +++++++++-------- app/views/projects/graphs/_head.html.haml | 2 +- .../20160831214002_create_project_features.rb | 16 +++ .../20160831214543_migrate_project_features.rb | 44 ++++++++ ...223750_remove_features_enabled_from_projects.rb | 29 +++++ db/schema.rb | 20 +++- doc/user/permissions.md | 9 ++ features/steps/project/project.rb | 2 +- features/steps/shared/project.rb | 6 +- lib/api/entities.rb | 12 +- lib/api/groups.rb | 2 +- lib/api/projects.rb | 2 +- lib/gitlab/github_import/importer.rb | 2 +- lib/gitlab/github_import/project_creator.rb | 12 +- lib/gitlab/import_export/import_export.yml | 7 +- .../projects/snippets_controller_spec.rb | 2 +- spec/factories/projects.rb | 21 +++- spec/features/projects/features_visibility_spec.rb | 122 +++++++++++++++++++++ .../import_export/test_project_export.tar.gz | Bin 687442 -> 676870 bytes spec/lib/gitlab/auth_spec.rb | 3 +- spec/lib/gitlab/github_import/importer_spec.rb | 2 +- spec/lib/gitlab/import_export/project.json | 6 +- .../import_export/project_tree_restorer_spec.rb | 13 ++- .../import_export/project_tree_saver_spec.rb | 12 ++ spec/lib/gitlab/import_export/reader_spec.rb | 6 + spec/models/ability_spec.rb | 4 +- .../project_features_compatibility_spec.rb | 25 +++++ spec/models/project_feature_spec.rb | 91 +++++++++++++++ spec/models/project_spec.rb | 4 +- spec/models/user_spec.rb | 3 +- spec/requests/api/projects_spec.rb | 9 +- spec/requests/git_http_spec.rb | 3 +- spec/requests/jwt_controller_spec.rb | 13 ++- spec/services/ci/register_build_service_spec.rb | 19 ++++ .../merge_requests/get_urls_service_spec.rb | 2 +- spec/services/projects/create_service_spec.rb | 6 +- .../single_repository_worker_spec.rb | 8 +- 60 files changed, 713 insertions(+), 143 deletions(-) create mode 100644 app/models/concerns/project_features_compatibility.rb create mode 100644 app/models/project_feature.rb create mode 100644 db/migrate/20160831214002_create_project_features.rb create mode 100644 db/migrate/20160831214543_migrate_project_features.rb create mode 100644 db/migrate/20160831223750_remove_features_enabled_from_projects.rb create mode 100644 spec/features/projects/features_visibility_spec.rb create mode 100644 spec/models/concerns/project_features_compatibility_spec.rb create mode 100644 spec/models/project_feature_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 13ec1bb885f..a2b6766f98b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.12.0 (unreleased) - Added project specific enable/disable setting for LFS !5997 - Don't expose a user's token in the `/api/v3/user` API (!6047) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) + - Ability to manage project issues, snippets, wiki, merge requests and builds access level - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Remove redundant pipeline tooltips (ClemMakesApps) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 014b9b43ff2..66ebdcc37a7 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -37,7 +37,7 @@ class JwtController < ApplicationController def authenticate_project(login, password) if login == 'gitlab-ci-token' - Project.find_by(builds_enabled: true, runners_token: password) + Project.with_builds_enabled.find_by(runners_token: password) end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 91315a07deb..b2ff36f6538 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -88,6 +88,6 @@ class Projects::ApplicationController < ApplicationController end def builds_enabled - return render_404 unless @project.builds_enabled? + return render_404 unless @project.feature_available?(:builds, current_user) end end diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb index b2e8733ccb7..d174e1145a7 100644 --- a/app/controllers/projects/discussions_controller.rb +++ b/app/controllers/projects/discussions_controller.rb @@ -38,6 +38,6 @@ class Projects::DiscussionsController < Projects::ApplicationController end def module_enabled - render_404 unless @project.merge_requests_enabled + render_404 unless @project.feature_available?(:merge_requests, current_user) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 7c03dcd2e64..72d2d361878 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -201,7 +201,7 @@ class Projects::IssuesController < Projects::ApplicationController end def module_enabled - return render_404 unless @project.issues_enabled && @project.default_issues_tracker? + return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker? end def redirect_to_external_issue_tracker diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 0ca675623e5..28fa4a5b141 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -99,7 +99,7 @@ class Projects::LabelsController < Projects::ApplicationController protected def module_enabled - unless @project.issues_enabled || @project.merge_requests_enabled + unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user) return render_404 end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 4f5f3b6aa09..4f9ca0097a1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -413,7 +413,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def module_enabled - return render_404 unless @project.merge_requests_enabled + return render_404 unless @project.feature_available?(:merge_requests, current_user) end def validates_merge_request diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index da2892bfb3f..ff63f22cb5b 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -106,7 +106,7 @@ class Projects::MilestonesController < Projects::ApplicationController end def module_enabled - unless @project.issues_enabled || @project.merge_requests_enabled + unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user) return render_404 end end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 6d0a7ee1031..17ceefec3b8 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -94,7 +94,7 @@ class Projects::SnippetsController < Projects::ApplicationController end def module_enabled - return render_404 unless @project.snippets_enabled + return render_404 unless @project.feature_available?(:snippets, current_user) end def snippet_params diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 84d6b106cd7..eaa38fa6c98 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -303,13 +303,23 @@ class ProjectsController < Projects::ApplicationController end def project_params + project_feature_attributes = + { + project_feature_attributes: + [ + :issues_access_level, :builds_access_level, + :wiki_access_level, :merge_requests_access_level, :snippets_access_level + ] + } + params.require(:project).permit( :name, :path, :description, :issues_tracker, :tag_list, :runners_token, - :issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled, + :container_registry_enabled, :issues_tracker_id, :default_branch, - :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, - :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, - :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :lfs_enabled + :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, + :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, + :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, + :lfs_enabled, project_feature_attributes ) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f3733b01721..5f3765cad0d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -110,7 +110,7 @@ module ApplicationHelper project = event.project # Skip if project repo is empty or MR disabled - return false unless project && !project.empty_repo? && project.merge_requests_enabled + return false unless project && !project.empty_repo? && project.feature_available?(:merge_requests, current_user) # Skip if user already created appropriate MR return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index f1dc906cab4..aa54ee07bdc 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -3,7 +3,7 @@ module CompareHelper from.present? && to.present? && from != to && - project.merge_requests_enabled && + project.feature_available?(:merge_requests, current_user) && project.repository.branch_names.include?(from) && project.repository.branch_names.include?(to) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f07077bd133..79a1eba9714 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -412,4 +412,23 @@ module ProjectsHelper message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") end + + def project_feature_options + { + 'Disabled' => ProjectFeature::DISABLED, + 'Only team members' => ProjectFeature::PRIVATE, + 'Everyone with access' => ProjectFeature::ENABLED + } + end + + def project_feature_access_select(field) + # Don't show option "everyone with access" if project is private + options = project_feature_options + level = @project.project_feature.public_send(field) + + options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED + + options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED) + content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control").html_safe + end end diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb new file mode 100644 index 00000000000..9216122923e --- /dev/null +++ b/app/models/concerns/project_features_compatibility.rb @@ -0,0 +1,37 @@ +# Makes api V3 compatible with old project features permissions methods +# +# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled +# fields to a new table "project_features", support for the old fields is still needed in the API. + +module ProjectFeaturesCompatibility + extend ActiveSupport::Concern + + def wiki_enabled=(value) + write_feature_attribute(:wiki_access_level, value) + end + + def builds_enabled=(value) + write_feature_attribute(:builds_access_level, value) + end + + def merge_requests_enabled=(value) + write_feature_attribute(:merge_requests_access_level, value) + end + + def issues_enabled=(value) + write_feature_attribute(:issues_access_level, value) + end + + def snippets_enabled=(value) + write_feature_attribute(:snippets_access_level, value) + end + + private + + def write_feature_attribute(field, value) + build_project_feature unless project_feature + + access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED + project_feature.update_attribute(field, access_level) + end +end diff --git a/app/models/project.rb b/app/models/project.rb index e5027af4a0e..a6de2c48071 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -11,24 +11,23 @@ class Project < ActiveRecord::Base include AfterCommitQueue include CaseSensitivity include TokenAuthenticatable + include ProjectFeaturesCompatibility extend Gitlab::ConfigHelper UNKNOWN_IMPORT_URL = 'http://unknown.git' + delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true + default_value_for :archived, false default_value_for :visibility_level, gitlab_config_features.visibility_level - default_value_for :issues_enabled, gitlab_config_features.issues - default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests - default_value_for :builds_enabled, gitlab_config_features.builds - default_value_for :wiki_enabled, gitlab_config_features.wiki - default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for(:repository_storage) { current_application_settings.repository_storage } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } after_create :ensure_dir_exist after_save :ensure_dir_exist, if: :namespace_id_changed? + after_initialize :setup_project_feature # set last_activity_at to the same as created_at after_create :set_last_activity_at @@ -62,10 +61,10 @@ class Project < ActiveRecord::Base belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :namespace - has_one :board, dependent: :destroy - has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' + has_one :board, dependent: :destroy + # Project services has_many :services has_one :campfire_service, dependent: :destroy @@ -130,6 +129,7 @@ class Project < ActiveRecord::Base has_many :notification_settings, dependent: :destroy, as: :source has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" + has_one :project_feature, dependent: :destroy has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id @@ -142,6 +142,7 @@ class Project < ActiveRecord::Base has_many :deployments, dependent: :destroy accepts_nested_attributes_for :variables, allow_destroy: true + accepts_nested_attributes_for :project_feature delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true @@ -159,8 +160,6 @@ class Project < ActiveRecord::Base length: { within: 0..255 }, format: { with: Gitlab::Regex.project_path_regex, message: Gitlab::Regex.project_path_regex_message } - validates :issues_enabled, :merge_requests_enabled, - :wiki_enabled, inclusion: { in: [true, false] } validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id @@ -196,6 +195,9 @@ class Project < ActiveRecord::Base scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) } + scope :with_builds_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0') } + scope :with_issues_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.issues_access_level IS NULL or project_features.issues_access_level > 0') } + scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') } scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) } @@ -1121,7 +1123,7 @@ class Project < ActiveRecord::Base end def enable_ci - self.builds_enabled = true + project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) end def any_runners?(&block) @@ -1288,6 +1290,11 @@ class Project < ActiveRecord::Base private + # Prevents the creation of project_feature record for every project + def setup_project_feature + build_project_feature unless project_feature + end + def default_branch_protected? current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb new file mode 100644 index 00000000000..9c602c582bd --- /dev/null +++ b/app/models/project_feature.rb @@ -0,0 +1,63 @@ +class ProjectFeature < ActiveRecord::Base + # == Project features permissions + # + # Grants access level to project tools + # + # Tools can be enabled only for users, everyone or disabled + # Access control is made only for non private projects + # + # levels: + # + # Disabled: not enabled for anyone + # Private: enabled only for team members + # Enabled: enabled for everyone able to access the project + # + + # Permision levels + DISABLED = 0 + PRIVATE = 10 + ENABLED = 20 + + FEATURES = %i(issues merge_requests wiki snippets builds) + + belongs_to :project + + def feature_available?(feature, user) + raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature) + + get_permission(user, public_send("#{feature}_access_level")) + end + + def builds_enabled? + return true unless builds_access_level + + builds_access_level > DISABLED + end + + def wiki_enabled? + return true unless wiki_access_level + + wiki_access_level > DISABLED + end + + def merge_requests_enabled? + return true unless merge_requests_access_level + + merge_requests_access_level > DISABLED + end + + private + + def get_permission(user, level) + case level + when DISABLED + false + when PRIVATE + user && (project.team.member?(user) || user.admin?) + when ENABLED + true + else + true + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 8f5958333d7..6996740eebd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -433,7 +433,7 @@ class User < ActiveRecord::Base # # This logic is duplicated from `Ability#project_abilities` into a SQL form. def projects_where_can_admin_issues - authorized_projects(Gitlab::Access::REPORTER).non_archived.where.not(issues_enabled: false) + authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled end def is_admin? diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 15a9f2f0dca..acf36d422d1 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -145,28 +145,28 @@ class ProjectPolicy < BasePolicy end def disabled_features! - unless project.issues_enabled + unless project.feature_available?(:issues, user) cannot!(*named_abilities(:issue)) end - unless project.merge_requests_enabled + unless project.feature_available?(:merge_requests, user) cannot!(*named_abilities(:merge_request)) end - unless project.issues_enabled || project.merge_requests_enabled + unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user) cannot!(*named_abilities(:label)) cannot!(*named_abilities(:milestone)) end - unless project.snippets_enabled + unless project.feature_available?(:snippets, user) cannot!(*named_abilities(:project_snippet)) end - unless project.has_wiki? + unless project.feature_available?(:wiki, user) || project.has_external_wiki? cannot!(*named_abilities(:wiki)) end - unless project.builds_enabled + unless project.feature_available?(:builds, user) cannot!(*named_abilities(:build)) cannot!(*named_abilities(:pipeline)) cannot!(*named_abilities(:environment)) diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index 9a187f5d694..6973191b203 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -8,16 +8,18 @@ module Ci builds = if current_runner.shared? builds. - # don't run projects which have not enabled shared runners - joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }). + # don't run projects which have not enabled shared runners and builds + joins(:project).where(projects: { shared_runners_enabled: true }). + joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id'). # this returns builds that are ordered by number of running builds # we prefer projects that don't use shared runners at all joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). + where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') else # do run projects which are only assigned to this runner (FIFO) - builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC') + builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC') end build = builds.find do |build| diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb index 08c1f72d65a..1262ecbc29a 100644 --- a/app/services/merge_requests/get_urls_service.rb +++ b/app/services/merge_requests/get_urls_service.rb @@ -31,7 +31,7 @@ module MergeRequests def get_branches(changes) return [] if project.empty_repo? - return [] unless project.merge_requests_enabled + return [] unless project.merge_requests_enabled? changes_list = Gitlab::ChangesList.new(changes) changes_list.map do |change| diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 55956be2844..be749ba4a1c 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -7,7 +7,6 @@ module Projects def execute forked_from_project_id = params.delete(:forked_from_project_id) import_data = params.delete(:import_data) - @project = Project.new(params) # Make sure that the user is allowed to use the specified visibility level @@ -81,8 +80,7 @@ module Projects log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") unless @project.gitlab_project_import? - @project.create_wiki if @project.wiki_enabled? - + @project.create_wiki if @project.feature_available?(:wiki, current_user) @project.build_missing_services @project.create_labels diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index de6dc38cc8e..a2de4dccece 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -8,7 +8,6 @@ module Projects name: @project.name, path: @project.path, shared_runners_enabled: @project.shared_runners_enabled, - builds_enabled: @project.builds_enabled, namespace_id: @params[:namespace].try(:id) || current_user.namespace.id } @@ -17,6 +16,9 @@ module Projects end new_project = CreateService.new(current_user, new_params).execute + builds_access_level = @project.project_feature.builds_access_level + new_project.project_feature.update_attributes(builds_access_level: builds_access_level) + new_project end diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 52a5bdc1a1b..613b8b7d301 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -26,7 +26,7 @@ %span Protected Branches - - if @project.builds_enabled? + - if @project.feature_available?(:builds, current_user) = nav_link(controller: :runners) do = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do %span diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 836c6d7b83f..f6d751a343e 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -44,52 +44,56 @@ %hr %fieldset.features.append-bottom-0 %h5.prepend-top-0 - Features - .form-group - .checkbox - = f.label :issues_enabled do - = f.check_box :issues_enabled - %strong Issues - %br - %span.descr Lightweight issue tracking system for this project - .form-group - .checkbox - = f.label :merge_requests_enabled do - = f.check_box :merge_requests_enabled - %strong Merge Requests - %br - %span.descr Submit changes to be merged upstream - .form-group - .checkbox - = f.label :builds_enabled do - = f.check_box :builds_enabled - %strong Builds - %br - %span.descr Test and deploy your changes before merge - .form-group - .checkbox - = f.label :wiki_enabled do - = f.check_box :wiki_enabled - %strong Wiki - %br - %span.descr Pages for project documentation - .form-group - .checkbox - = f.label :snippets_enabled do - = f.check_box :snippets_enabled - %strong Snippets - %br - %span.descr Share code pastes with others out of git repository - - if Gitlab.config.lfs.enabled && current_user.admin? - .form-group - .checkbox - = f.label :lfs_enabled do - = f.check_box :lfs_enabled, checked: @project.lfs_enabled? - %strong LFS - %br - %span.descr - Git Large File Storage - = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + Feature Visibility + + = f.fields_for :project_feature do |feature_fields| + .form_group.prepend-top-20 + .row + .col-md-9 + = feature_fields.label :issues_access_level, "Issues", class: 'label-light' + %span.help-block Lightweight issue tracking system for this project + .col-md-3 + = project_feature_access_select(:issues_access_level) + + .row + .col-md-9 + = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' + %span.help-block Submit changes to be merged upstream + .col-md-3 + = project_feature_access_select(:merge_requests_access_level) + + .row + .col-md-9 + = feature_fields.label :builds_access_level, "Builds", class: 'label-light' + %span.help-block Submit Test and deploy your changes before merge + .col-md-3 + = project_feature_access_select(:builds_access_level) + + .row + .col-md-9 + = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light' + %span.help-block Pages for project documentation + .col-md-3 + = project_feature_access_select(:wiki_access_level) + + .row + .col-md-9 + = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' + %span.help-block Share code pastes with others out of Git repository + .col-md-3 + = project_feature_access_select(:snippets_access_level) + + - if Gitlab.config.lfs.enabled && current_user.admin? + .form-group + .checkbox + = f.label :lfs_enabled do + = f.check_box :lfs_enabled, checked: @project.lfs_enabled? + %strong LFS + %br + %span.descr + Git Large File Storage + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + - if Gitlab.config.registry.enabled .form-group .checkbox @@ -98,7 +102,7 @@ %strong Container Registry %br %span.descr Enable Container Registry for this repository - %hr + = render 'merge_request_settings', f: f %hr %fieldset.features.append-bottom-default diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index a231d684559..082e2cb4d8c 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -12,7 +12,7 @@ = link_to 'Commits', commits_namespace_project_graph_path = nav_link(action: :languages) do = link_to 'Languages', languages_namespace_project_graph_path - - if @project.builds_enabled? + - if @project.feature_available?(:builds, current_user) = nav_link(action: :ci) do = link_to ci_namespace_project_graph_path do Continuous Integration diff --git a/db/migrate/20160831214002_create_project_features.rb b/db/migrate/20160831214002_create_project_features.rb new file mode 100644 index 00000000000..2d76a015a08 --- /dev/null +++ b/db/migrate/20160831214002_create_project_features.rb @@ -0,0 +1,16 @@ +class CreateProjectFeatures < ActiveRecord::Migration + DOWNTIME = false + + def change + create_table :project_features do |t| + t.belongs_to :project, index: true + t.integer :merge_requests_access_level + t.integer :issues_access_level + t.integer :wiki_access_level + t.integer :snippets_access_level + t.integer :builds_access_level + + t.timestamps + end + end +end diff --git a/db/migrate/20160831214543_migrate_project_features.rb b/db/migrate/20160831214543_migrate_project_features.rb new file mode 100644 index 00000000000..93f9821bc76 --- /dev/null +++ b/db/migrate/20160831214543_migrate_project_features.rb @@ -0,0 +1,44 @@ +class MigrateProjectFeatures < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = + <<-EOT + Migrating issues_enabled, merge_requests_enabled, wiki_enabled, builds_enabled, snippets_enabled fields from projects to + a new table called project_features. + EOT + + def up + sql = + %Q{ + INSERT INTO project_features(project_id, issues_access_level, merge_requests_access_level, wiki_access_level, + builds_access_level, snippets_access_level, created_at, updated_at) + SELECT + id AS project_id, + CASE WHEN issues_enabled IS true THEN 20 ELSE 0 END AS issues_access_level, + CASE WHEN merge_requests_enabled IS true THEN 20 ELSE 0 END AS merge_requests_access_level, + CASE WHEN wiki_enabled IS true THEN 20 ELSE 0 END AS wiki_access_level, + CASE WHEN builds_enabled IS true THEN 20 ELSE 0 END AS builds_access_level, + CASE WHEN snippets_enabled IS true THEN 20 ELSE 0 END AS snippets_access_level, + created_at, + updated_at + FROM projects + } + + execute(sql) + end + + def down + sql = %Q{ + UPDATE projects + SET + issues_enabled = COALESCE((SELECT CASE WHEN issues_access_level = 20 THEN true ELSE false END AS issues_enabled FROM project_features WHERE project_features.project_id = projects.id), true), + merge_requests_enabled = COALESCE((SELECT CASE WHEN merge_requests_access_level = 20 THEN true ELSE false END AS merge_requests_enabled FROM project_features WHERE project_features.project_id = projects.id),true), + wiki_enabled = COALESCE((SELECT CASE WHEN wiki_access_level = 20 THEN true ELSE false END AS wiki_enabled FROM project_features WHERE project_features.project_id = projects.id), true), + builds_enabled = COALESCE((SELECT CASE WHEN builds_access_level = 20 THEN true ELSE false END AS builds_enabled FROM project_features WHERE project_features.project_id = projects.id), true), + snippets_enabled = COALESCE((SELECT CASE WHEN snippets_access_level = 20 THEN true ELSE false END AS snippets_enabled FROM project_features WHERE project_features.project_id = projects.id),true) + } + + execute(sql) + end +end diff --git a/db/migrate/20160831223750_remove_features_enabled_from_projects.rb b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb new file mode 100644 index 00000000000..a2c207b49ea --- /dev/null +++ b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveFeaturesEnabledFromProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + # Set this constant to true if this migration requires downtime. + DOWNTIME = true + DOWNTIME_REASON = "Removing fields from database requires downtine." + + def up + remove_column :projects, :issues_enabled + remove_column :projects, :merge_requests_enabled + remove_column :projects, :builds_enabled + remove_column :projects, :wiki_enabled + remove_column :projects, :snippets_enabled + end + + # Ugly SQL but the only way i found to make it work on both Postgres and Mysql + # It will be slow but it is ok since it is a revert method + def down + add_column_with_default(:projects, :issues_enabled, :boolean, default: true, allow_null: false) + add_column_with_default(:projects, :merge_requests_enabled, :boolean, default: true, allow_null: false) + add_column_with_default(:projects, :builds_enabled, :boolean, default: true, allow_null: false) + add_column_with_default(:projects, :wiki_enabled, :boolean, default: true, allow_null: false) + add_column_with_default(:projects, :snippets_enabled, :boolean, default: true, allow_null: false) + end +end diff --git a/db/schema.rb b/db/schema.rb index 963d528d170..af6e74a4e25 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160830232601) do +ActiveRecord::Schema.define(version: 20160831223750) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -766,6 +766,19 @@ ActiveRecord::Schema.define(version: 20160830232601) do add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree + create_table "project_features", force: :cascade do |t| + t.integer "project_id" + t.integer "merge_requests_access_level" + t.integer "issues_access_level" + t.integer "wiki_access_level" + t.integer "snippets_access_level" + t.integer "builds_access_level" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree + create_table "project_group_links", force: :cascade do |t| t.integer "project_id", null: false t.integer "group_id", null: false @@ -790,11 +803,7 @@ ActiveRecord::Schema.define(version: 20160830232601) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" t.integer "visibility_level", default: 0, null: false @@ -808,7 +817,6 @@ ActiveRecord::Schema.define(version: 20160830232601) do t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 66542861781..1498cb361c8 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -104,6 +104,15 @@ will find the option to flag the user as external. By default new users are not set as external users. This behavior can be changed by an administrator under **Admin > Application Settings**. +## Project features + +Project features like wiki and issues can be hidden from users depending on +which visibility level you select on project settings. + +- Disabled: disabled for everyone +- Only team members: only team members will see even if your project is public or internal +- Everyone with access: everyone can see depending on your project visibility level + ## GitLab CI GitLab CI permissions rely on the role the user has in GitLab. There are four diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 76fefee9254..975c879149e 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -5,7 +5,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'change project settings' do fill_in 'project_name_edit', with: 'NewName' - uncheck 'project_issues_enabled' + select 'Disabled', from: 'project_project_feature_attributes_issues_access_level' end step 'I save project' do diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 0b4920883b8..afbd8ef1233 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -15,7 +15,7 @@ module SharedProject # Create a specific project called "Shop" step 'I own project "Shop"' do @project = Project.find_by(name: "Shop") - @project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true) + @project ||= create(:project, name: "Shop", namespace: @user.namespace) @project.team << [@user, :master] end @@ -41,6 +41,8 @@ module SharedProject step 'I own project "Forum"' do @project = Project.find_by(name: "Forum") @project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project') + @project.build_project_feature + @project.project_feature.save @project.team << [@user, :master] end @@ -95,7 +97,7 @@ module SharedProject step 'I should see project settings' do expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project) expect(page).to have_content("Project name") - expect(page).to have_content("Features") + expect(page).to have_content("Feature Visibility") end def current_project diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fe7468dd681..e7fe437ee0d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -76,7 +76,15 @@ module API expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled + expose :container_registry_enabled + + # Expose old field names with the new permissions methods to keep API compatible + expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:user]) } + expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:user]) } + expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:user]) } + expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:user]) } + expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) } + expose :created_at, :last_activity_at expose :shared_runners_enabled, :lfs_enabled expose :creator_id @@ -84,7 +92,7 @@ module API expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } expose :avatar_url expose :star_count, :forks_count - expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? } + expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:user]) && project.default_issues_tracker? } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds expose :shared_with_groups do |project, options| diff --git a/lib/api/groups.rb b/lib/api/groups.rb index f981ec0dbfe..d2df77238d5 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -97,7 +97,7 @@ module API group = find_group(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) projects = paginate projects - present projects, with: Entities::Project + present projects, with: Entities::Project, user: current_user end # Transfer a project to the Group namespace diff --git a/lib/api/projects.rb b/lib/api/projects.rb index f8979a1cc29..a1fd598414a 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -51,7 +51,7 @@ module API @projects = current_user.viewable_starred_projects @projects = filter_projects(@projects) @projects = paginate @projects - present @projects, with: Entities::Project + present @projects, with: Entities::Project, user: current_user end # Get all projects for admin user diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 1b2a5eb8f52..4fdc2f46be0 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -168,7 +168,7 @@ module Gitlab unless project.wiki_enabled? wiki = WikiFormatter.new(project) gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) - project.update_attribute(:wiki_enabled, true) + project.project.update_attribute(:wiki_access_level, ProjectFeature::ENABLED) end rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index f4221003db5..0ad30c1e5b2 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -11,7 +11,7 @@ module Gitlab end def execute - ::Projects::CreateService.new( + project = ::Projects::CreateService.new( current_user, name: repo.name, path: repo.name, @@ -20,9 +20,15 @@ module Gitlab visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, import_type: "github", import_source: repo.full_name, - import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"), - wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later + import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@") ).execute + + # If repo has wiki we'll import it later + if repo.has_wiki? && project + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + end + + project end end end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 1da51043611..c2e8a1ca5dd 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -39,15 +39,12 @@ project_tree: - :labels - milestones: - :events + - :project_feature # Only include the following attributes for the models specified. included_attributes: project: - :description - - :issues_enabled - - :merge_requests_enabled - - :wiki_enabled - - :snippets_enabled - :visibility_level - :archived user: @@ -72,4 +69,4 @@ methods: statuses: - :type merge_request_diff: - - :utf8_st_diffs \ No newline at end of file + - :utf8_st_diffs diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index b8a28f43707..72a3ebf2ebd 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::SnippetsController do - let(:project) { create(:project_empty_repo, :public, snippets_enabled: true) } + let(:project) { create(:project_empty_repo, :public) } let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index f82d68a1816..fb84ba07d25 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -8,7 +8,6 @@ FactoryGirl.define do path { name.downcase.gsub(/\s/, '_') } namespace creator - snippets_enabled true trait :public do visibility_level Gitlab::VisibilityLevel::PUBLIC @@ -27,6 +26,26 @@ FactoryGirl.define do project.create_repository end end + + # Nest Project Feature attributes + transient do + wiki_access_level ProjectFeature::ENABLED + builds_access_level ProjectFeature::ENABLED + snippets_access_level ProjectFeature::ENABLED + issues_access_level ProjectFeature::ENABLED + merge_requests_access_level ProjectFeature::ENABLED + end + + after(:create) do |project, evaluator| + project.project_feature. + update_attributes( + wiki_access_level: evaluator.wiki_access_level, + builds_access_level: evaluator.builds_access_level, + snippets_access_level: evaluator.snippets_access_level, + issues_access_level: evaluator.issues_access_level, + merge_requests_access_level: evaluator.merge_requests_access_level, + ) + end end # Project with empty repository diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb new file mode 100644 index 00000000000..9b487e350f2 --- /dev/null +++ b/spec/features/projects/features_visibility_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' +include WaitForAjax + +describe 'Edit Project Settings', feature: true do + let(:member) { create(:user) } + let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') } + let(:non_member) { create(:user) } + + describe 'project features visibility selectors', js: true do + before do + project.team << [member, :master] + login_as(member) + end + + tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" } + + tools.each do |tool_name, shortcut_name| + describe "feature #{tool_name}" do + it 'toggles visibility' do + visit edit_namespace_project_path(project.namespace, project) + + select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level" + click_button 'Save changes' + wait_for_ajax + expect(page).not_to have_selector(".shortcuts-#{shortcut_name}") + + select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level" + click_button 'Save changes' + wait_for_ajax + expect(page).to have_selector(".shortcuts-#{shortcut_name}") + + select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level" + click_button 'Save changes' + wait_for_ajax + expect(page).to have_selector(".shortcuts-#{shortcut_name}") + + sleep 0.1 + end + end + end + end + + describe 'project features visibility pages' do + before do + @tools = + { + builds: namespace_project_pipelines_path(project.namespace, project), + issues: namespace_project_issues_path(project.namespace, project), + wiki: namespace_project_wiki_path(project.namespace, project, :home), + snippets: namespace_project_snippets_path(project.namespace, project), + merge_requests: namespace_project_merge_requests_path(project.namespace, project), + } + end + + context 'normal user' do + it 'renders 200 if tool is enabled' do + @tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::ENABLED) + visit url + expect(page.status_code).to eq(200) + end + end + + it 'renders 404 if feature is disabled' do + @tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED) + visit url + expect(page.status_code).to eq(404) + end + end + + it 'renders 404 if feature is enabled only for team members' do + project.team.truncate + + @tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) + visit url + expect(page.status_code).to eq(404) + end + end + + it 'renders 200 if users is member of group' do + group = create(:group) + project.group = group + project.save + + group.add_owner(member) + + @tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) + visit url + expect(page.status_code).to eq(200) + end + end + end + + context 'admin user' do + before do + non_member.update_attribute(:admin, true) + login_as(non_member) + end + + it 'renders 404 if feature is disabled' do + @tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED) + visit url + expect(page.status_code).to eq(404) + end + end + + it 'renders 200 if feature is enabled only for team members' do + project.team.truncate + + @tools.each do |method_name, url| + project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE) + visit url + expect(page.status_code).to eq(200) + end + end + end + end +end diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz index 7bb0d26b21c..e14b2705704 100644 Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index b0772cad312..7c23e02d05a 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -7,7 +7,8 @@ describe Gitlab::Auth, lib: true do it 'recognizes CI' do token = '123' project = create(:empty_project) - project.update_attributes(runners_token: token, builds_enabled: true) + project.update_attributes(runners_token: token) + ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token') diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index b7c3bc4e1a7..3fb8de81545 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::GithubImport::Importer, lib: true do describe '#execute' do context 'when an error occurs' do - let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_enabled: false) } + let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_access_level: ProjectFeature::DISABLED) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index cbbf98dca94..5114f9c55e1 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -1,9 +1,5 @@ { "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", - "issues_enabled": true, - "merge_requests_enabled": true, - "wiki_enabled": true, - "snippets_enabled": false, "visibility_level": 10, "archived": false, "issues": [ @@ -7307,4 +7303,4 @@ "protected_branches": [ ] -} \ No newline at end of file +} diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 4d857945fde..a07ef279e68 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do let(:user) { create(:user) } let(:namespace) { create(:namespace, owner: user) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } - let(:project) { create(:empty_project, name: 'project', path: 'project') } + let!(:project) { create(:empty_project, name: 'project', path: 'project', builds_access_level: ProjectFeature::DISABLED, issues_access_level: ProjectFeature::DISABLED) } let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } let(:restored_project_json) { project_tree_restorer.restore } @@ -18,6 +18,17 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(restored_project_json).to be true end + it 'restore correct project features' do + restored_project_json + project = Project.find_by_path('project') + + expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.builds_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::ENABLED) + expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::ENABLED) + expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED) + end + it 'creates a valid pipeline note' do restored_project_json diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 3a86a4ce07c..d891c2d0cc6 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -111,6 +111,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty end + it 'has project feature' do + project_feature = saved_project_json['project_feature'] + expect(project_feature).not_to be_empty + expect(project_feature["issues_access_level"]).to eq(ProjectFeature::DISABLED) + expect(project_feature["wiki_access_level"]).to eq(ProjectFeature::ENABLED) + expect(project_feature["builds_access_level"]).to eq(ProjectFeature::PRIVATE) + end + it 'does not complain about non UTF-8 characters in MR diffs' do ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'") @@ -154,6 +162,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do create(:event, target: milestone, project: project, action: Event::CREATED, author: user) + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED) + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::PRIVATE) + project end diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb index b6dec41d218..3ceb1e7e803 100644 --- a/spec/lib/gitlab/import_export/reader_spec.rb +++ b/spec/lib/gitlab/import_export/reader_spec.rb @@ -32,6 +32,12 @@ describe Gitlab::ImportExport::Reader, lib: true do expect(described_class.new(shared: shared).project_tree).to match(include: [:issues]) end + it 'generates the correct hash for a single project feature relation' do + setup_yaml(project_tree: [:project_feature]) + + expect(described_class.new(shared: shared).project_tree).to match(include: [:project_feature]) + end + it 'generates the correct hash for a multiple project relation' do setup_yaml(project_tree: [:issues, :snippets]) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index b05510342bc..1bdf005c823 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -220,13 +220,13 @@ describe Ability, lib: true do end describe '.project_disabled_features_rules' do - let(:project) { build(:project) } + let(:project) { create(:project, wiki_access_level: ProjectFeature::DISABLED) } subject { described_class.allowed(project.owner, project) } context 'wiki named abilities' do it 'disables wiki abilities if the project has no wiki' do - expect(project).to receive(:has_wiki?).and_return(false) + expect(project).to receive(:has_external_wiki?).and_return(false) expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki) end end diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb new file mode 100644 index 00000000000..5363aea4d22 --- /dev/null +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe ProjectFeaturesCompatibility do + let(:project) { create(:project) } + let(:features) { %w(issues wiki builds merge_requests snippets) } + + # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table + # All those fields got moved to a new table called project_feature and are now integers instead of booleans + # This spec tests if the described concern makes sure parameters received by the API are correctly parsed to the new table + # So we can keep it compatible + + it "converts fields from 'true' to ProjectFeature::ENABLED" do + features.each do |feature| + project.update_attribute("#{feature}_enabled".to_sym, "true") + expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED) + end + end + + it "converts fields from 'false' to ProjectFeature::DISABLED" do + features.each do |feature| + project.update_attribute("#{feature}_enabled".to_sym, "false") + expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED) + end + end +end diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb new file mode 100644 index 00000000000..8d554a01be5 --- /dev/null +++ b/spec/models/project_feature_spec.rb @@ -0,0 +1,91 @@ +require 'spec_helper' + +describe ProjectFeature do + let(:project) { create(:project) } + let(:user) { create(:user) } + + describe '#feature_available?' do + let(:features) { %w(issues wiki builds merge_requests snippets) } + + context 'when features are disabled' do + it "returns false" do + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::DISABLED) + expect(project.feature_available?(:issues, user)).to eq(false) + end + end + end + + context 'when features are enabled only for team members' do + it "returns false when user is not a team member" do + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE) + expect(project.feature_available?(:issues, user)).to eq(false) + end + end + + it "returns true when user is a team member" do + project.team << [user, :developer] + + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE) + expect(project.feature_available?(:issues, user)).to eq(true) + end + end + + it "returns true when user is a member of project group" do + group = create(:group) + project = create(:project, namespace: group) + group.add_developer(user) + + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE) + expect(project.feature_available?(:issues, user)).to eq(true) + end + end + + it "returns true if user is an admin" do + user.update_attribute(:admin, true) + + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE) + expect(project.feature_available?(:issues, user)).to eq(true) + end + end + end + + context 'when feature is enabled for everyone' do + it "returns true" do + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::ENABLED) + expect(project.feature_available?(:issues, user)).to eq(true) + end + end + end + end + + describe '#*_enabled?' do + let(:features) { %w(wiki builds merge_requests) } + + it "returns false when feature is disabled" do + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::DISABLED) + expect(project.public_send("#{feature}_enabled?")).to eq(false) + end + end + + it "returns true when feature is enabled only for team members" do + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE) + expect(project.public_send("#{feature}_enabled?")).to eq(true) + end + end + + it "returns true when feature is enabled for everyone" do + features.each do |feature| + project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::ENABLED) + expect(project.public_send("#{feature}_enabled?")).to eq(true) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dd309ea1b68..4a41fafb84d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -508,7 +508,7 @@ describe Project, models: true do describe '#has_wiki?' do let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) } - let(:wiki_enabled_project) { build(:project, wiki_enabled: true) } + let(:wiki_enabled_project) { build(:project) } let(:external_wiki_project) { build(:project, has_external_wiki: true) } it 'returns true if project is wiki enabled or has external wiki' do @@ -734,8 +734,6 @@ describe Project, models: true do describe '#builds_enabled' do let(:project) { create :project } - before { project.builds_enabled = true } - subject { project.builds_enabled } it { expect(project.builds_enabled?).to be_truthy } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8eb0c5033c9..a1770d96f83 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1006,8 +1006,7 @@ describe User, models: true do end it 'does not include projects for which issues are disabled' do - project = create(:project) - project.update_attributes(issues_enabled: false) + project = create(:project, issues_access_level: ProjectFeature::DISABLED) expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, project)).to eq(false) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 63f2467be63..28aa56e8644 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -73,7 +73,7 @@ describe API::API, api: true do end it 'does not include open_issues_count' do - project.update_attributes( { issues_enabled: false } ) + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) get api('/projects', user) expect(response.status).to eq 200 @@ -231,8 +231,15 @@ describe API::API, api: true do post api('/projects', user), project project.each_pair do |k, v| + next if %i{ issues_enabled merge_requests_enabled wiki_enabled }.include?(k) expect(json_response[k.to_s]).to eq(v) end + + # Check feature permissions attributes + project = Project.find_by_path(project[:path]) + expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED) end it 'sets a project as public' do diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index afaf4b7cefb..9ca3b021aa2 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -289,7 +289,8 @@ describe 'Git HTTP requests', lib: true do let(:project) { FactoryGirl.create :empty_project } before do - project.update_attributes(runners_token: token, builds_enabled: true) + project.update_attributes(runners_token: token) + project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED) end it "downloads get status 200" do diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index c6172b9cc7d..fc42b534dca 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -22,19 +22,20 @@ describe JwtController do context 'when using authorized request' do context 'using CI token' do - let(:project) { create(:empty_project, runners_token: 'token', builds_enabled: builds_enabled) } + let(:project) { create(:empty_project, runners_token: 'token') } let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } } - subject! { get '/jwt/auth', parameters, headers } - context 'project with enabled CI' do - let(:builds_enabled) { true } - + subject! { get '/jwt/auth', parameters, headers } it { expect(service_class).to have_received(:new).with(project, nil, parameters) } end context 'project with disabled CI' do - let(:builds_enabled) { false } + before do + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) + end + + subject! { get '/jwt/auth', parameters, headers } it { expect(response).to have_http_status(403) } end diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb index 026d0ca6534..1e21a32a062 100644 --- a/spec/services/ci/register_build_service_spec.rb +++ b/spec/services/ci/register_build_service_spec.rb @@ -151,6 +151,25 @@ module Ci it { expect(build.runner).to eq(specific_runner) } end end + + context 'disallow when builds are disabled' do + before do + project.update(shared_runners_enabled: true) + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) + end + + context 'and uses shared runner' do + let(:build) { service.execute(shared_runner) } + + it { expect(build).to be_nil } + end + + context 'and uses specific runner' do + let(:build) { service.execute(specific_runner) } + + it { expect(build).to be_nil } + end + end end end end diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb index 8a4b76367e3..3a71776e81f 100644 --- a/spec/services/merge_requests/get_urls_service_spec.rb +++ b/spec/services/merge_requests/get_urls_service_spec.rb @@ -50,7 +50,7 @@ describe MergeRequests::GetUrlsService do let(:changes) { new_branch_changes } before do - project.merge_requests_enabled = false + project.project_feature.update_attribute(:merge_requests_access_level, ProjectFeature::DISABLED) end it_behaves_like 'no_merge_request_url' diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index bbced59ff02..3ea1273abc3 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -69,7 +69,7 @@ describe Projects::CreateService, services: true do context 'wiki_enabled false does not create wiki repository directory' do before do - @opts.merge!(wiki_enabled: false) + @opts.merge!( { project_feature_attributes: { wiki_access_level: ProjectFeature::DISABLED } }) @project = create_project(@user, @opts) @path = ProjectWiki.new(@project, @user).send(:path_to_repo) end @@ -85,7 +85,7 @@ describe Projects::CreateService, services: true do context 'global builds_enabled false does not enable CI by default' do before do - @opts.merge!(builds_enabled: false) + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) end it { is_expected.to be_falsey } @@ -93,7 +93,7 @@ describe Projects::CreateService, services: true do context 'global builds_enabled true does enable CI by default' do before do - @opts.merge!(builds_enabled: true) + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) end it { is_expected.to be_truthy } diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index 05e07789dac..59cfb2c8e3a 100644 --- a/spec/workers/repository_check/single_repository_worker_spec.rb +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -5,7 +5,7 @@ describe RepositoryCheck::SingleRepositoryWorker do subject { described_class.new } it 'passes when the project has no push events' do - project = create(:project_empty_repo, wiki_enabled: false) + project = create(:project_empty_repo, wiki_access_level: ProjectFeature::DISABLED) project.events.destroy_all break_repo(project) @@ -25,7 +25,7 @@ describe RepositoryCheck::SingleRepositoryWorker do end it 'fails if the wiki repository is broken' do - project = create(:project_empty_repo, wiki_enabled: true) + project = create(:project_empty_repo, wiki_access_level: ProjectFeature::ENABLED) project.create_wiki # Test sanity: everything should be fine before the wiki repo is broken @@ -39,7 +39,7 @@ describe RepositoryCheck::SingleRepositoryWorker do end it 'skips wikis when disabled' do - project = create(:project_empty_repo, wiki_enabled: false) + project = create(:project_empty_repo, wiki_access_level: ProjectFeature::DISABLED) # Make sure the test would fail if the wiki repo was checked break_wiki(project) @@ -49,7 +49,7 @@ describe RepositoryCheck::SingleRepositoryWorker do end it 'creates missing wikis' do - project = create(:project_empty_repo, wiki_enabled: true) + project = create(:project_empty_repo, wiki_access_level: ProjectFeature::ENABLED) FileUtils.rm_rf(wiki_path(project)) subject.perform(project.id) -- cgit v1.2.1 From 85e494890a931b6031aed2f9f2e50d30d6f37a32 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 25 Aug 2016 16:15:44 -0500 Subject: Replace play icon font with svg --- CHANGELOG | 1 + app/assets/stylesheets/framework/dropdowns.scss | 7 +++++++ app/assets/stylesheets/pages/environments.scss | 5 +++-- app/assets/stylesheets/pages/pipelines.scss | 7 +++++++ app/views/projects/ci/builds/_build.html.haml | 2 +- app/views/projects/ci/pipelines/_pipeline.html.haml | 4 ++-- app/views/projects/deployments/_actions.haml | 4 ++-- app/views/shared/icons/_icon_play.svg | 4 +++- 8 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9837b2edb9d..08745aea3f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -51,6 +51,7 @@ v 8.12.0 (unreleased) - Load branches asynchronously in Cherry Pick and Revert dialogs. - Add merge request versions !5467 - Change using size to use count and caching it for number of group members. !5935 + - Replace play icon font with svg (ClemMakesApps) - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Reduce number of database queries on builds tab - Capitalize mentioned issue timeline notes (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index edb2ff01f88..b0ba112476b 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -183,6 +183,13 @@ &.dropdown-menu-user-link { line-height: 16px; } + + .icon-play { + fill: $table-text-gray; + margin-right: 6px; + height: 12px; + width: 11px; + } } .dropdown-header { diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 55f9d4a0011..d01c60ee6ab 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -4,8 +4,9 @@ margin: 0; } - .fa-play { - font-size: 14px; + .icon-play { + height: 13px; + width: 12px; } .dropdown-new { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 2d6653cd867..7aabafe11a5 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -215,6 +215,13 @@ border-color: $border-white-normal; } } + + .btn { + .icon-play { + height: 13px; + width: 12px; + } + } } } diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 1fdf32466f2..73de8abe55b 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -89,4 +89,4 @@ = icon('repeat') - elsif build.playable? = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do - = icon('play') + = custom_icon('icon_play') diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index b119f6edf14..bb9493f5158 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -66,13 +66,13 @@ - if actions.any? .btn-group %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} - = icon("play") + = custom_icon('icon_play') %b.caret %ul.dropdown-menu.dropdown-menu-align-right - actions.each do |build| %li = link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do - = icon("play") + = custom_icon('icon_play') %span= build.name.humanize - if artifacts.present? .btn-group diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index f7bf3b834ef..16d134eb6b6 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -5,13 +5,13 @@ .inline .dropdown %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} - = icon("play") + = custom_icon('icon_play') %b.caret %ul.dropdown-menu.dropdown-menu-align-right - actions.each do |action| %li = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do - = icon("play") + = custom_icon('icon_play') %span= action.name.humanize - if local_assigns.fetch(:allow_rollback, false) diff --git a/app/views/shared/icons/_icon_play.svg b/app/views/shared/icons/_icon_play.svg index 80a6d41dbf6..e965afa9a56 100644 --- a/app/views/shared/icons/_icon_play.svg +++ b/app/views/shared/icons/_icon_play.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + \ No newline at end of file -- cgit v1.2.1 From 2560fc5adbc533d87e8ca56b471ed56bc84d95f9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 24 Aug 2016 09:38:31 -0500 Subject: Remove inconsistent font weight for sidebar's labels --- CHANGELOG | 1 + app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 77d44296880..a3d9508dd8f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 8.12.0 (unreleased) - Don't expose a user's token in the `/api/v3/user` API (!6047) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Ability to manage project issues, snippets, wiki, merge requests and builds access level + - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Remove redundant pipeline tooltips (ClemMakesApps) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index c1b50e65af5..b13daaf43c9 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -118,7 +118,7 @@ = icon('spinner spin', class: 'block-loading') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' - .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) } + .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) } - if issuable.labels_array.any? - issuable.labels_array.each do |label| = link_to_label(label, type: issuable.to_ability_name) -- cgit v1.2.1 From dd0431c5f7c93a2f9cee6766c9c8f3b0536022e9 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 1 Sep 2016 12:33:37 -0500 Subject: Some minor updates for upgrade guides for 8.12. --- doc/install/installation.md | 4 ++-- doc/update/8.11-to-8.12.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 2d0932d4f04..9522c3e7170 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -268,9 +268,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-11-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-12-stable gitlab -**Note:** You can change `8-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-12-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 953e9d7e74c..c8ca42f97bc 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -82,7 +82,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.7.8 +sudo -u git -H git checkout v0.7.11 sudo -u git -H make ``` -- cgit v1.2.1 From 9a10c0a8d542df2db2e75c230bf116bed421f8d5 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 1 Sep 2016 19:50:45 +0200 Subject: Rubocop syntax 2.3 --- .rubocop.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 282f4539f03..5bd31ccf329 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,8 +5,8 @@ require: inherit_from: .rubocop_todo.yml AllCops: - TargetRubyVersion: 2.1 - # Cop names are not displayed in offense messages by default. Change behavior + TargetRubyVersion: 2.3 + # Cop names are not d§splayed in offense messages by default. Change behavior # by overriding DisplayCopNames, or by giving the -D/--display-cop-names # option. DisplayCopNames: true @@ -192,6 +192,9 @@ Style/FlipFlop: Style/For: Enabled: true +# Checks if there is a magic comment to enforce string literals +Style/FrozenStringLiteralComment: + Enabled: false # Do not introduce global variables. Style/GlobalVars: Enabled: true -- cgit v1.2.1 From 191ed1faebfa486ea6b1f72ce764ece730d6e64a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 1 Sep 2016 13:34:50 -0500 Subject: Add curve to generic commit status pipeline --- .../generic_commit_statuses/_generic_commit_status_pipeline.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 31d40f6ad03..576d0bec51b 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,4 +1,5 @@ %li.build + .curve .build-content - if subject.target_url - link_to subject.target_url do -- cgit v1.2.1 From 0ee5efbd20bf99385183618dc0bcf74deaa20075 Mon Sep 17 00:00:00 2001 From: Luke Howell Date: Thu, 1 Sep 2016 14:52:43 +0000 Subject: Prepend blank line to close message on merge request - Added an extra new line to the prepend of the Close message Fixes #21710 --- CHANGELOG | 1 + app/services/merge_requests/build_service.rb | 2 +- spec/services/merge_requests/build_service_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c8748151b1c..e34312bb37c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Filter tags by name !6121 - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 290742f1506..e57791f6818 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -83,7 +83,7 @@ module MergeRequests closes_issue = "Closes ##{iid}" if merge_request.description.present? - merge_request.description += closes_issue.prepend("\n") + merge_request.description += closes_issue.prepend("\n\n") else merge_request.description = closes_issue end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 232508cda23..0d586e2216b 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -99,14 +99,14 @@ describe MergeRequests::BuildService, services: true do let(:source_branch) { "#{issue.iid}-fix-issue" } it 'appends "Closes #$issue-iid" to the description' do - expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\nCloses ##{issue.iid}") + expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\n\nCloses ##{issue.iid}") end context 'merge request already has a description set' do let(:description) { 'Merge request description' } it 'appends "Closes #$issue-iid" to the description' do - expect(merge_request.description).to eq("#{description}\nCloses ##{issue.iid}") + expect(merge_request.description).to eq("#{description}\n\nCloses ##{issue.iid}") end end -- cgit v1.2.1 From bf1337be949baef050ad99fc5e4c60e7ac4e3aae Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 1 Sep 2016 14:28:53 -0500 Subject: Change widths of content in MR pipeline tab --- app/assets/stylesheets/pages/pipelines.scss | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 2911576b66f..ee5d9de66d8 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -474,12 +474,16 @@ .pipelines.tab-pane { .content-list.pipelines { - overflow: scroll; + overflow: auto; } .stage { - max-width: 60px; - width: 60px; + max-width: 100px; + width: 100px; + } + + .pipeline-actions { + min-width: initial; } } -- cgit v1.2.1 From 7a3cdd4aa0b989fde9024ac52aa513cbbc1ae42d Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 1 Sep 2016 13:59:18 +0200 Subject: Move CHANGELOG entry to a proper version --- CHANGELOG | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0f4bb7ecc3d..23d214a7bdd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -60,6 +60,7 @@ v 8.12.0 (unreleased) - Fix hover leading space bug in pipeline graph !5980 - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fixed invisible scroll controls on build page on iPhone + - Fix error on raw build trace download for old builds stored in database !4822 v 8.11.4 (unreleased) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) @@ -74,9 +75,6 @@ v 8.11.4 (unreleased) v 8.11.3 - Do not enforce using hash with hidden key in CI configuration. !6079 - - Fix error on raw build trace download for old builds stored in database !4822 - -v 8.11.3 (unreleased) - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label - Don't show resolve conflicts link before MR status is updated -- cgit v1.2.1 From 52fe6098861bf36601be6555d2b39f366795ddd3 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 1 Sep 2016 22:17:05 +0200 Subject: Refactor Ci::Build#raw_trace --- app/models/ci/build.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f219cee4a62..61052437318 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -217,11 +217,8 @@ module Ci end def raw_trace - if File.exist?(path_to_trace) - File.read(path_to_trace) - elsif has_old_trace_file? - # Temporary fix for build trace data integrity - File.read(old_path_to_trace) + if File.exist?(trace_file_path) + File.read(trace_file_path) else # backward compatibility read_attribute :trace -- cgit v1.2.1 From f43a0470bd52132c2853582c51637a736dcce5e8 Mon Sep 17 00:00:00 2001 From: Regis Date: Thu, 1 Sep 2016 14:51:53 -0600 Subject: removed null return - renamed 'placeTop' to 'placeProfileAvatarsToTop' --- app/assets/javascripts/user.js.es6 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index 833f35df59b..6889d3a7491 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -2,12 +2,12 @@ global.User = class { constructor(opts) { this.opts = opts; - this.placeTop(); + this.placeProfileAvatarsToTop(); this.initTabs(); this.hideProjectLimitMessage(); } - placeTop() { + placeProfileAvatarsToTop() { $('.profile-groups-avatars').tooltip({ placement: 'top' }); @@ -28,7 +28,6 @@ path: path }); $(this).parents('.project-limit-message').remove(); - return; }); } } -- cgit v1.2.1 From 818c3f79f74f205f9e8762c4eab3ace28f311797 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 1 Sep 2016 17:22:03 -0500 Subject: Update CHANGELOG with 8.11.4 entries. --- CHANGELOG | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bdc5685ed27..76123f62b27 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -71,27 +71,33 @@ v 8.12.0 (unreleased) v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch + - Fix suggested colors options for new labels in the admin area. !6138 -v 8.11.4 (unreleased) - - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) +v 8.11.4 + - Fix resolving conflicts on forks. !6082 + - Fix diff commenting on merge requests created prior to 8.10. !6029 + - Fix pipelines tab layout regression. !5952 + - Fix "Wiki" link not appearing in navigation for projects with external wiki. !6057 + - Do not enforce using hash with hidden key in CI configuration. !6079 + - Fix hover leading space bug in pipeline graph !5980 - Fix sorting issues by "last updated" doesn't work after import from GitHub - GitHub importer use default project visibility for non-private projects - Creating an issue through our API now emails label subscribers !5720 - - Fix suggested colors options for new labels in the admin area - Block concurrent updates for Pipeline - - Fix resolving conflicts on forks - - Fix diff commenting on merge requests created prior to 8.10 + - Don't create groups for unallowed users when importing projects - Fix issue boards leak private label names and descriptions + - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) + - Remove gitorious. !5866 v 8.11.3 - - Do not enforce using hash with hidden key in CI configuration. !6079 - Allow system info page to handle case where info is unavailable - Label list shows all issues (opened or closed) with that label - Don't show resolve conflicts link before MR status is updated - - Fix "Wiki" link not appearing in navigation for projects with external wiki - - Fix IE11 fork button bug !598 + - Fix IE11 fork button bug !5982 - Don't prevent viewing the MR when git refs for conflicts can't be found on disk - Fix external issue tracker "Issues" link leading to 404s + - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters + - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 @@ -103,8 +109,6 @@ v 8.11.2 v 8.11.1 - Pulled due to packaging error. -v 8.11.0 (unreleased) - - Fix pipelines tab layout regression (brycepj) v 8.11.0 - Use test coverage value from the latest successful pipeline in badge. !5862 - Add test coverage report badge. !5708 -- cgit v1.2.1 From 10d9fa99e55be0d377ebd45cb767bccc7fd6b485 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 23 Aug 2016 16:28:21 -0500 Subject: Align add button on repository view --- CHANGELOG | 1 + app/assets/stylesheets/pages/tree.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..5c06192771f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.12.0 (unreleased) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Ability to manage project issues, snippets, wiki, merge requests and builds access level - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) + - Align add button on repository view (ClemMakesApps) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Remove redundant pipeline tooltips (ClemMakesApps) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 538f211c65b..cdd38442550 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -11,6 +11,10 @@ } } + .add-to-tree { + vertical-align: top; + } + .last-commit { max-width: 506px; -- cgit v1.2.1 From d8d699ff17a972729b89cb7505a95257ee859b57 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 25 Aug 2016 20:19:44 -0500 Subject: Convert datetime coffeescript spec to ES6 --- CHANGELOG | 1 + spec/javascripts/datetime_utility_spec.js.coffee | 50 ------------------ spec/javascripts/datetime_utility_spec.js.es6 | 64 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 50 deletions(-) delete mode 100644 spec/javascripts/datetime_utility_spec.js.coffee create mode 100644 spec/javascripts/datetime_utility_spec.js.es6 diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..a655db6e236 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -58,6 +58,7 @@ v 8.12.0 (unreleased) - Move to project dropdown with infinite scroll for better performance - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz) - Load branches asynchronously in Cherry Pick and Revert dialogs. + - Convert datetime coffeescript spec to ES6 (ClemMakesApps) - Add merge request versions !5467 - Change using size to use count and caching it for number of group members. !5935 - Replace play icon font with svg (ClemMakesApps) diff --git a/spec/javascripts/datetime_utility_spec.js.coffee b/spec/javascripts/datetime_utility_spec.js.coffee deleted file mode 100644 index 8bd113e7d86..00000000000 --- a/spec/javascripts/datetime_utility_spec.js.coffee +++ /dev/null @@ -1,50 +0,0 @@ -#= require lib/utils/datetime_utility - -describe 'Date time utils', -> - describe 'get day name', -> - it 'should return Sunday', -> - day = gl.utils.getDayName(new Date('07/17/2016')) - expect(day).toBe('Sunday') - - it 'should return Monday', -> - day = gl.utils.getDayName(new Date('07/18/2016')) - expect(day).toBe('Monday') - - it 'should return Tuesday', -> - day = gl.utils.getDayName(new Date('07/19/2016')) - expect(day).toBe('Tuesday') - - it 'should return Wednesday', -> - day = gl.utils.getDayName(new Date('07/20/2016')) - expect(day).toBe('Wednesday') - - it 'should return Thursday', -> - day = gl.utils.getDayName(new Date('07/21/2016')) - expect(day).toBe('Thursday') - - it 'should return Friday', -> - day = gl.utils.getDayName(new Date('07/22/2016')) - expect(day).toBe('Friday') - - it 'should return Saturday', -> - day = gl.utils.getDayName(new Date('07/23/2016')) - expect(day).toBe('Saturday') - - describe 'get day difference', -> - it 'should return 7', -> - firstDay = new Date('07/01/2016') - secondDay = new Date('07/08/2016') - difference = gl.utils.getDayDifference(firstDay, secondDay) - expect(difference).toBe(7) - - it 'should return 31', -> - firstDay = new Date('07/01/2016') - secondDay = new Date('08/01/2016') - difference = gl.utils.getDayDifference(firstDay, secondDay) - expect(difference).toBe(31) - - it 'should return 365', -> - firstDay = new Date('07/02/2015') - secondDay = new Date('07/01/2016') - difference = gl.utils.getDayDifference(firstDay, secondDay) - expect(difference).toBe(365) \ No newline at end of file diff --git a/spec/javascripts/datetime_utility_spec.js.es6 b/spec/javascripts/datetime_utility_spec.js.es6 new file mode 100644 index 00000000000..a2d1b0a7732 --- /dev/null +++ b/spec/javascripts/datetime_utility_spec.js.es6 @@ -0,0 +1,64 @@ +//= require lib/utils/datetime_utility +(() => { + describe('Date time utils', () => { + describe('get day name', () => { + it('should return Sunday', () => { + const day = gl.utils.getDayName(new Date('07/17/2016')); + expect(day).toBe('Sunday'); + }); + + it('should return Monday', () => { + const day = gl.utils.getDayName(new Date('07/18/2016')); + expect(day).toBe('Monday'); + }); + + it('should return Tuesday', () => { + const day = gl.utils.getDayName(new Date('07/19/2016')); + expect(day).toBe('Tuesday'); + }); + + it('should return Wednesday', () => { + const day = gl.utils.getDayName(new Date('07/20/2016')); + expect(day).toBe('Wednesday'); + }); + + it('should return Thursday', () => { + const day = gl.utils.getDayName(new Date('07/21/2016')); + expect(day).toBe('Thursday'); + }); + + it('should return Friday', () => { + const day = gl.utils.getDayName(new Date('07/22/2016')); + expect(day).toBe('Friday'); + }); + + it('should return Saturday', () => { + const day = gl.utils.getDayName(new Date('07/23/2016')); + expect(day).toBe('Saturday'); + }); + }); + + describe('get day difference', () => { + it('should return 7', () => { + const firstDay = new Date('07/01/2016'); + const secondDay = new Date('07/08/2016'); + const difference = gl.utils.getDayDifference(firstDay, secondDay); + expect(difference).toBe(7); + }); + + it('should return 31', () => { + const firstDay = new Date('07/01/2016'); + const secondDay = new Date('08/01/2016'); + const difference = gl.utils.getDayDifference(firstDay, secondDay); + expect(difference).toBe(31); + }); + + it('should return 365', () => { + const firstDay = new Date('07/02/2015'); + const secondDay = new Date('07/01/2016'); + const difference = gl.utils.getDayDifference(firstDay, secondDay); + expect(difference).toBe(365); + }); + }); + }); +})(); -- cgit v1.2.1 From e7f7df619d8ab4a3ccef830e397b931eb2143f7d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 1 Sep 2016 22:01:02 -0500 Subject: Rename behaviour to behavior in bug issue template for consistency --- .gitlab/issue_templates/Bug.md | 2 +- CHANGELOG | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index b676916fdf4..ac38f0c9521 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -10,7 +10,7 @@ (What you should see instead) -### Actual behaviour +### Actual behavior (What actually happens) diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..27eb0c07130 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.12.0 (unreleased) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Center build stage columns in pipeline overview (ClemMakesApps) + - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) -- cgit v1.2.1 From 9a1974aa9b9f08e90aec5cb6ea98b60f7eb86187 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 2 Sep 2016 08:19:14 +0200 Subject: Fix GitLab import button --- CHANGELOG | 1 + app/views/projects/new.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..9a2c9047936 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -74,6 +74,7 @@ v 8.12.0 (unreleased) v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch - Fix suggested colors options for new labels in the admin area. !6138 + - Fix GitLab import button v 8.11.4 - Fix resolving conflicts on forks. !6082 diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 0a1e2bb2cc6..fda0592dd41 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -55,7 +55,7 @@ = render 'bitbucket_import_modal' %div - if gitlab_import_enabled? - = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless bitbucket_import_configured?}" do + = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do = icon('gitlab', text: 'GitLab.com') - unless gitlab_import_configured? = render 'gitlab_import_modal' -- cgit v1.2.1 From 3ef2c38b63031984c2a284a983bce805c5a0181a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 2 Sep 2016 08:55:54 +0200 Subject: Change the inline code to codeblocks for the new features doc guideline [ci skip] --- doc/development/doc_styleguide.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 37bb59e112c..39b801f761d 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -155,15 +155,30 @@ Inside the document: - Every piece of documentation that comes with a new feature should declare the GitLab version that feature got introduced. Right below the heading add a - note: `> Introduced in GitLab 8.3.`. + note: + + ``` + > Introduced in GitLab 8.3. + ``` + - If possible every feature should have a link to the MR that introduced it. The above note would be then transformed to: - `> [Introduced][ce-1242] in GitLab 8.3.`, where - the [link identifier](#links) is named after the repository (CE) and the MR - number. -- If the feature is only in GitLab EE, don't forget to mention it, like: - `> Introduced in GitLab EE 8.3.`. Otherwise, leave - this mention out. + + ``` + > [Introduced][ce-1242] in GitLab 8.3. + ``` + + , where the [link identifier](#links) is named after the repository (CE) and + the MR number. + +- If the feature is only in GitLab Enterprise Edition, don't forget to mention + it, like: + + ``` + > Introduced in GitLab Enterprise Edition 8.3. + ``` + + Otherwise, leave this mention out. ## References -- cgit v1.2.1 From eb6a5982652e74485242dbd54f17bd051a88f56b Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 2 Sep 2016 09:08:13 +0000 Subject: Added `.term-bold` declaration. --- app/assets/stylesheets/pages/xterm.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index 8d855ce99b0..c9846103762 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -20,6 +20,9 @@ $l-cyan: #8abeb7; $l-white: $ci-text-color; + .term-bold { + font-weight: bold; + } .term-italic { font-style: italic; } -- cgit v1.2.1 From 619b350279c6e3a987cefce935fdaf7ab13632ff Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Tue, 23 Aug 2016 15:18:42 +0000 Subject: Update memory requirements --- doc/install/requirements.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 571f1a38358..b499d3422d1 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -63,24 +63,24 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ### Memory -You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab! +You need at least 4GB of addressable memory (RAM + swap) to install and use GitLab! The operating system and any other running applications will also be using memory -so keep in mind that you need at least 2GB available before running GitLab. With +so keep in mind that you need at least 4GB available before running GitLab. With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage. -- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice. -- 1GB RAM + 1GB swap supports up to 100 users but it will be very slow -- **2GB RAM** is the **recommended** memory size for all installations and supports up to 100 users -- 4GB RAM supports up to 1,000 users -- 8GB RAM supports up to 2,000 users -- 16GB RAM supports up to 4,000 users -- 32GB RAM supports up to 8,000 users -- 64GB RAM supports up to 16,000 users -- 128GB RAM supports up to 32,000 users +- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice. +- 2GB RAM + 2GB swap supports up to 100 users but it will be very slow +- **4GB RAM** is the **recommended** memory size for all installations and supports up to 100 users +- 8GB RAM supports up to 1,000 users +- 16GB RAM supports up to 2,000 users +- 32GB RAM supports up to 4,000 users +- 64GB RAM supports up to 8,000 users +- 128GB RAM supports up to 16,000 users +- 256GB RAM supports up to 32,000 users - More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/) -We recommend having at least 1GB of swap on your server, even if you currently have +We recommend having at least 2GB of swap on your server, even if you currently have enough available RAM. Having swap will help reduce the chance of errors occurring if your available memory changes. @@ -113,10 +113,10 @@ It's possible to increase the amount of unicorn workers and this will usually he For most instances we recommend using: CPU cores + 1 = unicorn workers. So for a machine with 2 cores, 3 unicorn workers is ideal. -For all machines that have 1GB and up we recommend a minimum of three unicorn workers. -If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping. +For all machines that have 2GB and up we recommend a minimum of three unicorn workers. +If you have a 1GB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping. With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping. +If you have a 1GB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping. To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings). -- cgit v1.2.1 From a3828abb63306967c8e2d1c3602b8b7d953b74a0 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 1 Sep 2016 09:08:25 +0200 Subject: Change minimum Unicorns required to two --- doc/install/requirements.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index b499d3422d1..04907249f5c 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -114,9 +114,7 @@ For most instances we recommend using: CPU cores + 1 = unicorn workers. So for a machine with 2 cores, 3 unicorn workers is ideal. For all machines that have 2GB and up we recommend a minimum of three unicorn workers. -If you have a 1GB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping. -With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you have a 1GB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping. +If you have a 1GB machine we recommend to configure only two Unicorn worker to prevent excessive swapping. To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings). -- cgit v1.2.1 From 04a5a88f61c61fa916d151da9505be7292b7c7d9 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 1 Sep 2016 09:24:47 +0200 Subject: Fix a typo --- doc/install/requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 04907249f5c..9799e0a3730 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -114,7 +114,7 @@ For most instances we recommend using: CPU cores + 1 = unicorn workers. So for a machine with 2 cores, 3 unicorn workers is ideal. For all machines that have 2GB and up we recommend a minimum of three unicorn workers. -If you have a 1GB machine we recommend to configure only two Unicorn worker to prevent excessive swapping. +If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping. To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings). -- cgit v1.2.1 From a93a610bac7d9ee7c0908592b6a5d91ef0d94333 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 30 Aug 2016 16:06:40 +0200 Subject: Use 'git update-ref' for safer web commits --- CHANGELOG | 1 + app/models/repository.rb | 41 +++++++++++++++++++++-------------------- lib/gitlab/popen.rb | 4 ++-- spec/lib/gitlab/popen_spec.rb | 9 +++++++++ spec/models/repository_spec.rb | 30 +++++++++++++++++++++++------- 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..f115edf26e0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.12.0 (unreleased) - Remove prefixes from transition CSS property (ClemMakesApps) - Add Sentry logging to API calls - Add BroadcastMessage API + - Use 'git update-ref' for safer web commits !6130 - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Remove unused mixins (ClemMakesApps) - Add search to all issue board lists diff --git a/app/models/repository.rb b/app/models/repository.rb index f891e8374d2..b0644259af8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -149,7 +149,7 @@ class Repository return false unless target GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do - rugged.branches.create(branch_name, target) + update_ref!(ref, target, oldrev) end after_create_branch @@ -181,7 +181,7 @@ class Repository ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do - rugged.branches.delete(branch_name) + update_ref!(ref, newrev, oldrev) end after_remove_branch @@ -215,6 +215,21 @@ class Repository rugged.references.exist?(ref) end + def update_ref!(name, newrev, oldrev) + # We use 'git update-ref' because libgit2/rugged currently does not + # offer 'compare and swap' ref updates. Without compare-and-swap we can + # (and have!) accidentally reset the ref to an earlier state, clobbering + # commits. See also https://github.com/libgit2/libgit2/issues/1534. + command = %w[git update-ref --stdin -z] + output, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin| + stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00") + end + + return if status.zero? + + raise CommitError.new("error updating ref #{name} #{oldrev}->#{newrev}\n#{output}") + end + # Makes sure a commit is kept around when Git garbage collection runs. # Git GC will delete commits from the repository that are no longer in any # branches or tags, but we want to keep some of these commits around, for @@ -1014,15 +1029,10 @@ class Repository def commit_with_hooks(current_user, branch) update_autocrlf_option - oldrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch target_branch = find_branch(branch) was_empty = empty? - if !was_empty && target_branch - oldrev = target_branch.target.id - end - # Make commit newrev = yield(ref) @@ -1030,24 +1040,15 @@ class Repository raise CommitError.new('Failed to create commit') end + oldrev = rugged.lookup(newrev).parent_ids.first || Gitlab::Git::BLANK_SHA + GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do + update_ref!(ref, newrev, oldrev) + if was_empty || !target_branch - # Create branch - rugged.references.create(ref, newrev) - # If repo was empty expire cache after_create if was_empty after_create_branch - else - # Update head - current_head = find_branch(branch).target.id - - # Make sure target branch was not changed during pre-receive hook - if current_head == oldrev - rugged.references.update(ref, newrev) - else - raise CommitError.new('Commit was rejected because branch received new push') - end end end diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index ca23ccef25b..a0fd41161a5 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -21,9 +21,9 @@ module Gitlab @cmd_output = "" @cmd_status = 0 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| - # We are not using stdin so we should close it, in case the command we - # are running waits for input. + yield(stdin) if block_given? stdin.close + @cmd_output << stdout.read @cmd_output << stderr.read @cmd_status = wait_thr.value.exitstatus diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index e8b236426e9..4ae216d55b0 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -40,4 +40,13 @@ describe 'Gitlab::Popen', lib: true, no_db: true do it { expect(@status).to be_zero } it { expect(@output).to include('spec') } end + + context 'use stdin' do + before do + @output, @status = @klass.new.popen(%w[cat]) { |stdin| stdin.write 'hello' } + end + + it { expect(@status).to be_zero } + it { expect(@output).to eq('hello') } + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 812c72c48cb..afc7dc5db81 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -443,31 +443,32 @@ describe Repository, models: true do describe '#commit_with_hooks' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature + let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev context 'when pre hooks were successful' do before do expect_any_instance_of(GitHooksService).to receive(:execute). - with(user, repository.path_to_repo, old_rev, sample_commit.id, 'refs/heads/feature'). + with(user, repository.path_to_repo, old_rev, new_rev, 'refs/heads/feature'). and_yield.and_return(true) end it 'runs without errors' do expect do - repository.commit_with_hooks(user, 'feature') { sample_commit.id } + repository.commit_with_hooks(user, 'feature') { new_rev } end.not_to raise_error end it 'ensures the autocrlf Git option is set to :input' do expect(repository).to receive(:update_autocrlf_option) - repository.commit_with_hooks(user, 'feature') { sample_commit.id } + repository.commit_with_hooks(user, 'feature') { new_rev } end context "when the branch wasn't empty" do it 'updates the head' do expect(repository.find_branch('feature').target.id).to eq(old_rev) - repository.commit_with_hooks(user, 'feature') { sample_commit.id } - expect(repository.find_branch('feature').target.id).to eq(sample_commit.id) + repository.commit_with_hooks(user, 'feature') { new_rev } + expect(repository.find_branch('feature').target.id).to eq(new_rev) end end end @@ -477,7 +478,7 @@ describe Repository, models: true do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do - repository.commit_with_hooks(user, 'feature') { sample_commit.id } + repository.commit_with_hooks(user, 'feature') { new_rev } end.to raise_error(GitHooksService::PreReceiveError) end end @@ -485,6 +486,7 @@ describe Repository, models: true do context 'when target branch is different from source branch' do before do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, '']) + allow(repository).to receive(:update_ref!) end it 'expires branch cache' do @@ -495,7 +497,7 @@ describe Repository, models: true do expect(repository).to receive(:expire_has_visible_content_cache) expect(repository).to receive(:expire_branch_count_cache) - repository.commit_with_hooks(user, 'new-feature') { sample_commit.id } + repository.commit_with_hooks(user, 'new-feature') { new_rev } end end @@ -1268,4 +1270,18 @@ describe Repository, models: true do File.delete(path) end end + + describe '#update_ref!' do + it 'can create a ref' do + repository.update_ref!('refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA) + + expect(repository.find_branch('foobar')).not_to be_nil + end + + it 'raises CommitError when the ref update fails' do + expect do + repository.update_ref!('refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA) + end.to raise_error(Repository::CommitError) + end + end end -- cgit v1.2.1 From 4da474ca928fff916a1e8ac3bb42d81df2364431 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 12:03:49 +0100 Subject: Hides merge request section in edit project when disabled --- app/assets/javascripts/project_new.js | 8 +-- .../projects/_merge_request_settings.html.haml | 25 +++++----- spec/features/projects/edit_spec.rb | 57 ++++++++++++++++++++++ 3 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 spec/features/projects/edit_spec.rb diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 798f15e40a0..06034574c8a 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -15,18 +15,18 @@ } ProjectNew.prototype.toggleSettings = function() { - this._showOrHide('#project_builds_enabled', '.builds-feature'); - return this._showOrHide('#project_merge_requests_enabled', '.merge-requests-feature'); + this._showOrHide('#project_project_feature_attributes_builds_access_level', '.builds-feature'); + this._showOrHide('#project_project_feature_attributes_merge_requests_access_level', '.merge-requests-feature'); }; ProjectNew.prototype.toggleSettingsOnclick = function() { - return $('#project_builds_enabled, #project_merge_requests_enabled').on('click', this.toggleSettings); + $('#project_project_feature_attributes_builds_access_level, #project_project_feature_attributes_merge_requests_access_level').on('change', this.toggleSettings); }; ProjectNew.prototype._showOrHide = function(checkElement, container) { var $container; $container = $(container); - if ($(checkElement).prop('checked')) { + if ($(checkElement).val() !== '0') { return $container.show(); } else { return $container.hide(); diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 19b4249374b..14eb47089b1 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -1,11 +1,14 @@ -%fieldset.builds-feature - %h5.prepend-top-0 - Merge Requests - .form-group - .checkbox - = f.label :only_allow_merge_if_build_succeeds do - = f.check_box :only_allow_merge_if_build_succeeds - %strong Only allow merge requests to be merged if the build succeeds - .help-block - Builds need to be configured to enable this feature. - = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') +.merge-requests-feature + %fieldset.builds-feature + %hr + %h5.prepend-top-0 + Merge Requests + .form-group + .checkbox + = f.label :only_allow_merge_if_build_succeeds do + = f.check_box :only_allow_merge_if_build_succeeds + %strong Only allow merge requests to be merged if the build succeeds + %br + %span.descr + Builds need to be configured to enable this feature. + = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb new file mode 100644 index 00000000000..a1643fd1f43 --- /dev/null +++ b/spec/features/projects/edit_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +feature 'Project edit', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + project.team << [user, :master] + login_as(user) + + visit edit_namespace_project_path(project.namespace, project) + end + + context 'feature visibility' do + context 'merge requests select' do + it 'hides merge requests section' do + select('Disabled', from: 'project_project_feature_attributes_merge_requests_access_level') + + expect(page).to have_selector('.merge-requests-feature', visible: false) + end + + it 'hides merge requests section after save' do + select('Disabled', from: 'project_project_feature_attributes_merge_requests_access_level') + + expect(page).to have_selector('.merge-requests-feature', visible: false) + + click_button 'Save changes' + + wait_for_ajax + + expect(page).to have_selector('.merge-requests-feature', visible: false) + end + end + + context 'builds select' do + it 'hides merge requests section' do + select('Disabled', from: 'project_project_feature_attributes_builds_access_level') + + expect(page).to have_selector('.builds-feature', visible: false) + end + + it 'hides merge requests section after save' do + select('Disabled', from: 'project_project_feature_attributes_builds_access_level') + + expect(page).to have_selector('.builds-feature', visible: false) + + click_button 'Save changes' + + wait_for_ajax + + expect(page).to have_selector('.builds-feature', visible: false) + end + end + end +end -- cgit v1.2.1 From 05c22ca3e0a4db1cd4b23376bb2b61cc0e877eff Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 22 Jun 2016 22:56:15 +0200 Subject: Add documentation on template dropdowns --- doc/gitlab-basics/add-file.md | 16 +++++++++++++--- doc/gitlab-basics/basicsimages/file_button.png | Bin 0 -> 14131 bytes doc/gitlab-basics/basicsimages/first_file.png | Bin 0 -> 25748 bytes doc/gitlab-basics/basicsimages/mit_license.png | Bin 0 -> 101405 bytes 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 doc/gitlab-basics/basicsimages/file_button.png create mode 100644 doc/gitlab-basics/basicsimages/first_file.png create mode 100644 doc/gitlab-basics/basicsimages/mit_license.png diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index 57136ac5c39..e1e55a43de4 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -26,6 +26,16 @@ Add a commit message based on what you just added and then click on "commit chan ![Commit changes](basicsimages/commit_changes.png) -### Note -Besides its regular files, every directory needs a README.md or README.html file which works like an index, telling -what the directory is about. It's the first document you'll find when you open a directory. +# On a new project + +When starting a new project, there are some common files which the new project might need too. Therefor a message will be displayed by GitLab to make this easy for you. + +![First file for your project](basicsimages/first_file.png) + +When clicking on either `LICENSE` or `.gitignore`, a dropdown will be displayed to provide you with a template which might be suitable for your project. + +![MIT license selected](basicsimages/mit_license.png) + +The license, changelog, contribution guide, or `.gitlab-ci.yml` file could also be added through a button on the project page. In the example below the license has already been created, which creates a link to the license itself. + +![New file button](basicsimages/file_button.png) diff --git a/doc/gitlab-basics/basicsimages/file_button.png b/doc/gitlab-basics/basicsimages/file_button.png new file mode 100644 index 00000000000..4efc51cc423 Binary files /dev/null and b/doc/gitlab-basics/basicsimages/file_button.png differ diff --git a/doc/gitlab-basics/basicsimages/first_file.png b/doc/gitlab-basics/basicsimages/first_file.png new file mode 100644 index 00000000000..67190c58823 Binary files /dev/null and b/doc/gitlab-basics/basicsimages/first_file.png differ diff --git a/doc/gitlab-basics/basicsimages/mit_license.png b/doc/gitlab-basics/basicsimages/mit_license.png new file mode 100644 index 00000000000..3695a11e34c Binary files /dev/null and b/doc/gitlab-basics/basicsimages/mit_license.png differ -- cgit v1.2.1 From 8ab659aa5d09b2da1cf68ac6c149d0225690d6a2 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 22 Jul 2016 08:30:33 +0200 Subject: Correct spelling of therefore [ci skip] --- doc/gitlab-basics/add-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index e1e55a43de4..27591a4a15a 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -28,7 +28,7 @@ Add a commit message based on what you just added and then click on "commit chan # On a new project -When starting a new project, there are some common files which the new project might need too. Therefor a message will be displayed by GitLab to make this easy for you. +When starting a new project, there are some common files which the new project might need too. Therefore a message will be displayed by GitLab to make this easy for you. ![First file for your project](basicsimages/first_file.png) -- cgit v1.2.1 From 30b56b160b854baf03bfacd80c0e87d62716dd0f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 2 Sep 2016 13:11:57 +0200 Subject: Move web_editor doc to new location --- .../img/web_editor_new_branch_dropdown.png | Bin 0 -> 20436 bytes .../repository/img/web_editor_new_branch_page.png | Bin 0 -> 11245 bytes .../img/web_editor_new_directory_dialog.png | Bin 0 -> 13339 bytes .../img/web_editor_new_directory_dropdown.png | Bin 0 -> 20007 bytes .../img/web_editor_new_file_dropdown.png | Bin 0 -> 20680 bytes .../repository/img/web_editor_new_file_editor.png | Bin 0 -> 66261 bytes .../repository/img/web_editor_new_push_widget.png | Bin 0 -> 7076 bytes .../repository/img/web_editor_new_tag_dropdown.png | Bin 0 -> 20080 bytes .../repository/img/web_editor_new_tag_page.png | Bin 0 -> 36610 bytes .../img/web_editor_start_new_merge_request.png | Bin 0 -> 8596 bytes .../img/web_editor_upload_file_dialog.png | Bin 0 -> 21502 bytes .../img/web_editor_upload_file_dropdown.png | Bin 0 -> 20651 bytes doc/user/project/repository/web_editor.md | 151 ++++++++++++++++++++ doc/workflow/README.md | 2 +- .../img/web_editor_new_branch_dropdown.png | Bin 20436 -> 0 bytes doc/workflow/img/web_editor_new_branch_page.png | Bin 11245 -> 0 bytes .../img/web_editor_new_directory_dialog.png | Bin 13339 -> 0 bytes .../img/web_editor_new_directory_dropdown.png | Bin 20007 -> 0 bytes doc/workflow/img/web_editor_new_file_dropdown.png | Bin 20680 -> 0 bytes doc/workflow/img/web_editor_new_file_editor.png | Bin 66261 -> 0 bytes doc/workflow/img/web_editor_new_push_widget.png | Bin 7076 -> 0 bytes doc/workflow/img/web_editor_new_tag_dropdown.png | Bin 20080 -> 0 bytes doc/workflow/img/web_editor_new_tag_page.png | Bin 36610 -> 0 bytes .../img/web_editor_start_new_merge_request.png | Bin 8596 -> 0 bytes doc/workflow/img/web_editor_upload_file_dialog.png | Bin 21502 -> 0 bytes .../img/web_editor_upload_file_dropdown.png | Bin 20651 -> 0 bytes doc/workflow/web_editor.md | 152 +-------------------- 27 files changed, 153 insertions(+), 152 deletions(-) create mode 100644 doc/user/project/repository/img/web_editor_new_branch_dropdown.png create mode 100644 doc/user/project/repository/img/web_editor_new_branch_page.png create mode 100644 doc/user/project/repository/img/web_editor_new_directory_dialog.png create mode 100644 doc/user/project/repository/img/web_editor_new_directory_dropdown.png create mode 100644 doc/user/project/repository/img/web_editor_new_file_dropdown.png create mode 100644 doc/user/project/repository/img/web_editor_new_file_editor.png create mode 100644 doc/user/project/repository/img/web_editor_new_push_widget.png create mode 100644 doc/user/project/repository/img/web_editor_new_tag_dropdown.png create mode 100644 doc/user/project/repository/img/web_editor_new_tag_page.png create mode 100644 doc/user/project/repository/img/web_editor_start_new_merge_request.png create mode 100644 doc/user/project/repository/img/web_editor_upload_file_dialog.png create mode 100644 doc/user/project/repository/img/web_editor_upload_file_dropdown.png create mode 100644 doc/user/project/repository/web_editor.md delete mode 100644 doc/workflow/img/web_editor_new_branch_dropdown.png delete mode 100644 doc/workflow/img/web_editor_new_branch_page.png delete mode 100644 doc/workflow/img/web_editor_new_directory_dialog.png delete mode 100644 doc/workflow/img/web_editor_new_directory_dropdown.png delete mode 100644 doc/workflow/img/web_editor_new_file_dropdown.png delete mode 100644 doc/workflow/img/web_editor_new_file_editor.png delete mode 100644 doc/workflow/img/web_editor_new_push_widget.png delete mode 100644 doc/workflow/img/web_editor_new_tag_dropdown.png delete mode 100644 doc/workflow/img/web_editor_new_tag_page.png delete mode 100644 doc/workflow/img/web_editor_start_new_merge_request.png delete mode 100644 doc/workflow/img/web_editor_upload_file_dialog.png delete mode 100644 doc/workflow/img/web_editor_upload_file_dropdown.png diff --git a/doc/user/project/repository/img/web_editor_new_branch_dropdown.png b/doc/user/project/repository/img/web_editor_new_branch_dropdown.png new file mode 100644 index 00000000000..a8e635d2faf Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_branch_dropdown.png differ diff --git a/doc/user/project/repository/img/web_editor_new_branch_page.png b/doc/user/project/repository/img/web_editor_new_branch_page.png new file mode 100644 index 00000000000..7f36b7faf63 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_branch_page.png differ diff --git a/doc/user/project/repository/img/web_editor_new_directory_dialog.png b/doc/user/project/repository/img/web_editor_new_directory_dialog.png new file mode 100644 index 00000000000..d16e3c67116 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_directory_dialog.png differ diff --git a/doc/user/project/repository/img/web_editor_new_directory_dropdown.png b/doc/user/project/repository/img/web_editor_new_directory_dropdown.png new file mode 100644 index 00000000000..c8d77b16ee8 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_directory_dropdown.png differ diff --git a/doc/user/project/repository/img/web_editor_new_file_dropdown.png b/doc/user/project/repository/img/web_editor_new_file_dropdown.png new file mode 100644 index 00000000000..3fcb91c9b93 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_file_dropdown.png differ diff --git a/doc/user/project/repository/img/web_editor_new_file_editor.png b/doc/user/project/repository/img/web_editor_new_file_editor.png new file mode 100644 index 00000000000..21c340b9288 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_file_editor.png differ diff --git a/doc/user/project/repository/img/web_editor_new_push_widget.png b/doc/user/project/repository/img/web_editor_new_push_widget.png new file mode 100644 index 00000000000..c7738a4c930 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_push_widget.png differ diff --git a/doc/user/project/repository/img/web_editor_new_tag_dropdown.png b/doc/user/project/repository/img/web_editor_new_tag_dropdown.png new file mode 100644 index 00000000000..ac7415009b3 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_tag_dropdown.png differ diff --git a/doc/user/project/repository/img/web_editor_new_tag_page.png b/doc/user/project/repository/img/web_editor_new_tag_page.png new file mode 100644 index 00000000000..231e1a13fc0 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_tag_page.png differ diff --git a/doc/user/project/repository/img/web_editor_start_new_merge_request.png b/doc/user/project/repository/img/web_editor_start_new_merge_request.png new file mode 100644 index 00000000000..2755501dfd1 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_start_new_merge_request.png differ diff --git a/doc/user/project/repository/img/web_editor_upload_file_dialog.png b/doc/user/project/repository/img/web_editor_upload_file_dialog.png new file mode 100644 index 00000000000..9d6d8250bbe Binary files /dev/null and b/doc/user/project/repository/img/web_editor_upload_file_dialog.png differ diff --git a/doc/user/project/repository/img/web_editor_upload_file_dropdown.png b/doc/user/project/repository/img/web_editor_upload_file_dropdown.png new file mode 100644 index 00000000000..6b5205b05ec Binary files /dev/null and b/doc/user/project/repository/img/web_editor_upload_file_dropdown.png differ diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md new file mode 100644 index 00000000000..ee8e7862572 --- /dev/null +++ b/doc/user/project/repository/web_editor.md @@ -0,0 +1,151 @@ +# GitLab Web Editor + +Sometimes it's easier to make quick changes directly from the GitLab interface +than to clone the project and use the Git command line tool. In this feature +highlight we look at how you can create a new file, directory, branch or +tag from the file browser. All of these actions are available from a single +dropdown menu. + +## Create a file + +From a project's files page, click the '+' button to the right of the branch selector. +Choose **New file** from the dropdown. + +![New file dropdown menu](img/web_editor_new_file_dropdown.png) + +--- + +Enter a file name in the **File name** box. Then, add file content in the editor +area. Add a descriptive commit message and choose a branch. The branch field +will default to the branch you were viewing in the file browser. If you enter +a new branch name, a checkbox will appear allowing you to start a new merge +request after you commit the changes. + +When you are satisfied with your new file, click **Commit Changes** at the bottom. + +![Create file editor](img/web_editor_new_file_editor.png) + +## Upload a file + +The ability to create a file is great when the content is text. However, this +doesn't work well for binary data such as images, PDFs or other file types. In +this case you need to upload a file. + +From a project's files page, click the '+' button to the right of the branch +selector. Choose **Upload file** from the dropdown. + +![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png) + +--- + +Once the upload dialog pops up there are two ways to upload your file. Either +drag and drop a file on the pop up or use the **click to upload** link. A file +preview will appear once you have selected a file to upload. + +Enter a commit message, choose a branch, and click **Upload file** when you are +ready. + +![Upload file dialog](img/web_editor_upload_file_dialog.png) + +## Create a directory + +To keep files in the repository organized it is often helpful to create a new +directory. + +From a project's files page, click the '+' button to the right of the branch selector. +Choose **New directory** from the dropdown. + +![New directory dropdown](img/web_editor_new_directory_dropdown.png) + +--- + +In the new directory dialog enter a directory name, a commit message and choose +the target branch. Click **Create directory** to finish. + +![New directory dialog](img/web_editor_new_directory_dialog.png) + +## Create a new branch + +There are multiple ways to create a branch from GitLab's web interface. + +### Create a new branch from an issue + +> [Introduced][ce-2808] in GitLab 8.6. + +In case your development workflow dictates to have an issue for every merge +request, you can quickly create a branch right on the issue page which will be +tied with the issue itself. You can see a **New Branch** button after the issue +description, unless there is already a branch with the same name or a referenced +merge request. + +![New Branch Button](img/new_branch_from_issue.png) + +Once you click it, a new branch will be created that diverges from the default +branch of your project, by default `master`. The branch name will be based on +the title of the issue and as suffix it will have its ID. Thus, the example +screenshot above will yield a branch named +`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`. + +After the branch is created, you can edit files in the repository to fix +the issue. When a merge request is created based on the newly created branch, +the description field will automatically display the [issue closing pattern] +`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the +merge request is merged. + +### Create a new branch from a project's dashboard + +If you want to make changes to several files before creating a new merge +request, you can create a new branch up front. From a project's files page, +choose **New branch** from the dropdown. + +![New branch dropdown](img/web_editor_new_branch_dropdown.png) + +--- + +Enter a new **Branch name**. Optionally, change the **Create from** field +to choose which branch, tag or commit SHA this new branch will originate from. +This field will autocomplete if you start typing an existing branch or tag. +Click **Create branch** and you will be returned to the file browser on this new +branch. + +![New branch page](img/web_editor_new_branch_page.png) + +--- + +You can now make changes to any files, as needed. When you're ready to merge +the changes back to master you can use the widget at the top of the screen. +This widget only appears for a period of time after you create the branch or +modify files. + +![New push widget](img/web_editor_new_push_widget.png) + +## Create a new tag + +Tags are useful for marking major milestones such as production releases, +release candidates, and more. You can create a tag from a branch or a commit +SHA. From a project's files page, choose **New tag** from the dropdown. + +![New tag dropdown](img/web_editor_new_tag_dropdown.png) + +--- + +Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you +would like to create this new tag. You can optionally add a message and +release notes. The release notes section supports markdown format and you can +also upload an attachment. Click **Create tag** and you will be taken to the tag +list page. + +![New tag page](img/web_editor_new_tag_page.png) + +## Tips + +When creating or uploading a new file, or creating a new directory, you can +trigger a new merge request rather than committing directly to master. Enter +a new branch name in the **Target branch** field. You will notice a checkbox +appear that is labeled **Start a new merge request with these changes**. After +you commit the changes you will be taken to a new merge request form. + +![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png) + +[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808 +[issue closing pattern]: ../customization/issue_closing.md diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 0cf56449de2..fe13bd757c5 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -18,7 +18,7 @@ - [Slash commands](../user/project/slash_commands.md) - [Sharing a project with a group](share_with_group.md) - [Share projects with other groups](share_projects_with_other_groups.md) -- [Web Editor](web_editor.md) +- [Web Editor](../user/project/repository/web_editor.md) - [Releases](releases.md) - [Milestones](milestones.md) - [Merge Requests](merge_requests.md) diff --git a/doc/workflow/img/web_editor_new_branch_dropdown.png b/doc/workflow/img/web_editor_new_branch_dropdown.png deleted file mode 100644 index a8e635d2faf..00000000000 Binary files a/doc/workflow/img/web_editor_new_branch_dropdown.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_branch_page.png b/doc/workflow/img/web_editor_new_branch_page.png deleted file mode 100644 index 7f36b7faf63..00000000000 Binary files a/doc/workflow/img/web_editor_new_branch_page.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_directory_dialog.png b/doc/workflow/img/web_editor_new_directory_dialog.png deleted file mode 100644 index d16e3c67116..00000000000 Binary files a/doc/workflow/img/web_editor_new_directory_dialog.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_directory_dropdown.png b/doc/workflow/img/web_editor_new_directory_dropdown.png deleted file mode 100644 index c8d77b16ee8..00000000000 Binary files a/doc/workflow/img/web_editor_new_directory_dropdown.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_file_dropdown.png b/doc/workflow/img/web_editor_new_file_dropdown.png deleted file mode 100644 index 3fcb91c9b93..00000000000 Binary files a/doc/workflow/img/web_editor_new_file_dropdown.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_file_editor.png b/doc/workflow/img/web_editor_new_file_editor.png deleted file mode 100644 index 21c340b9288..00000000000 Binary files a/doc/workflow/img/web_editor_new_file_editor.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_push_widget.png b/doc/workflow/img/web_editor_new_push_widget.png deleted file mode 100644 index c7738a4c930..00000000000 Binary files a/doc/workflow/img/web_editor_new_push_widget.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_tag_dropdown.png b/doc/workflow/img/web_editor_new_tag_dropdown.png deleted file mode 100644 index ac7415009b3..00000000000 Binary files a/doc/workflow/img/web_editor_new_tag_dropdown.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_new_tag_page.png b/doc/workflow/img/web_editor_new_tag_page.png deleted file mode 100644 index 231e1a13fc0..00000000000 Binary files a/doc/workflow/img/web_editor_new_tag_page.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_start_new_merge_request.png b/doc/workflow/img/web_editor_start_new_merge_request.png deleted file mode 100644 index 2755501dfd1..00000000000 Binary files a/doc/workflow/img/web_editor_start_new_merge_request.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_upload_file_dialog.png b/doc/workflow/img/web_editor_upload_file_dialog.png deleted file mode 100644 index 9d6d8250bbe..00000000000 Binary files a/doc/workflow/img/web_editor_upload_file_dialog.png and /dev/null differ diff --git a/doc/workflow/img/web_editor_upload_file_dropdown.png b/doc/workflow/img/web_editor_upload_file_dropdown.png deleted file mode 100644 index 6b5205b05ec..00000000000 Binary files a/doc/workflow/img/web_editor_upload_file_dropdown.png and /dev/null differ diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md index ee8e7862572..595c7da155b 100644 --- a/doc/workflow/web_editor.md +++ b/doc/workflow/web_editor.md @@ -1,151 +1 @@ -# GitLab Web Editor - -Sometimes it's easier to make quick changes directly from the GitLab interface -than to clone the project and use the Git command line tool. In this feature -highlight we look at how you can create a new file, directory, branch or -tag from the file browser. All of these actions are available from a single -dropdown menu. - -## Create a file - -From a project's files page, click the '+' button to the right of the branch selector. -Choose **New file** from the dropdown. - -![New file dropdown menu](img/web_editor_new_file_dropdown.png) - ---- - -Enter a file name in the **File name** box. Then, add file content in the editor -area. Add a descriptive commit message and choose a branch. The branch field -will default to the branch you were viewing in the file browser. If you enter -a new branch name, a checkbox will appear allowing you to start a new merge -request after you commit the changes. - -When you are satisfied with your new file, click **Commit Changes** at the bottom. - -![Create file editor](img/web_editor_new_file_editor.png) - -## Upload a file - -The ability to create a file is great when the content is text. However, this -doesn't work well for binary data such as images, PDFs or other file types. In -this case you need to upload a file. - -From a project's files page, click the '+' button to the right of the branch -selector. Choose **Upload file** from the dropdown. - -![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png) - ---- - -Once the upload dialog pops up there are two ways to upload your file. Either -drag and drop a file on the pop up or use the **click to upload** link. A file -preview will appear once you have selected a file to upload. - -Enter a commit message, choose a branch, and click **Upload file** when you are -ready. - -![Upload file dialog](img/web_editor_upload_file_dialog.png) - -## Create a directory - -To keep files in the repository organized it is often helpful to create a new -directory. - -From a project's files page, click the '+' button to the right of the branch selector. -Choose **New directory** from the dropdown. - -![New directory dropdown](img/web_editor_new_directory_dropdown.png) - ---- - -In the new directory dialog enter a directory name, a commit message and choose -the target branch. Click **Create directory** to finish. - -![New directory dialog](img/web_editor_new_directory_dialog.png) - -## Create a new branch - -There are multiple ways to create a branch from GitLab's web interface. - -### Create a new branch from an issue - -> [Introduced][ce-2808] in GitLab 8.6. - -In case your development workflow dictates to have an issue for every merge -request, you can quickly create a branch right on the issue page which will be -tied with the issue itself. You can see a **New Branch** button after the issue -description, unless there is already a branch with the same name or a referenced -merge request. - -![New Branch Button](img/new_branch_from_issue.png) - -Once you click it, a new branch will be created that diverges from the default -branch of your project, by default `master`. The branch name will be based on -the title of the issue and as suffix it will have its ID. Thus, the example -screenshot above will yield a branch named -`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`. - -After the branch is created, you can edit files in the repository to fix -the issue. When a merge request is created based on the newly created branch, -the description field will automatically display the [issue closing pattern] -`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the -merge request is merged. - -### Create a new branch from a project's dashboard - -If you want to make changes to several files before creating a new merge -request, you can create a new branch up front. From a project's files page, -choose **New branch** from the dropdown. - -![New branch dropdown](img/web_editor_new_branch_dropdown.png) - ---- - -Enter a new **Branch name**. Optionally, change the **Create from** field -to choose which branch, tag or commit SHA this new branch will originate from. -This field will autocomplete if you start typing an existing branch or tag. -Click **Create branch** and you will be returned to the file browser on this new -branch. - -![New branch page](img/web_editor_new_branch_page.png) - ---- - -You can now make changes to any files, as needed. When you're ready to merge -the changes back to master you can use the widget at the top of the screen. -This widget only appears for a period of time after you create the branch or -modify files. - -![New push widget](img/web_editor_new_push_widget.png) - -## Create a new tag - -Tags are useful for marking major milestones such as production releases, -release candidates, and more. You can create a tag from a branch or a commit -SHA. From a project's files page, choose **New tag** from the dropdown. - -![New tag dropdown](img/web_editor_new_tag_dropdown.png) - ---- - -Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you -would like to create this new tag. You can optionally add a message and -release notes. The release notes section supports markdown format and you can -also upload an attachment. Click **Create tag** and you will be taken to the tag -list page. - -![New tag page](img/web_editor_new_tag_page.png) - -## Tips - -When creating or uploading a new file, or creating a new directory, you can -trigger a new merge request rather than committing directly to master. Enter -a new branch name in the **Target branch** field. You will notice a checkbox -appear that is labeled **Start a new merge request with these changes**. After -you commit the changes you will be taken to a new merge request form. - -![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png) - -[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808 -[issue closing pattern]: ../customization/issue_closing.md +This document was moved to [user/project/repository/web_editor](../user/project/repository/web_editor.md). -- cgit v1.2.1 From 8e6e3423c3ad7ca6e70576713aa890dc8444f1bb Mon Sep 17 00:00:00 2001 From: Gustav Trenwith Date: Fri, 2 Sep 2016 11:20:43 +0000 Subject: Update README.md --- doc/ci/ssh_keys/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 7c0fb225dac..4041cc41853 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -30,7 +30,8 @@ This is the universal solution which works with any type of executor ## SSH keys when using the Docker executor You will first need to create an SSH key pair. For more information, follow the -instructions to [generate an SSH key](../../ssh/README.md). +instructions to [generate an SSH key](../../ssh/README.md). Do not add a comment +to the ssh key, or the `before_script` will prompt for a passphrase. Then, create a new **Secret Variable** in your project settings on GitLab following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` -- cgit v1.2.1 From 7c89308ae4891085d59c6f85da77c5c13e0bd233 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 2 Sep 2016 13:28:17 +0200 Subject: Move docs for template dropdowns to the new location --- doc/gitlab-basics/add-file.md | 14 ------------ doc/gitlab-basics/basicsimages/file_button.png | Bin 14131 -> 0 bytes doc/gitlab-basics/basicsimages/first_file.png | Bin 25748 -> 0 bytes doc/gitlab-basics/basicsimages/mit_license.png | Bin 101405 -> 0 bytes .../img/web_editor_template_dropdown_buttons.png | Bin 0 -> 14131 bytes .../web_editor_template_dropdown_first_file.png | Bin 0 -> 25748 bytes .../web_editor_template_dropdown_mit_license.png | Bin 0 -> 85413 bytes doc/user/project/repository/web_editor.md | 24 +++++++++++++++++++++ 8 files changed, 24 insertions(+), 14 deletions(-) delete mode 100644 doc/gitlab-basics/basicsimages/file_button.png delete mode 100644 doc/gitlab-basics/basicsimages/first_file.png delete mode 100644 doc/gitlab-basics/basicsimages/mit_license.png create mode 100644 doc/user/project/repository/img/web_editor_template_dropdown_buttons.png create mode 100644 doc/user/project/repository/img/web_editor_template_dropdown_first_file.png create mode 100644 doc/user/project/repository/img/web_editor_template_dropdown_mit_license.png diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index 27591a4a15a..ff10a98e8f5 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -25,17 +25,3 @@ Add all the information that you'd like to include in your file: Add a commit message based on what you just added and then click on "commit changes": ![Commit changes](basicsimages/commit_changes.png) - -# On a new project - -When starting a new project, there are some common files which the new project might need too. Therefore a message will be displayed by GitLab to make this easy for you. - -![First file for your project](basicsimages/first_file.png) - -When clicking on either `LICENSE` or `.gitignore`, a dropdown will be displayed to provide you with a template which might be suitable for your project. - -![MIT license selected](basicsimages/mit_license.png) - -The license, changelog, contribution guide, or `.gitlab-ci.yml` file could also be added through a button on the project page. In the example below the license has already been created, which creates a link to the license itself. - -![New file button](basicsimages/file_button.png) diff --git a/doc/gitlab-basics/basicsimages/file_button.png b/doc/gitlab-basics/basicsimages/file_button.png deleted file mode 100644 index 4efc51cc423..00000000000 Binary files a/doc/gitlab-basics/basicsimages/file_button.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/first_file.png b/doc/gitlab-basics/basicsimages/first_file.png deleted file mode 100644 index 67190c58823..00000000000 Binary files a/doc/gitlab-basics/basicsimages/first_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/mit_license.png b/doc/gitlab-basics/basicsimages/mit_license.png deleted file mode 100644 index 3695a11e34c..00000000000 Binary files a/doc/gitlab-basics/basicsimages/mit_license.png and /dev/null differ diff --git a/doc/user/project/repository/img/web_editor_template_dropdown_buttons.png b/doc/user/project/repository/img/web_editor_template_dropdown_buttons.png new file mode 100644 index 00000000000..4efc51cc423 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_template_dropdown_buttons.png differ diff --git a/doc/user/project/repository/img/web_editor_template_dropdown_first_file.png b/doc/user/project/repository/img/web_editor_template_dropdown_first_file.png new file mode 100644 index 00000000000..67190c58823 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_template_dropdown_first_file.png differ diff --git a/doc/user/project/repository/img/web_editor_template_dropdown_mit_license.png b/doc/user/project/repository/img/web_editor_template_dropdown_mit_license.png new file mode 100644 index 00000000000..47719113805 Binary files /dev/null and b/doc/user/project/repository/img/web_editor_template_dropdown_mit_license.png differ diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md index ee8e7862572..7c041d019bb 100644 --- a/doc/user/project/repository/web_editor.md +++ b/doc/user/project/repository/web_editor.md @@ -25,6 +25,29 @@ When you are satisfied with your new file, click **Commit Changes** at the botto ![Create file editor](img/web_editor_new_file_editor.png) +### Template dropdowns + +When starting a new project, there are some common files which the new project +might need too. Therefore a message will be displayed by GitLab to make this +easy for you. + +![First file for your project](img/web_editor_template_dropdown_first_file.png) + +When clicking on either `LICENSE` or `.gitignore`, a dropdown will be displayed +to provide you with a template which might be suitable for your project. + +![MIT license selected](img/web_editor_template_dropdown_mit_license.png) + +The license, changelog, contribution guide, or `.gitlab-ci.yml` file could also +be added through a button on the project page. In the example below the license +has already been created, which creates a link to the license itself. + +![New file button](img/web_editor_template_dropdown_buttons.png) + +>**Note:** +The **Set up CI** button will not appear on an empty repository. You have to at +least add a file in order for the button to show up. + ## Upload a file The ability to create a file is great when the content is text. However, this @@ -147,5 +170,6 @@ you commit the changes you will be taken to a new merge request form. ![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png) +![New file button](basicsimages/file_button.png) [ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808 [issue closing pattern]: ../customization/issue_closing.md -- cgit v1.2.1 From 88fe7c0f24d751e23ac02222305dbee6f128f6a1 Mon Sep 17 00:00:00 2001 From: Winnie Date: Fri, 2 Sep 2016 13:05:01 +0000 Subject: Move CHANGELOG entry for !5858 from 8.11 to 8.12 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0eb076fd62f..c506c0b9272 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,7 @@ v 8.12.0 (unreleased) - Expire commit info views after one day, instead of two weeks, to allow for user email updates - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) + - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Fix repo title alignment (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) @@ -126,7 +127,6 @@ v 8.11.0 - Add Issues Board !5548 - Allow resolving merge conflicts in the UI !5479 - Improve diff performance by eliminating redundant checks for text blobs - - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi) - Convert switch icon into icon font (ClemMakesApps) - API: Endpoints for enabling and disabling deploy keys -- cgit v1.2.1 From f8513d768242dffbd0fd0230e0bbeca972bcf0ec Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:51:36 +0100 Subject: Refactored code to rely less on IDs that could change --- app/assets/javascripts/project_new.js | 18 +++++++++++++----- app/helpers/projects_helper.rb | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 06034574c8a..a787b11f2a9 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -4,6 +4,8 @@ this.ProjectNew = (function() { function ProjectNew() { this.toggleSettings = bind(this.toggleSettings, this); + this.$selects = $('.features select'); + $('.project-edit-container').on('ajax:before', (function(_this) { return function() { $('.project-edit-container').hide(); @@ -15,17 +17,23 @@ } ProjectNew.prototype.toggleSettings = function() { - this._showOrHide('#project_project_feature_attributes_builds_access_level', '.builds-feature'); - this._showOrHide('#project_project_feature_attributes_merge_requests_access_level', '.merge-requests-feature'); + var self = this; + + this.$selects.each(function () { + var $select = $(this), + className = $select.data('field').replace(/_/g, '-') + .replace('access-level', 'feature'); + self._showOrHide($select, '.' + className); + }); }; ProjectNew.prototype.toggleSettingsOnclick = function() { - $('#project_project_feature_attributes_builds_access_level, #project_project_feature_attributes_merge_requests_access_level').on('change', this.toggleSettings); + this.$selects.on('change', this.toggleSettings); }; ProjectNew.prototype._showOrHide = function(checkElement, container) { - var $container; - $container = $(container); + var $container = $(container); + if ($(checkElement).val() !== '0') { return $container.show(); } else { diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d6efa603223..4c685b97c03 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -431,6 +431,6 @@ module ProjectsHelper options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED) - content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control").html_safe + content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe end end -- cgit v1.2.1 From e119f994d779878e49d8e1d785725e80b2c48b27 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Sep 2016 12:29:05 +0100 Subject: Fix pagination on user snippets page --- CHANGELOG | 1 + app/assets/javascripts/snippets_list.js | 7 +++++++ app/views/snippets/_snippets.html.haml | 16 ++++++++++------ spec/features/users/snippets_spec.rb | 29 +++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/snippets_list.js create mode 100644 spec/features/users/snippets_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..657a4bf723c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.12.0 (unreleased) - Add font color contrast to external label in admin area (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 + - Fix pagination on user snippets page - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js new file mode 100644 index 00000000000..dce3d71aeee --- /dev/null +++ b/app/assets/javascripts/snippets_list.js @@ -0,0 +1,7 @@ +(function() { + this.gl.SnippetsList = function() { + $('.snippets-list-holder .pagination').on('ajax:success', function(e, data) { + $('.snippets-list-holder').replaceWith(data.html); + }); + }; +}).call(this); diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 80a3e731e1d..7be4a471579 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,7 +1,11 @@ -%ul.content-list - = render partial: 'shared/snippets/snippet', collection: @snippets - - if @snippets.empty? - %li - .nothing-here-block Nothing here. +.snippets-list-holder + %ul.content-list + = render partial: 'shared/snippets/snippet', collection: @snippets + - if @snippets.empty? + %li + .nothing-here-block Nothing here. -= paginate @snippets, theme: 'gitlab' + = paginate @snippets, theme: 'gitlab', remote: true + +:javascript + gl.SnippetsList(); diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb new file mode 100644 index 00000000000..356a8d668b0 --- /dev/null +++ b/spec/features/users/snippets_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe 'Snippets tab on a user profile', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + + context 'when the user has snippets' do + before do + create_list(:snippet, 25, :public, author: user) + + visit user_path(user) + page.within('.user-profile-nav') { click_link 'Snippets' } + wait_for_ajax + end + + it 'is limited to 20 items per page' do + expect(page.all('.snippets-list-holder .snippet-row').count).to eq(20) + end + + context 'clicking on the link to the second page' do + before { click_link('2') } + + it 'shows the remaining snippets' do + expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5) + end + end + end +end -- cgit v1.2.1 From 4f192c997f7ef52da3530786b8b37221cd548f77 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Sep 2016 16:29:07 +0100 Subject: Fix expiration date picker after update --- CHANGELOG | 1 + app/views/groups/group_members/update.js.haml | 2 +- app/views/projects/project_members/update.js.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..adfcf4f035b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -73,6 +73,7 @@ v 8.12.0 (unreleased) v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch + - Fix member expiration date picker after update - Fix suggested colors options for new labels in the admin area. !6138 v 8.11.4 diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index 742f9d7a433..3be7ed8432c 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,3 +1,3 @@ :plain $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); - new MemberExpirationDate(); + new gl.MemberExpirationDate(); diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 833954bc039..37e55dc72a3 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,3 @@ :plain $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}'); - new MemberExpirationDate(); + new gl.MemberExpirationDate(); -- cgit v1.2.1 From ffef94f17e16a9ca9f0c1d1b970f2354d5144357 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 2 Sep 2016 17:54:09 +0200 Subject: Make error message appropriate for end users --- app/models/repository.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index b0644259af8..414b82516bc 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -221,13 +221,13 @@ class Repository # (and have!) accidentally reset the ref to an earlier state, clobbering # commits. See also https://github.com/libgit2/libgit2/issues/1534. command = %w[git update-ref --stdin -z] - output, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin| + _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin| stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00") end return if status.zero? - raise CommitError.new("error updating ref #{name} #{oldrev}->#{newrev}\n#{output}") + raise CommitError.new("Could not update branch #{name.sub('refs/heads/', '')}. Please refresh and try again.") end # Makes sure a commit is kept around when Git garbage collection runs. -- cgit v1.2.1 From 2fd647259874399a14f1f1fac4bddb93fef714d0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 30 Jul 2016 14:23:46 -0500 Subject: Fix markdown anchor icon interaction --- CHANGELOG | 1 + app/assets/images/icon-link.png | Bin 729 -> 0 bytes app/assets/images/icon_anchor.svg | 1 + app/assets/stylesheets/framework/typography.scss | 27 +++++++++-------------- 4 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 app/assets/images/icon-link.png create mode 100644 app/assets/images/icon_anchor.svg diff --git a/CHANGELOG b/CHANGELOG index 08562b1a5a0..399a461e6d3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ v 8.12.0 (unreleased) - Fix branches page dropdown sort alignment (ClemMakesApps) - Add white background for no readme container (ClemMakesApps) - API: Expose issue confidentiality flag. (Robert Schilling) + - Fix markdown anchor icon interaction (ClemMakesApps) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png deleted file mode 100644 index 5b55e12571c..00000000000 Binary files a/app/assets/images/icon-link.png and /dev/null differ diff --git a/app/assets/images/icon_anchor.svg b/app/assets/images/icon_anchor.svg new file mode 100644 index 00000000000..7e242586bad --- /dev/null +++ b/app/assets/images/icon_anchor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 06874a993fa..3f8433a0e7f 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -159,25 +159,18 @@ position: relative; a.anchor { - // Setting `display: none` would prevent the anchor being scrolled to, so - // instead we set the height to 0 and it gets updated on hover. - height: 0; + left: -16px; + position: absolute; + text-decoration: none; + + &:after { + content: url('icon_anchor.svg'); + visibility: hidden; + } } - &:hover > a.anchor { - $size: 14px; - position: absolute; - right: 100%; - top: 50%; - margin-top: -11px; - margin-right: 0; - padding-right: 15px; - display: inline-block; - width: $size; - height: $size; - background-image: image-url("icon-link.png"); - background-size: contain; - background-repeat: no-repeat; + &:hover > a.anchor:after { + visibility: visible; } } } -- cgit v1.2.1 From 610fafc706de465fbefe172caa443ebc12d43ce2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 1 Sep 2016 15:03:50 -0500 Subject: Remove suggested colors hover underline --- CHANGELOG | 1 + app/assets/stylesheets/pages/labels.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ab4aff0f89a..00779c9f9b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.12.0 (unreleased) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Center build stage columns in pipeline overview (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) + - Remove suggested colors hover underline (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 606459f82cd..38c7cd98e41 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -7,6 +7,7 @@ display: inline-block; margin-right: 10px; margin-bottom: 10px; + text-decoration: none; } &.suggest-colors-dropdown { -- cgit v1.2.1 From 9dff647a7bc181706d1e9d46d1ebfd5fb964dcf7 Mon Sep 17 00:00:00 2001 From: Winnie Date: Fri, 2 Sep 2016 21:32:01 +0000 Subject: Move CHANGELOG entries of !5361, !5451 and !5887 from 8.11 to 8.12 --- CHANGELOG | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8f139d0ad12..9b2316c4d3a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,9 @@ v 8.12.0 (unreleased) - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Use JavaScript tooltips for mentions !5301 (winniehell) + - Add hover state to todos !5361 (winniehell) + - Fix icon alignment of star and fork buttons !5451 (winniehell) + - Fix alignment of icon buttons !5887 (winniehell) - Fix markdown help references (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps) - Fix accessibility and visibility of project list dropdown button !6140 @@ -178,9 +181,6 @@ v 8.11.0 - Update `timeago` plugin to use multiple string/locale settings - Remove unused images (ClemMakesApps) - Get issue and merge request description templates from repositories - - Add hover state to todos !5361 (winniehell) - - Fix icon alignment of star and fork buttons !5451 (winniehell) - - Fix alignment of icon buttons !5887 (winniehell) - Enforce 2FA restrictions on API authentication endpoints !5820 - Limit git rev-list output count to one in forced push check - Show deployment status on merge requests with external URLs -- cgit v1.2.1 From 61fbe7efe9972bf43287d7ca76d7ec63fd5798a5 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Sat, 3 Sep 2016 09:57:52 +0200 Subject: Add link on API docs index page [ci skip] --- doc/api/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/api/README.md b/doc/api/README.md index 3e79cce0120..96d94e08487 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -11,9 +11,10 @@ following locations: - [Award Emoji](award_emoji.md) - [Branches](branches.md) - [Builds](builds.md) -- [Build triggers](build_triggers.md) +- [Build Triggers](build_triggers.md) - [Build Variables](build_variables.md) - [Commits](commits.md) +- [Deployments](deployments.md) - [Deploy Keys](deploy_keys.md) - [Groups](groups.md) - [Group Access Requests](access_requests.md) -- cgit v1.2.1 From 49b9d8aef95dc1b0fe7ca8db9f5fab9ac35edfa5 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 3 Sep 2016 20:51:18 -0700 Subject: Fix randomly failing specs in expand_collapse_diff_spec: ``` Failure/Error: namespace.human_name + ' / ' + name ActionView::Template::Error: undefined method `+' for nil:NilClass # ./app/models/project.rb:807:in `name_with_namespace' # ./app/views/layouts/project.html.haml:1:in `_app_views_layouts_project_html_haml___2918737809244135908_70160161538920 ' # ./app/controllers/projects/commit_controller.rb:23:in `show' # ./lib/gitlab/request_profiler/middleware.rb:15:in `call' # ./lib/gitlab/middleware/go.rb:16:in `call' # ./lib/gitlab/middleware/static.rb:9:in `call' # ------------------ # --- Caused by: --- # NoMethodError: # undefined method `+' for nil:NilClass # ./app/models/project.rb:807:in `name_with_namespace' ``` Capybara's `click_link` method doesn't actually wait for the page to reload. When the `expand_all_diffs` parameter is used, we need to search for unique elements that appear to ensure that the page has actually reloaded. Closes #21841 --- spec/features/expand_collapse_diffs_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 688f68d3cff..8863554ee91 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -211,6 +211,13 @@ feature 'Expand and collapse diffs', js: true, feature: true do context 'expanding all diffs' do before do click_link('Expand all') + + # Wait for elements to appear to ensure full page reload + expect(page).to have_content('This diff was suppressed by a .gitattributes entry') + expect(page).to have_content('This diff could not be displayed because it is too large.') + expect(page).to have_content('too_large_image.jpg') + find('.note-textarea') + wait_for_ajax execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });') end -- cgit v1.2.1 From de4c0c2e710e4511ad5781bb2d2ab2e0f85dc2f5 Mon Sep 17 00:00:00 2001 From: Gustav Trenwith Date: Mon, 5 Sep 2016 06:32:26 +0000 Subject: Update README.md --- doc/ci/ssh_keys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 4041cc41853..b858029d25e 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -31,7 +31,7 @@ This is the universal solution which works with any type of executor You will first need to create an SSH key pair. For more information, follow the instructions to [generate an SSH key](../../ssh/README.md). Do not add a comment -to the ssh key, or the `before_script` will prompt for a passphrase. +to the SSH key, or the `before_script` will prompt for a passphrase. Then, create a new **Secret Variable** in your project settings on GitLab following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` -- cgit v1.2.1 From 7973a22ff4f3ca4d34f69864e2438f1e44c1cfe2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 12:13:11 +0200 Subject: Move `workflow/merge_requests.md` to `user/project/merge_requests.md` --- doc/user/project/merge_requests.md | 90 ++++++++++++++++++++ .../project/merge_requests/img/commit_compare.png | Bin 0 -> 65010 bytes .../merge_requests/img/merge_request_diff.png | Bin 0 -> 103239 bytes .../img/merge_request_diff_without_whitespace.png | Bin 0 -> 71896 bytes .../img/only_allow_merge_if_build_succeeds.png | Bin 0 -> 17552 bytes doc/user/project/merge_requests/img/versions.png | Bin 0 -> 100566 bytes doc/workflow/README.md | 2 +- doc/workflow/merge_requests.md | 91 +-------------------- doc/workflow/merge_requests/commit_compare.png | Bin 65010 -> 0 bytes doc/workflow/merge_requests/merge_request_diff.png | Bin 103239 -> 0 bytes .../merge_request_diff_without_whitespace.png | Bin 71896 -> 0 bytes .../only_allow_merge_if_build_succeeds.png | Bin 17552 -> 0 bytes doc/workflow/merge_requests/versions.png | Bin 100566 -> 0 bytes 13 files changed, 92 insertions(+), 91 deletions(-) create mode 100644 doc/user/project/merge_requests.md create mode 100644 doc/user/project/merge_requests/img/commit_compare.png create mode 100644 doc/user/project/merge_requests/img/merge_request_diff.png create mode 100644 doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png create mode 100644 doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png create mode 100644 doc/user/project/merge_requests/img/versions.png delete mode 100644 doc/workflow/merge_requests/commit_compare.png delete mode 100644 doc/workflow/merge_requests/merge_request_diff.png delete mode 100644 doc/workflow/merge_requests/merge_request_diff_without_whitespace.png delete mode 100644 doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png delete mode 100644 doc/workflow/merge_requests/versions.png diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md new file mode 100644 index 00000000000..ecccc96582c --- /dev/null +++ b/doc/user/project/merge_requests.md @@ -0,0 +1,90 @@ +# Merge Requests + +Merge requests allow you to exchange changes you made to source code + +## Only allow merge requests to be merged if the build succeeds + +You can prevent merge requests from being merged if their build did not succeed +in the project settings page. + +![only_allow_merge_if_build_succeeds](merge_requests/img/only_allow_merge_if_build_succeeds.png) + +Navigate to project settings page and select the `Only allow merge requests to be merged if the build succeeds` check box. + +Please note that you need to have builds configured to enable this feature. + +## Checkout merge requests locally + +### By adding a git alias + +Add the following alias to your `~/.gitconfig`: + +``` +[alias] + mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' - +``` + +Now you can check out a particular merge request from any repository and any remote, e.g. to check out a merge request number 5 as shown in GitLab from the `upstream` remote, do: + +``` +$ git mr upstream 5 +``` + +This will fetch the merge request into a local `mr-upstream-5` branch and check it out. + +### By modifying `.git/config` for a given repository + +Locate the section for your GitLab remote in the `.git/config` file. It looks like this: + +``` +[remote "origin"] + url = https://gitlab.com/gitlab-org/gitlab-ce.git + fetch = +refs/heads/*:refs/remotes/origin/* +``` + +Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section. + +It should look like this: + +``` +[remote "origin"] + url = https://gitlab.com/gitlab-org/gitlab-ce.git + fetch = +refs/heads/*:refs/remotes/origin/* + fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/* +``` + +Now you can fetch all the merge requests: + +``` +$ git fetch origin +From https://gitlab.com/gitlab-org/gitlab-ce.git + * [new ref] refs/merge-requests/1/head -> origin/merge-requests/1 + * [new ref] refs/merge-requests/2/head -> origin/merge-requests/2 +... +``` + +To check out a particular merge request: + +``` +$ git checkout origin/merge-requests/1 +``` + +## Ignore whitespace changes in Merge Request diff view + +![MR diff](merge_requests/img/merge_request_diff.png) + +If you click the "Hide whitespace changes" button, you can see the diff without whitespace changes. + +![MR diff without whitespace](merge_requests/img/merge_request_diff_without_whitespace.png) + +It is also working on commits compare view. + +![Commit Compare](merge_requests/img/commit_compare.png) + +## Merge Requests versions + +Every time you push to merge request branch, a new version of merge request diff +is created. When you visit the merge request page you see latest version of changes. +However you can select an older one from version dropdown + +![Merge Request Versions](merge_requests/img/versions.png) diff --git a/doc/user/project/merge_requests/img/commit_compare.png b/doc/user/project/merge_requests/img/commit_compare.png new file mode 100644 index 00000000000..0e4a2b23c04 Binary files /dev/null and b/doc/user/project/merge_requests/img/commit_compare.png differ diff --git a/doc/user/project/merge_requests/img/merge_request_diff.png b/doc/user/project/merge_requests/img/merge_request_diff.png new file mode 100644 index 00000000000..3ebbfb75ea3 Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_request_diff.png differ diff --git a/doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png b/doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png new file mode 100644 index 00000000000..a0db535019c Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png differ diff --git a/doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png b/doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png new file mode 100644 index 00000000000..18bebf5fe92 Binary files /dev/null and b/doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png differ diff --git a/doc/user/project/merge_requests/img/versions.png b/doc/user/project/merge_requests/img/versions.png new file mode 100644 index 00000000000..c0a6dfe6806 Binary files /dev/null and b/doc/user/project/merge_requests/img/versions.png differ diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 0cf56449de2..d626b1311a2 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -21,7 +21,7 @@ - [Web Editor](web_editor.md) - [Releases](releases.md) - [Milestones](milestones.md) -- [Merge Requests](merge_requests.md) +- [Merge Requests](../user/project/merge_requests.md) - [Revert changes](revert_changes.md) - [Cherry-pick changes](cherry_pick_changes.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index 40a5e4476be..a68bb8b27ca 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -1,90 +1 @@ -# Merge Requests - -Merge requests allow you to exchange changes you made to source code - -## Only allow merge requests to be merged if the build succeeds - -You can prevent merge requests from being merged if their build did not succeed -in the project settings page. - -![only_allow_merge_if_build_succeeds](merge_requests/only_allow_merge_if_build_succeeds.png) - -Navigate to project settings page and select the `Only allow merge requests to be merged if the build succeeds` check box. - -Please note that you need to have builds configured to enable this feature. - -## Checkout merge requests locally - -### By adding a git alias - -Add the following alias to your `~/.gitconfig`: - -``` -[alias] - mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' - -``` - -Now you can check out a particular merge request from any repository and any remote, e.g. to check out a merge request number 5 as shown in GitLab from the `upstream` remote, do: - -``` -$ git mr upstream 5 -``` - -This will fetch the merge request into a local `mr-upstream-5` branch and check it out. - -### By modifying `.git/config` for a given repository - -Locate the section for your GitLab remote in the `.git/config` file. It looks like this: - -``` -[remote "origin"] - url = https://gitlab.com/gitlab-org/gitlab-ce.git - fetch = +refs/heads/*:refs/remotes/origin/* -``` - -Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section. - -It should look like this: - -``` -[remote "origin"] - url = https://gitlab.com/gitlab-org/gitlab-ce.git - fetch = +refs/heads/*:refs/remotes/origin/* - fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/* -``` - -Now you can fetch all the merge requests: - -``` -$ git fetch origin -From https://gitlab.com/gitlab-org/gitlab-ce.git - * [new ref] refs/merge-requests/1/head -> origin/merge-requests/1 - * [new ref] refs/merge-requests/2/head -> origin/merge-requests/2 -... -``` - -To check out a particular merge request: - -``` -$ git checkout origin/merge-requests/1 -``` - -## Ignore whitespace changes in Merge Request diff view - -![MR diff](merge_requests/merge_request_diff.png) - -If you click the "Hide whitespace changes" button, you can see the diff without whitespace changes. - -![MR diff without whitespace](merge_requests/merge_request_diff_without_whitespace.png) - -It is also working on commits compare view. - -![Commit Compare](merge_requests/commit_compare.png) - -## Merge Requests versions - -Every time you push to merge request branch, a new version of merge request diff -is created. When you visit the merge request page you see latest version of changes. -However you can select an older one from version dropdown - -![Merge Request Versions](merge_requests/versions.png) +This document was moved to [user/project/merge_requests](../user/project/merge_requests.md). diff --git a/doc/workflow/merge_requests/commit_compare.png b/doc/workflow/merge_requests/commit_compare.png deleted file mode 100644 index 0e4a2b23c04..00000000000 Binary files a/doc/workflow/merge_requests/commit_compare.png and /dev/null differ diff --git a/doc/workflow/merge_requests/merge_request_diff.png b/doc/workflow/merge_requests/merge_request_diff.png deleted file mode 100644 index 3ebbfb75ea3..00000000000 Binary files a/doc/workflow/merge_requests/merge_request_diff.png and /dev/null differ diff --git a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png deleted file mode 100644 index a0db535019c..00000000000 Binary files a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png and /dev/null differ diff --git a/doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png b/doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png deleted file mode 100644 index 18bebf5fe92..00000000000 Binary files a/doc/workflow/merge_requests/only_allow_merge_if_build_succeeds.png and /dev/null differ diff --git a/doc/workflow/merge_requests/versions.png b/doc/workflow/merge_requests/versions.png deleted file mode 100644 index c0a6dfe6806..00000000000 Binary files a/doc/workflow/merge_requests/versions.png and /dev/null differ -- cgit v1.2.1 From cd93b90f85b197dc0b386bbcaa027beaa93e3410 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 12:36:14 +0200 Subject: Move authorization_for_merge_requests.md to new location --- .../authorization_for_merge_requests.md | 40 +++++++++++++++++++++ doc/workflow/authorization_for_merge_requests.md | 41 +--------------------- 2 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 doc/user/project/merge_requests/authorization_for_merge_requests.md diff --git a/doc/user/project/merge_requests/authorization_for_merge_requests.md b/doc/user/project/merge_requests/authorization_for_merge_requests.md new file mode 100644 index 00000000000..d1d6d94ec11 --- /dev/null +++ b/doc/user/project/merge_requests/authorization_for_merge_requests.md @@ -0,0 +1,40 @@ +# Authorization for Merge requests + +There are two main ways to have a merge request flow with GitLab: working with protected branches in a single repository, or working with forks of an authoritative project. + +## Protected branch flow + +With the protected branch flow everybody works within the same GitLab project. + +The project maintainers get Master access and the regular developers get Developer access. + +The maintainers mark the authoritative branches as 'Protected'. + +The developers push feature branches to the project and create merge requests to have their feature branches reviewed and merged into one of the protected branches. + +Only users with Master access can merge changes into a protected branch. + +### Advantages + +- fewer projects means less clutter +- developers need to consider only one remote repository + +### Disadvantages + +- manual setup of protected branch required for each new project + +## Forking workflow + +With the forking workflow the maintainers get Master access and the regular developers get Reporter access to the authoritative repository, which prohibits them from pushing any changes to it. + +Developers create forks of the authoritative project and push their feature branches to their own forks. + +To get their changes into master they need to create a merge request across forks. + +### Advantages + +- in an appropriately configured GitLab group, new projects automatically get the required access restrictions for regular developers: fewer manual steps to configure authorization for new projects + +### Disadvantages + +- the project need to keep their forks up to date, which requires more advanced Git skills (managing multiple remotes) diff --git a/doc/workflow/authorization_for_merge_requests.md b/doc/workflow/authorization_for_merge_requests.md index d1d6d94ec11..7bf80a3ad0d 100644 --- a/doc/workflow/authorization_for_merge_requests.md +++ b/doc/workflow/authorization_for_merge_requests.md @@ -1,40 +1 @@ -# Authorization for Merge requests - -There are two main ways to have a merge request flow with GitLab: working with protected branches in a single repository, or working with forks of an authoritative project. - -## Protected branch flow - -With the protected branch flow everybody works within the same GitLab project. - -The project maintainers get Master access and the regular developers get Developer access. - -The maintainers mark the authoritative branches as 'Protected'. - -The developers push feature branches to the project and create merge requests to have their feature branches reviewed and merged into one of the protected branches. - -Only users with Master access can merge changes into a protected branch. - -### Advantages - -- fewer projects means less clutter -- developers need to consider only one remote repository - -### Disadvantages - -- manual setup of protected branch required for each new project - -## Forking workflow - -With the forking workflow the maintainers get Master access and the regular developers get Reporter access to the authoritative repository, which prohibits them from pushing any changes to it. - -Developers create forks of the authoritative project and push their feature branches to their own forks. - -To get their changes into master they need to create a merge request across forks. - -### Advantages - -- in an appropriately configured GitLab group, new projects automatically get the required access restrictions for regular developers: fewer manual steps to configure authorization for new projects - -### Disadvantages - -- the project need to keep their forks up to date, which requires more advanced Git skills (managing multiple remotes) +This document was moved to [user/project/merge_requests/authorization_for_merge_requests](../user/project/merge_requests/authorization_for_merge_requests.md) -- cgit v1.2.1 From b76ef40e36c70b18fc655a153c08c8814b6edf89 Mon Sep 17 00:00:00 2001 From: Airat Shigapov Date: Sun, 4 Sep 2016 16:33:14 +0300 Subject: Fix missing flash messages on service edit page --- CHANGELOG | 1 + app/controllers/projects/services_controller.rb | 5 ++--- spec/controllers/projects/services_controller_spec.rb | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 93e91c457a3..29515e59c52 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 8.12.0 (unreleased) - Fix markdown help references (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps) - Fix accessibility and visibility of project list dropdown button !6140 + - Fix missing flash messages on service edit page (airatshigapov) - Added project specific enable/disable setting for LFS !5997 - Don't expose a user's token in the `/api/v3/user` API (!6047) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 6a227d85f6f..97e6e9471e0 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -20,9 +20,8 @@ class Projects::ServicesController < Projects::ApplicationController def update if @service.update_attributes(service_params[:service]) redirect_to( - edit_namespace_project_service_path(@project.namespace, @project, - @service.to_param, notice: - 'Successfully updated.') + edit_namespace_project_service_path(@project.namespace, @project, @service.to_param), + notice: 'Successfully updated.' ) else render 'edit' diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index cccd492ef06..2e44b5128b4 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -49,4 +49,20 @@ describe Projects::ServicesController do let!(:referrer) { nil } end end + + describe 'PUT #update' do + context 'on successful update' do + it 'sets the flash' do + expect(service).to receive(:to_param).and_return('hipchat') + + put :update, + namespace_id: project.namespace.id, + project_id: project.id, + id: service.id, + service: { active: false } + + expect(flash[:notice]).to eq 'Successfully updated.' + end + end + end end -- cgit v1.2.1 From cbc97870bc03d987d82dac679d2a81eee54a4448 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 13:21:22 +0200 Subject: Move merge_when_build_succeeds.md to new location --- .../img/merge_when_build_succeeds_enable.png | Bin 0 -> 68769 bytes .../img/merge_when_build_succeeds_status.png | Bin 0 -> 82655 bytes .../merge_requests/merge_when_build_succeeds.md | 28 +++++++++++++++++++++ doc/workflow/merge_when_build_succeeds.md | 16 +----------- doc/workflow/merge_when_build_succeeds/enable.png | Bin 68769 -> 0 bytes doc/workflow/merge_when_build_succeeds/status.png | Bin 82655 -> 0 bytes 6 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png create mode 100644 doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png create mode 100644 doc/user/project/merge_requests/merge_when_build_succeeds.md delete mode 100644 doc/workflow/merge_when_build_succeeds/enable.png delete mode 100644 doc/workflow/merge_when_build_succeeds/status.png diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png new file mode 100644 index 00000000000..b86e6d7b3fd Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_when_build_succeeds_enable.png differ diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png new file mode 100644 index 00000000000..f3ea61d8147 Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_when_build_succeeds_status.png differ diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md new file mode 100644 index 00000000000..e89fb772d0d --- /dev/null +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -0,0 +1,28 @@ +# Merge When Build Succeeds + +When reviewing a merge request that looks ready to merge but still has one or +more CI builds running, you can set it to be merged automatically when all +builds succeed. This way, you don't have to wait for the builds to finish and +remember to merge the request manually. + +![Enable](img/merge_when_build_succeeds_enable.png) + +When you hit the "Merge When Build Succeeds" button, the status of the merge +request will be updated to represent the impending merge. If you cannot wait +for the build to succeed and want to merge immediately, this option is available +in the dropdown menu on the right of the main button. + +Both team developers and the author of the merge request have the option to +cancel the automatic merge if they find a reason why it shouldn't be merged +after all. + +![Status](img/merge_when_build_succeeds_status.png) + +When the build succeeds, the merge request will automatically be merged. When +the build fails, the author gets a chance to retry any failed builds, or to +push new commits to fix the failure. + +When the builds are retried and succeed on the second try, the merge request +will automatically be merged after all. When the merge request is updated with +new commits, the automatic merge is automatically canceled to allow the new +changes to be reviewed. diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md index 75e1fdff2b2..95afd12ebdb 100644 --- a/doc/workflow/merge_when_build_succeeds.md +++ b/doc/workflow/merge_when_build_succeeds.md @@ -1,15 +1 @@ -# Merge When Build Succeeds - -When reviewing a merge request that looks ready to merge but still has one or more CI builds running, you can set it to be merged automatically when all builds succeed. This way, you don't have to wait for the builds to finish and remember to merge the request manually. - -![Enable](merge_when_build_succeeds/enable.png) - -When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait for the build to succeed and want to merge immediately, this option is available in the dropdown menu on the right of the main button. - -Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged after all. - -![Status](merge_when_build_succeeds/status.png) - -When the build succeeds, the merge request will automatically be merged. When the build fails, the author gets a chance to retry any failed builds, or to push new commits to fix the failure. - -When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with new commits, the automatic merge is automatically canceled to allow the new changes to be reviewed. +This document was moved to [user/project/merge_requests/merge_when_build_succeeds](../user/project/merge_requests/merge_when_build_succeeds.md). diff --git a/doc/workflow/merge_when_build_succeeds/enable.png b/doc/workflow/merge_when_build_succeeds/enable.png deleted file mode 100644 index b86e6d7b3fd..00000000000 Binary files a/doc/workflow/merge_when_build_succeeds/enable.png and /dev/null differ diff --git a/doc/workflow/merge_when_build_succeeds/status.png b/doc/workflow/merge_when_build_succeeds/status.png deleted file mode 100644 index f3ea61d8147..00000000000 Binary files a/doc/workflow/merge_when_build_succeeds/status.png and /dev/null differ -- cgit v1.2.1 From 99e03f80ca875d261622268cf11eb63a02ae2bf5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 13:33:45 +0200 Subject: Move `wip_merge_requests.md` to a new location --- doc/intro/README.md | 2 +- .../merge_requests/img/wip_blocked_accept_button.png | Bin 0 -> 32720 bytes .../project/merge_requests/img/wip_mark_as_wip.png | Bin 0 -> 21640 bytes .../project/merge_requests/img/wip_unmark_as_wip.png | Bin 0 -> 16606 bytes .../merge_requests/work_in_progress_merge_requests.md | 17 +++++++++++++++++ doc/workflow/wip_merge_requests.md | 14 +------------- .../wip_merge_requests/blocked_accept_button.png | Bin 32720 -> 0 bytes doc/workflow/wip_merge_requests/mark_as_wip.png | Bin 21640 -> 0 bytes doc/workflow/wip_merge_requests/unmark_as_wip.png | Bin 16606 -> 0 bytes 9 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 doc/user/project/merge_requests/img/wip_blocked_accept_button.png create mode 100644 doc/user/project/merge_requests/img/wip_mark_as_wip.png create mode 100644 doc/user/project/merge_requests/img/wip_unmark_as_wip.png create mode 100644 doc/user/project/merge_requests/work_in_progress_merge_requests.md delete mode 100644 doc/workflow/wip_merge_requests/blocked_accept_button.png delete mode 100644 doc/workflow/wip_merge_requests/mark_as_wip.png delete mode 100644 doc/workflow/wip_merge_requests/unmark_as_wip.png diff --git a/doc/intro/README.md b/doc/intro/README.md index 1850031eb26..26f9cb0ec4a 100644 --- a/doc/intro/README.md +++ b/doc/intro/README.md @@ -23,7 +23,7 @@ Create merge requests and review code. - [Fork a project and contribute to it](../workflow/forking_workflow.md) - [Create a new merge request](../gitlab-basics/add-merge-request.md) - [Automatically close issues from merge requests](../customization/issue_closing.md) -- [Automatically merge when your builds succeed](../workflow/merge_when_build_succeeds.md) +- [Automatically merge when your builds succeed](../user/project/merge_requests/merge_when_build_succeeds.md) - [Revert any commit](../workflow/revert_changes.md) - [Cherry-pick any commit](../workflow/cherry_pick_changes.md) diff --git a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png new file mode 100644 index 00000000000..89c458aa8d9 Binary files /dev/null and b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png differ diff --git a/doc/user/project/merge_requests/img/wip_mark_as_wip.png b/doc/user/project/merge_requests/img/wip_mark_as_wip.png new file mode 100644 index 00000000000..9c37354a653 Binary files /dev/null and b/doc/user/project/merge_requests/img/wip_mark_as_wip.png differ diff --git a/doc/user/project/merge_requests/img/wip_unmark_as_wip.png b/doc/user/project/merge_requests/img/wip_unmark_as_wip.png new file mode 100644 index 00000000000..31f7326beb0 Binary files /dev/null and b/doc/user/project/merge_requests/img/wip_unmark_as_wip.png differ diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md new file mode 100644 index 00000000000..546c8bdc5e5 --- /dev/null +++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md @@ -0,0 +1,17 @@ +# "Work In Progress" Merge Requests + +To prevent merge requests from accidentally being accepted before they're +completely ready, GitLab blocks the "Accept" button for merge requests that +have been marked a **Work In Progress**. + +![Blocked Accept Button](img/wip_blocked_accept_button.png) + +To mark a merge request a Work In Progress, simply start its title with `[WIP]` +or `WIP:`. + +![Mark as WIP](img/wip_mark_as_wip.png) + +To allow a Work In Progress merge request to be accepted again when it's ready, +simply remove the `WIP` prefix. + +![Unark as WIP](img/wip_unmark_as_wip.png) diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md index 46035a5e6b6..abb8002f442 100644 --- a/doc/workflow/wip_merge_requests.md +++ b/doc/workflow/wip_merge_requests.md @@ -1,13 +1 @@ -# "Work In Progress" Merge Requests - -To prevent merge requests from accidentally being accepted before they're completely ready, GitLab blocks the "Accept" button for merge requests that have been marked a **Work In Progress**. - -![Blocked Accept Button](wip_merge_requests/blocked_accept_button.png) - -To mark a merge request a Work In Progress, simply start its title with `[WIP]` or `WIP:`. - -![Mark as WIP](wip_merge_requests/mark_as_wip.png) - -To allow a Work In Progress merge request to be accepted again when it's ready, simply remove the `WIP` prefix. - -![Unark as WIP](wip_merge_requests/unmark_as_wip.png) +This document was moved to [user/project/merge_requests/work_in_progress_merge_requests](../user/project/merge_requests/work_in_progress_merge_requests.md). diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png deleted file mode 100644 index 89c458aa8d9..00000000000 Binary files a/doc/workflow/wip_merge_requests/blocked_accept_button.png and /dev/null differ diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png deleted file mode 100644 index 9c37354a653..00000000000 Binary files a/doc/workflow/wip_merge_requests/mark_as_wip.png and /dev/null differ diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png deleted file mode 100644 index 31f7326beb0..00000000000 Binary files a/doc/workflow/wip_merge_requests/unmark_as_wip.png and /dev/null differ -- cgit v1.2.1 From 14a96a1ad2d96078cbd73a60e260601f47373060 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 13:40:41 +0200 Subject: Move cherry_pick_changes.md to new location --- doc/intro/README.md | 2 +- .../project/merge_requests/cherry_pick_changes.md | 52 ++++++++++++++++++++ .../img/cherry_pick_changes_commit.png | Bin 0 -> 304098 bytes .../img/cherry_pick_changes_commit_modal.png | Bin 0 -> 264883 bytes .../merge_requests/img/cherry_pick_changes_mr.png | Bin 0 -> 212267 bytes .../img/cherry_pick_changes_mr_modal.png | Bin 0 -> 186597 bytes doc/workflow/cherry_pick_changes.md | 53 +-------------------- doc/workflow/img/cherry_pick_changes_commit.png | Bin 304098 -> 0 bytes .../img/cherry_pick_changes_commit_modal.png | Bin 264883 -> 0 bytes doc/workflow/img/cherry_pick_changes_mr.png | Bin 212267 -> 0 bytes doc/workflow/img/cherry_pick_changes_mr_modal.png | Bin 186597 -> 0 bytes 11 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 doc/user/project/merge_requests/cherry_pick_changes.md create mode 100644 doc/user/project/merge_requests/img/cherry_pick_changes_commit.png create mode 100644 doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png create mode 100644 doc/user/project/merge_requests/img/cherry_pick_changes_mr.png create mode 100644 doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png delete mode 100644 doc/workflow/img/cherry_pick_changes_commit.png delete mode 100644 doc/workflow/img/cherry_pick_changes_commit_modal.png delete mode 100644 doc/workflow/img/cherry_pick_changes_mr.png delete mode 100644 doc/workflow/img/cherry_pick_changes_mr_modal.png diff --git a/doc/intro/README.md b/doc/intro/README.md index 26f9cb0ec4a..c319eb6c5ab 100644 --- a/doc/intro/README.md +++ b/doc/intro/README.md @@ -25,7 +25,7 @@ Create merge requests and review code. - [Automatically close issues from merge requests](../customization/issue_closing.md) - [Automatically merge when your builds succeed](../user/project/merge_requests/merge_when_build_succeeds.md) - [Revert any commit](../workflow/revert_changes.md) -- [Cherry-pick any commit](../workflow/cherry_pick_changes.md) +- [Cherry-pick any commit](../user/project/merge_requests/cherry_pick_changes.md) ## Test and Deploy diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md new file mode 100644 index 00000000000..64b94d81024 --- /dev/null +++ b/doc/user/project/merge_requests/cherry_pick_changes.md @@ -0,0 +1,52 @@ +# Cherry-pick changes + +> [Introduced][ce-3514] in GitLab 8.7. + +--- + +GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick] +with introducing a **Cherry-pick** button in Merge Requests and commit details. + +## Cherry-picking a Merge Request + +After the Merge Request has been merged, a **Cherry-pick** button will be available +to cherry-pick the changes introduced by that Merge Request: + +![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png) + +--- + +You can cherry-pick the changes directly into the selected branch or you can opt to +create a new Merge Request with the cherry-pick changes: + +![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png) + +## Cherry-picking a Commit + +You can cherry-pick a Commit from the Commit details page: + +![Cherry-pick commit](img/cherry_pick_changes_commit.png) + +--- + +Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes +directly into the target branch or create a new Merge Request to cherry-pick the +changes: + +![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png) + +--- + +Please note that when cherry-picking merge commits, the mainline will always be the +first parent. If you want to use a different mainline then you need to do that +from the command line. + +Here is a quick example to cherry-pick a merge commit using the second parent as the +mainline: + +```bash +git cherry-pick -m 2 7a39eb0 +``` + +[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request" +[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation" diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png b/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png new file mode 100644 index 00000000000..7fb68cc9e9b Binary files /dev/null and b/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png differ diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png b/doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png new file mode 100644 index 00000000000..5267e04562f Binary files /dev/null and b/doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png differ diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png b/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png new file mode 100644 index 00000000000..975fb13e463 Binary files /dev/null and b/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png differ diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png b/doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png new file mode 100644 index 00000000000..6c003bacbe3 Binary files /dev/null and b/doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png differ diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md index 64b94d81024..663ffd3f746 100644 --- a/doc/workflow/cherry_pick_changes.md +++ b/doc/workflow/cherry_pick_changes.md @@ -1,52 +1 @@ -# Cherry-pick changes - -> [Introduced][ce-3514] in GitLab 8.7. - ---- - -GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick] -with introducing a **Cherry-pick** button in Merge Requests and commit details. - -## Cherry-picking a Merge Request - -After the Merge Request has been merged, a **Cherry-pick** button will be available -to cherry-pick the changes introduced by that Merge Request: - -![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png) - ---- - -You can cherry-pick the changes directly into the selected branch or you can opt to -create a new Merge Request with the cherry-pick changes: - -![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png) - -## Cherry-picking a Commit - -You can cherry-pick a Commit from the Commit details page: - -![Cherry-pick commit](img/cherry_pick_changes_commit.png) - ---- - -Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes -directly into the target branch or create a new Merge Request to cherry-pick the -changes: - -![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png) - ---- - -Please note that when cherry-picking merge commits, the mainline will always be the -first parent. If you want to use a different mainline then you need to do that -from the command line. - -Here is a quick example to cherry-pick a merge commit using the second parent as the -mainline: - -```bash -git cherry-pick -m 2 7a39eb0 -``` - -[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request" -[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation" +This document was moved to [user/project/merge_requests/cherry_pick_changes](../user/project/merge_requests/cherry_pick_changes.md). diff --git a/doc/workflow/img/cherry_pick_changes_commit.png b/doc/workflow/img/cherry_pick_changes_commit.png deleted file mode 100644 index 7fb68cc9e9b..00000000000 Binary files a/doc/workflow/img/cherry_pick_changes_commit.png and /dev/null differ diff --git a/doc/workflow/img/cherry_pick_changes_commit_modal.png b/doc/workflow/img/cherry_pick_changes_commit_modal.png deleted file mode 100644 index 5267e04562f..00000000000 Binary files a/doc/workflow/img/cherry_pick_changes_commit_modal.png and /dev/null differ diff --git a/doc/workflow/img/cherry_pick_changes_mr.png b/doc/workflow/img/cherry_pick_changes_mr.png deleted file mode 100644 index 975fb13e463..00000000000 Binary files a/doc/workflow/img/cherry_pick_changes_mr.png and /dev/null differ diff --git a/doc/workflow/img/cherry_pick_changes_mr_modal.png b/doc/workflow/img/cherry_pick_changes_mr_modal.png deleted file mode 100644 index 6c003bacbe3..00000000000 Binary files a/doc/workflow/img/cherry_pick_changes_mr_modal.png and /dev/null differ -- cgit v1.2.1 From a8ba5840e0c86f3fb72cf81940674698e02ed025 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 13:46:37 +0200 Subject: Move revert_changes.md to new location --- doc/intro/README.md | 2 +- .../merge_requests/img/revert_changes_commit.png | Bin 0 -> 233750 bytes .../img/revert_changes_commit_modal.png | Bin 0 -> 205046 bytes .../merge_requests/img/revert_changes_mr.png | Bin 0 -> 241051 bytes .../merge_requests/img/revert_changes_mr_modal.png | Bin 0 -> 211022 bytes doc/user/project/merge_requests/revert_changes.md | 64 ++++++++++++++++++++ doc/workflow/img/revert_changes_commit.png | Bin 233750 -> 0 bytes doc/workflow/img/revert_changes_commit_modal.png | Bin 205046 -> 0 bytes doc/workflow/img/revert_changes_mr.png | Bin 241051 -> 0 bytes doc/workflow/img/revert_changes_mr_modal.png | Bin 211022 -> 0 bytes doc/workflow/revert_changes.md | 65 +-------------------- 11 files changed, 66 insertions(+), 65 deletions(-) create mode 100644 doc/user/project/merge_requests/img/revert_changes_commit.png create mode 100644 doc/user/project/merge_requests/img/revert_changes_commit_modal.png create mode 100644 doc/user/project/merge_requests/img/revert_changes_mr.png create mode 100644 doc/user/project/merge_requests/img/revert_changes_mr_modal.png create mode 100644 doc/user/project/merge_requests/revert_changes.md delete mode 100644 doc/workflow/img/revert_changes_commit.png delete mode 100644 doc/workflow/img/revert_changes_commit_modal.png delete mode 100644 doc/workflow/img/revert_changes_mr.png delete mode 100644 doc/workflow/img/revert_changes_mr_modal.png diff --git a/doc/intro/README.md b/doc/intro/README.md index c319eb6c5ab..71fef50ceb4 100644 --- a/doc/intro/README.md +++ b/doc/intro/README.md @@ -24,7 +24,7 @@ Create merge requests and review code. - [Create a new merge request](../gitlab-basics/add-merge-request.md) - [Automatically close issues from merge requests](../customization/issue_closing.md) - [Automatically merge when your builds succeed](../user/project/merge_requests/merge_when_build_succeeds.md) -- [Revert any commit](../workflow/revert_changes.md) +- [Revert any commit](../user/project/merge_requests/revert_changes.md) - [Cherry-pick any commit](../user/project/merge_requests/cherry_pick_changes.md) ## Test and Deploy diff --git a/doc/user/project/merge_requests/img/revert_changes_commit.png b/doc/user/project/merge_requests/img/revert_changes_commit.png new file mode 100644 index 00000000000..e7194fc3504 Binary files /dev/null and b/doc/user/project/merge_requests/img/revert_changes_commit.png differ diff --git a/doc/user/project/merge_requests/img/revert_changes_commit_modal.png b/doc/user/project/merge_requests/img/revert_changes_commit_modal.png new file mode 100644 index 00000000000..c660ec7eaec Binary files /dev/null and b/doc/user/project/merge_requests/img/revert_changes_commit_modal.png differ diff --git a/doc/user/project/merge_requests/img/revert_changes_mr.png b/doc/user/project/merge_requests/img/revert_changes_mr.png new file mode 100644 index 00000000000..3002f0ac1c5 Binary files /dev/null and b/doc/user/project/merge_requests/img/revert_changes_mr.png differ diff --git a/doc/user/project/merge_requests/img/revert_changes_mr_modal.png b/doc/user/project/merge_requests/img/revert_changes_mr_modal.png new file mode 100644 index 00000000000..c6aaeecc8a6 Binary files /dev/null and b/doc/user/project/merge_requests/img/revert_changes_mr_modal.png differ diff --git a/doc/user/project/merge_requests/revert_changes.md b/doc/user/project/merge_requests/revert_changes.md new file mode 100644 index 00000000000..5ead9f4177f --- /dev/null +++ b/doc/user/project/merge_requests/revert_changes.md @@ -0,0 +1,64 @@ +# Reverting changes + +> [Introduced][ce-1990] in GitLab 8.5. + +--- + +GitLab implements Git's powerful feature to [revert any commit][git-revert] +with introducing a **Revert** button in Merge Requests and commit details. + +## Reverting a Merge Request + +_**Note:** The **Revert** button will only be available for Merge Requests +created since GitLab 8.5. However, you can still revert a Merge Request +by reverting the merge commit from the list of Commits page._ + +After the Merge Request has been merged, a **Revert** button will be available +to revert the changes introduced by that Merge Request: + +![Revert Merge Request](img/revert_changes_mr.png) + +--- + +You can revert the changes directly into the selected branch or you can opt to +create a new Merge Request with the revert changes: + +![Revert Merge Request modal](img/revert_changes_mr_modal.png) + +--- + +After the Merge Request has been reverted, the **Revert** button will not be +available anymore. + +## Reverting a Commit + +You can revert a Commit from the Commit details page: + +![Revert commit](img/revert_changes_commit.png) + +--- + +Similar to reverting a Merge Request, you can opt to revert the changes +directly into the target branch or create a new Merge Request to revert the +changes: + +![Revert commit modal](img/revert_changes_commit_modal.png) + +--- + +After the Commit has been reverted, the **Revert** button will not be available +anymore. + +Please note that when reverting merge commits, the mainline will always be the +first parent. If you want to use a different mainline then you need to do that +from the command line. + +Here is a quick example to revert a merge commit using the second parent as the +mainline: + +```bash +git revert -m 2 7a39eb0 +``` + +[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request" +[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation" diff --git a/doc/workflow/img/revert_changes_commit.png b/doc/workflow/img/revert_changes_commit.png deleted file mode 100644 index e7194fc3504..00000000000 Binary files a/doc/workflow/img/revert_changes_commit.png and /dev/null differ diff --git a/doc/workflow/img/revert_changes_commit_modal.png b/doc/workflow/img/revert_changes_commit_modal.png deleted file mode 100644 index c660ec7eaec..00000000000 Binary files a/doc/workflow/img/revert_changes_commit_modal.png and /dev/null differ diff --git a/doc/workflow/img/revert_changes_mr.png b/doc/workflow/img/revert_changes_mr.png deleted file mode 100644 index 3002f0ac1c5..00000000000 Binary files a/doc/workflow/img/revert_changes_mr.png and /dev/null differ diff --git a/doc/workflow/img/revert_changes_mr_modal.png b/doc/workflow/img/revert_changes_mr_modal.png deleted file mode 100644 index c6aaeecc8a6..00000000000 Binary files a/doc/workflow/img/revert_changes_mr_modal.png and /dev/null differ diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md index 5ead9f4177f..cf1292253fc 100644 --- a/doc/workflow/revert_changes.md +++ b/doc/workflow/revert_changes.md @@ -1,64 +1 @@ -# Reverting changes - -> [Introduced][ce-1990] in GitLab 8.5. - ---- - -GitLab implements Git's powerful feature to [revert any commit][git-revert] -with introducing a **Revert** button in Merge Requests and commit details. - -## Reverting a Merge Request - -_**Note:** The **Revert** button will only be available for Merge Requests -created since GitLab 8.5. However, you can still revert a Merge Request -by reverting the merge commit from the list of Commits page._ - -After the Merge Request has been merged, a **Revert** button will be available -to revert the changes introduced by that Merge Request: - -![Revert Merge Request](img/revert_changes_mr.png) - ---- - -You can revert the changes directly into the selected branch or you can opt to -create a new Merge Request with the revert changes: - -![Revert Merge Request modal](img/revert_changes_mr_modal.png) - ---- - -After the Merge Request has been reverted, the **Revert** button will not be -available anymore. - -## Reverting a Commit - -You can revert a Commit from the Commit details page: - -![Revert commit](img/revert_changes_commit.png) - ---- - -Similar to reverting a Merge Request, you can opt to revert the changes -directly into the target branch or create a new Merge Request to revert the -changes: - -![Revert commit modal](img/revert_changes_commit_modal.png) - ---- - -After the Commit has been reverted, the **Revert** button will not be available -anymore. - -Please note that when reverting merge commits, the mainline will always be the -first parent. If you want to use a different mainline then you need to do that -from the command line. - -Here is a quick example to revert a merge commit using the second parent as the -mainline: - -```bash -git revert -m 2 7a39eb0 -``` - -[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request" -[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation" +This document was moved to [user/project/merge_requests/revert_changes](../user/project/merge_requests/revert_changes.md). -- cgit v1.2.1 From 4d77056af1ca5532ae8482b362b5ef466a640ae2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 14:15:57 +0200 Subject: Move "Only allow merge requests to be merged if the build succeeds" to new location --- app/views/projects/_merge_request_settings.html.haml | 2 +- doc/user/project/merge_requests.md | 14 ++------------ ...merge_when_build_succeeds_only_if_succeeds_msg.png | Bin 0 -> 11136 bytes ..._when_build_succeeds_only_if_succeeds_settings.png | Bin 0 -> 17552 bytes .../img/only_allow_merge_if_build_succeeds.png | Bin 17552 -> 0 bytes .../merge_requests/merge_when_build_succeeds.md | 18 ++++++++++++++++++ 6 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png create mode 100644 doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png delete mode 100644 doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 14eb47089b1..80053dd501b 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -11,4 +11,4 @@ %br %span.descr Builds need to be configured to enable this feature. - = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') + = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md index ecccc96582c..a1f5cc29ba8 100644 --- a/doc/user/project/merge_requests.md +++ b/doc/user/project/merge_requests.md @@ -1,17 +1,7 @@ # Merge Requests -Merge requests allow you to exchange changes you made to source code - -## Only allow merge requests to be merged if the build succeeds - -You can prevent merge requests from being merged if their build did not succeed -in the project settings page. - -![only_allow_merge_if_build_succeeds](merge_requests/img/only_allow_merge_if_build_succeeds.png) - -Navigate to project settings page and select the `Only allow merge requests to be merged if the build succeeds` check box. - -Please note that you need to have builds configured to enable this feature. +Merge requests allow you to exchange changes you made to source code and +collaborate with other people on the same project. ## Checkout merge requests locally diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png new file mode 100644 index 00000000000..6b9756b7418 Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_msg.png differ diff --git a/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png new file mode 100644 index 00000000000..18bebf5fe92 Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_when_build_succeeds_only_if_succeeds_settings.png differ diff --git a/doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png b/doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png deleted file mode 100644 index 18bebf5fe92..00000000000 Binary files a/doc/user/project/merge_requests/img/only_allow_merge_if_build_succeeds.png and /dev/null differ diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md index e89fb772d0d..011f9cbc381 100644 --- a/doc/user/project/merge_requests/merge_when_build_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -26,3 +26,21 @@ When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with new commits, the automatic merge is automatically canceled to allow the new changes to be reviewed. + +## Only allow merge requests to be merged if the build succeeds + +> **Note:** +You need to have builds configured to enable this feature. + +You can prevent merge requests from being merged if their build did not succeed. + +Navigate to your project's settings page, select the +**Only allow merge requests to be merged if the build succeeds** check box and +hit **Save** for the changes to take effect. + +![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) + +From now on, every time the build fails you will not be able to merge the merge +request from the UI, until you make the build pass. + +![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png) -- cgit v1.2.1 From 99f28c50d5483d6f55e7accaf1806e68acc46568 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 15:04:51 +0200 Subject: Move merge request versions to its own document --- doc/user/project/merge_requests.md | 6 +----- doc/user/project/merge_requests/img/versions.png | Bin 100566 -> 35001 bytes doc/user/project/merge_requests/versions.md | 22 ++++++++++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 doc/user/project/merge_requests/versions.md diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md index a1f5cc29ba8..f1a14e7e91c 100644 --- a/doc/user/project/merge_requests.md +++ b/doc/user/project/merge_requests.md @@ -73,8 +73,4 @@ It is also working on commits compare view. ## Merge Requests versions -Every time you push to merge request branch, a new version of merge request diff -is created. When you visit the merge request page you see latest version of changes. -However you can select an older one from version dropdown - -![Merge Request Versions](merge_requests/img/versions.png) +Select an older revision to from the version dropdown. diff --git a/doc/user/project/merge_requests/img/versions.png b/doc/user/project/merge_requests/img/versions.png index c0a6dfe6806..f279ccd7ce3 100644 Binary files a/doc/user/project/merge_requests/img/versions.png and b/doc/user/project/merge_requests/img/versions.png differ diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md new file mode 100644 index 00000000000..2b1c5ddd562 --- /dev/null +++ b/doc/user/project/merge_requests/versions.md @@ -0,0 +1,22 @@ +# Merge requests versions + +> Will be [introduced][ce-5467] in GitLab 8.12. + +Every time you push to a branch that is tied to a merge request, a new version +of merge request diff is created. When you visit a merge request that contains +more than one pushes, you can select and compare the versions of those merge +request diffs. + +By default, the latest version of changes is shown. However, you +can select an older one from version dropdown. + +![Merge Request Versions](img/versions.png) + +--- + +>**Note:** +Merge request versions are based on push not on commit. So, if you pushed 5 +commits in a single push, it will be a single option in the dropdown. If you +pushed 5 times, that will count for 5 options. + +[ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467 -- cgit v1.2.1 From bf00e0f4b2fd8994df8f7b567839f12715a05fde Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 18:25:05 +0200 Subject: Add links to new docs in merge_requests.md and workflow/README.md --- .../merge_requests/show/_how_to_merge.html.haml | 5 +- doc/user/project/merge_requests.md | 144 +++++++++++++++++---- .../merge_requests/img/merge_request_diff.png | Bin 103239 -> 69394 bytes .../img/merge_request_diff_without_whitespace.png | Bin 71896 -> 0 bytes doc/workflow/README.md | 13 +- 5 files changed, 128 insertions(+), 34 deletions(-) delete mode 100644 doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index b727efaa6a6..571e4dd1b1a 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -47,8 +47,9 @@ Note that pushing to GitLab requires write access to this repository. %p %strong Tip: - You can also checkout merge requests locally by - %a{href: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/workflow/merge_requests.md#checkout-merge-requests-locally', target: '_blank'} following these guidelines + = succeed '.' do + You can also checkout merge requests locally by + = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank' :javascript $(function(){ diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md index f1a14e7e91c..f79535d1542 100644 --- a/doc/user/project/merge_requests.md +++ b/doc/user/project/merge_requests.md @@ -3,9 +3,97 @@ Merge requests allow you to exchange changes you made to source code and collaborate with other people on the same project. -## Checkout merge requests locally +## Authorization for merge requests -### By adding a git alias +There are two main ways to have a merge request flow with GitLab: + +1. Working with [protected branches][] in a single repository +1. Working with forks of an authoritative project + +[Learn more about the authorization for merge requests.](merge_requests/authorization_for_merge_requests.md) + +## Cherry-pick changes + +Cherry-pick any commit in the UI by simply clicking the **Cherry-pick** button +in a merged merge requests or a commit. + +[Learn more about cherry-picking changes.](merge_requests/cherry_pick_changes.md) + +## Merge when build succeeds + +When reviewing a merge request that looks ready to merge but still has one or +more CI builds running, you can set it to be merged automatically when all +builds succeed. This way, you don't have to wait for the builds to finish and +remember to merge the request manually. + +[Learn more about merging when build succeeds.](merge_requests/merge_when_build_succeeds.md) + +## Resolve discussion comments in merge requests reviews + +Keep track of the progress during a code review with resolving comments. +Resolving comments prevents you from forgetting to address feedback and lets +you hide discussions that are no longer relevant. + +[Read more about resolving discussion comments in merge requests reviews.](merge_requests/merge_request_discussion_resolution.md) + +## Resolve conflicts + +When a merge request has conflicts, GitLab may provide the option to resolve +those conflicts in the GitLab UI. + +[Learn more about resolving merge conflicts in the UI.](merge_requests/resolve_conflicts.md) + +## Revert changes + +GitLab implements Git's powerful feature to revert any commit with introducing +a **Revert** button in merge requests and commit details. + +[Learn more about reverting changes in the UI](merge_requests/revert_changes.md) + +## Merge requests versions + +Every time you push to a branch that is tied to a merge request, a new version +of merge request diff is created. When you visit a merge request that contains +more than one pushes, you can select and compare the versions of those merge +request diffs. + +[Read more about the merge requests versions.](merge_requests/versions.md) + +## Work In Progress merge requests + +To prevent merge requests from accidentally being accepted before they're +completely ready, GitLab blocks the "Accept" button for merge requests that +have been marked as a **Work In Progress**. + +[Learn more about settings a merge request as "Work In Progress".](merge_requests/work_in_progress_merge_requests.md) + +## Ignore whitespace changes in Merge Request diff view + +If you click the **Hide whitespace changes** button, you can see the diff +without whitespace changes (if there are any). This is also working when on a +specific commit page. + +![MR diff](merge_requests/img/merge_request_diff.png) + +>**Tip:** +You can append `?w=1` while on the diffs page of a merge request to ignore any +whitespace changes. + +## Tips + +Here are some tips that will help you be more efficient with merge requests in +the command line. + +> **Note:** +This section might move in its own document in the future. + +### Checkout merge requests locally + +A merge request contains all the history from a repository, plus the additional +commits added to the branch associated with the merge request. Here's a few +tricks to checkout a merge request locally. + +#### Checkout locally by adding a git alias Add the following alias to your `~/.gitconfig`: @@ -14,17 +102,21 @@ Add the following alias to your `~/.gitconfig`: mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' - ``` -Now you can check out a particular merge request from any repository and any remote, e.g. to check out a merge request number 5 as shown in GitLab from the `upstream` remote, do: +Now you can check out a particular merge request from any repository and any +remote. For example, to check out the merge request with ID 5 as shown in GitLab +from the `upstream` remote, do: ``` -$ git mr upstream 5 +git mr upstream 5 ``` -This will fetch the merge request into a local `mr-upstream-5` branch and check it out. +This will fetch the merge request into a local `mr-upstream-5` branch and check +it out. -### By modifying `.git/config` for a given repository +#### Checkout locally by modifying `.git/config` for a given repository -Locate the section for your GitLab remote in the `.git/config` file. It looks like this: +Locate the section for your GitLab remote in the `.git/config` file. It looks +like this: ``` [remote "origin"] @@ -32,9 +124,19 @@ Locate the section for your GitLab remote in the `.git/config` file. It looks li fetch = +refs/heads/*:refs/remotes/origin/* ``` -Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section. +You can open the file with: + +``` +git config -e +``` + +Now add the following line to the above section: + +``` +fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/* +``` -It should look like this: +In the end, it should look like this: ``` [remote "origin"] @@ -46,31 +148,19 @@ It should look like this: Now you can fetch all the merge requests: ``` -$ git fetch origin +git fetch origin + +... From https://gitlab.com/gitlab-org/gitlab-ce.git * [new ref] refs/merge-requests/1/head -> origin/merge-requests/1 * [new ref] refs/merge-requests/2/head -> origin/merge-requests/2 ... ``` -To check out a particular merge request: +And to check out a particular merge request: ``` -$ git checkout origin/merge-requests/1 +git checkout origin/merge-requests/1 ``` -## Ignore whitespace changes in Merge Request diff view - -![MR diff](merge_requests/img/merge_request_diff.png) - -If you click the "Hide whitespace changes" button, you can see the diff without whitespace changes. - -![MR diff without whitespace](merge_requests/img/merge_request_diff_without_whitespace.png) - -It is also working on commits compare view. - -![Commit Compare](merge_requests/img/commit_compare.png) - -## Merge Requests versions - -Select an older revision to from the version dropdown. +[protected branches]: protected_branches.md diff --git a/doc/user/project/merge_requests/img/merge_request_diff.png b/doc/user/project/merge_requests/img/merge_request_diff.png index 3ebbfb75ea3..06ee4908edc 100644 Binary files a/doc/user/project/merge_requests/img/merge_request_diff.png and b/doc/user/project/merge_requests/img/merge_request_diff.png differ diff --git a/doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png b/doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png deleted file mode 100644 index a0db535019c..00000000000 Binary files a/doc/user/project/merge_requests/img/merge_request_diff_without_whitespace.png and /dev/null differ diff --git a/doc/workflow/README.md b/doc/workflow/README.md index d626b1311a2..d021cca7c38 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -1,6 +1,5 @@ # Workflow -- [Authorization for merge requests](authorization_for_merge_requests.md) - [Change your time zone](timezone.md) - [Description templates](../user/project/description_templates.md) - [Feature branch workflow](workflow.md) @@ -22,10 +21,14 @@ - [Releases](releases.md) - [Milestones](milestones.md) - [Merge Requests](../user/project/merge_requests.md) -- [Revert changes](revert_changes.md) -- [Cherry-pick changes](cherry_pick_changes.md) -- ["Work In Progress" Merge Requests](wip_merge_requests.md) -- [Merge When Build Succeeds](merge_when_build_succeeds.md) + - [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md) + - [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md) + - [Merge when build succeeds](../user/project/merge_requests/merge_when_build_succeeds.md) + - [Resolve discussion comments in merge requests reviews](../user/project/merge_requests/merge_request_discussion_resolution.md) + - [Resolve merge conflicts in the UI](../user/project/merge_requests/resolve_conflicts.md) + - [Revert changes in the UI](../user/project/merge_requests/revert_changes.md) + - [Merge requests versions](../user/project/merge_requests/versions.md) + - ["Work In Progress" merge requests](../user/project/merge_requests/work_in_progress_merge_requests.md) - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md) - [Importing from SVN, GitHub, BitBucket, etc](importing/README.md) - [Todos](todos.md) -- cgit v1.2.1 From 7f0400398d7a09fd6eb35e8058c93ab693c0c527 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 5 Sep 2016 17:38:20 +0100 Subject: Convert SnippetsList to ES6 --- app/assets/javascripts/snippets_list.js | 7 ------- app/assets/javascripts/snippets_list.js.es6 | 11 +++++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) delete mode 100644 app/assets/javascripts/snippets_list.js create mode 100644 app/assets/javascripts/snippets_list.js.es6 diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js deleted file mode 100644 index dce3d71aeee..00000000000 --- a/app/assets/javascripts/snippets_list.js +++ /dev/null @@ -1,7 +0,0 @@ -(function() { - this.gl.SnippetsList = function() { - $('.snippets-list-holder .pagination').on('ajax:success', function(e, data) { - $('.snippets-list-holder').replaceWith(data.html); - }); - }; -}).call(this); diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js.es6 new file mode 100644 index 00000000000..6f0996c0d2a --- /dev/null +++ b/app/assets/javascripts/snippets_list.js.es6 @@ -0,0 +1,11 @@ +(global => { + global.gl = global.gl || {}; + + gl.SnippetsList = function() { + var $holder = $('.snippets-list-holder'); + + $holder.find('.pagination').on('ajax:success', (e, data) => { + $holder.replaceWith(data.html); + }); + } +})(window); -- cgit v1.2.1 From 2246218cc00e1f8db92cae7234131ca00943f567 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 2 Sep 2016 15:24:19 +0200 Subject: Remove gitorious from import_sources on ApplicationSetting model --- CHANGELOG | 1 + ...op_gitorious_field_from_application_settings.rb | 31 ++++++++++++++++++++++ db/schema.rb | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb diff --git a/CHANGELOG b/CHANGELOG index 3f52fab74d7..f54a17e1ea2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -90,6 +90,7 @@ v 8.11.5 (unreleased) - Fix member expiration date picker after update - Fix suggested colors options for new labels in the admin area. !6138 - Fix GitLab import button + - Remove gitorious from import_sources v 8.11.4 - Fix resolving conflicts on forks. !6082 diff --git a/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb new file mode 100644 index 00000000000..5395d38ea8f --- /dev/null +++ b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb @@ -0,0 +1,31 @@ +class DropGitoriousFieldFromApplicationSettings < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # After the deploy the caches will be cold anyway + DOWNTIME = false + + def up + require 'yaml' + + yaml = connection.execute('SELECT import_sources FROM application_settings;').values[0][0] + yaml = YAML.safe_load(yaml) + yaml.delete 'gitorious' + + # No need for a WHERE clause as there is only one + connection.execute("UPDATE application_settings SET import_sources = #{update_yaml(yaml)}") + end + + def down + # noop, gitorious still yields a 404 anyway + end + + private + + def connection + ActiveRecord::Base.connection + end + + def update_yaml(yaml) + connection.quote(YAML.dump(yaml)) + end +end diff --git a/db/schema.rb b/db/schema.rb index c9023a02c77..5c283141084 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160901141443) do +ActiveRecord::Schema.define(version: 20160902122721) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From e683eecdd99748f4b08359e2e979f94bf8ab5792 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 2 Sep 2016 22:36:20 +0200 Subject: Support MySQL too, when removing gitorious from import_sources --- ...20160902122721_drop_gitorious_field_from_application_settings.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb index 5395d38ea8f..3342744ba93 100644 --- a/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb +++ b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb @@ -7,7 +7,11 @@ class DropGitoriousFieldFromApplicationSettings < ActiveRecord::Migration def up require 'yaml' - yaml = connection.execute('SELECT import_sources FROM application_settings;').values[0][0] + yaml = if Gitlab::Database.postgresql? + connection.execute('SELECT import_sources FROM application_settings;').values[0][0] + else + connection.execute('SELECT import_sources FROM application_settings;').first[0] + end yaml = YAML.safe_load(yaml) yaml.delete 'gitorious' -- cgit v1.2.1 From 026d1acd26dd09a84db969bfc0d5f64e67a5e96f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Sep 2016 19:32:37 +0200 Subject: Refactor authorization_for_merge_requests.md --- .../authorization_for_merge_requests.md | 48 ++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/doc/user/project/merge_requests/authorization_for_merge_requests.md b/doc/user/project/merge_requests/authorization_for_merge_requests.md index d1d6d94ec11..59b3fe7242c 100644 --- a/doc/user/project/merge_requests/authorization_for_merge_requests.md +++ b/doc/user/project/merge_requests/authorization_for_merge_requests.md @@ -1,40 +1,56 @@ # Authorization for Merge requests -There are two main ways to have a merge request flow with GitLab: working with protected branches in a single repository, or working with forks of an authoritative project. +There are two main ways to have a merge request flow with GitLab: + +1. Working with [protected branches] in a single repository. +1. Working with forks of an authoritative project. ## Protected branch flow With the protected branch flow everybody works within the same GitLab project. -The project maintainers get Master access and the regular developers get Developer access. +The project maintainers get Master access and the regular developers get +Developer access. The maintainers mark the authoritative branches as 'Protected'. -The developers push feature branches to the project and create merge requests to have their feature branches reviewed and merged into one of the protected branches. +The developers push feature branches to the project and create merge requests +to have their feature branches reviewed and merged into one of the protected +branches. -Only users with Master access can merge changes into a protected branch. +By default, only users with Master access can merge changes into a protected +branch. -### Advantages +**Advantages** -- fewer projects means less clutter -- developers need to consider only one remote repository +- Fewer projects means less clutter. +- Developers need to consider only one remote repository. -### Disadvantages +**Disadvantages** -- manual setup of protected branch required for each new project +- Manual setup of protected branch required for each new project ## Forking workflow -With the forking workflow the maintainers get Master access and the regular developers get Reporter access to the authoritative repository, which prohibits them from pushing any changes to it. +With the forking workflow the maintainers get Master access and the regular +developers get Reporter access to the authoritative repository, which prohibits +them from pushing any changes to it. + +Developers create forks of the authoritative project and push their feature +branches to their own forks. -Developers create forks of the authoritative project and push their feature branches to their own forks. +To get their changes into master they need to create a merge request across +forks. -To get their changes into master they need to create a merge request across forks. +**Advantages** -### Advantages +- In an appropriately configured GitLab group, new projects automatically get + the required access restrictions for regular developers: fewer manual steps + to configure authorization for new projects. -- in an appropriately configured GitLab group, new projects automatically get the required access restrictions for regular developers: fewer manual steps to configure authorization for new projects +**Disadvantages** -### Disadvantages +- The project need to keep their forks up to date, which requires more advanced + Git skills (managing multiple remotes). -- the project need to keep their forks up to date, which requires more advanced Git skills (managing multiple remotes) +[protected branches]: ../protected_branches.md -- cgit v1.2.1 From 5204911f610c4062ce7d831e8182b0e959eda17c Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 5 Sep 2016 19:29:49 +0200 Subject: Don't fail on an empty database --- ...2721_drop_gitorious_field_from_application_settings.rb | 15 ++++++++++----- db/schema.rb | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb index 3342744ba93..b7b6e9b3484 100644 --- a/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb +++ b/db/migrate/20160902122721_drop_gitorious_field_from_application_settings.rb @@ -7,11 +7,16 @@ class DropGitoriousFieldFromApplicationSettings < ActiveRecord::Migration def up require 'yaml' - yaml = if Gitlab::Database.postgresql? - connection.execute('SELECT import_sources FROM application_settings;').values[0][0] - else - connection.execute('SELECT import_sources FROM application_settings;').first[0] - end + import_sources = connection.execute('SELECT import_sources FROM application_settings;') + + yaml = if Gitlab::Database.postgresql? + import_sources.values[0][0] + else + return unless import_sources.first + + import_sources.first[0] + end + yaml = YAML.safe_load(yaml) yaml.delete 'gitorious' diff --git a/db/schema.rb b/db/schema.rb index 5c283141084..c9023a02c77 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160902122721) do +ActiveRecord::Schema.define(version: 20160901141443) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 1847227f4db1862825356f49f4147b82e8ff0f3d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Sep 2016 08:25:21 +0200 Subject: Remove doctoc --- doc/ci/yaml/README.md | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 58d5306f12a..86b94cf8f54 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -6,50 +6,6 @@ GitLab Runner to manage your project's builds. If you want a quick introduction to GitLab CI, follow our [quick start guide](../quick_start/README.md). ---- - - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [.gitlab-ci.yml](#gitlab-ci-yml) - - [image and services](#image-and-services) - - [before_script](#before_script) - - [after_script](#after_script) - - [stages](#stages) - - [types](#types) - - [variables](#variables) - - [cache](#cache) - - [cache:key](#cache-key) -- [Jobs](#jobs) - - [script](#script) - - [stage](#stage) - - [only and except](#only-and-except) - - [job variables](#job-variables) - - [tags](#tags) - - [allow_failure](#allow_failure) - - [when](#when) - - [Manual actions](#manual-actions) - - [environment](#environment) - - [artifacts](#artifacts) - - [artifacts:name](#artifacts-name) - - [artifacts:when](#artifacts-when) - - [artifacts:expire_in](#artifacts-expire_in) - - [dependencies](#dependencies) - - [before_script and after_script](#before_script-and-after_script) -- [Git Strategy](#git-strategy) -- [Shallow cloning](#shallow-cloning) -- [Hidden keys](#hidden-keys) -- [Special YAML features](#special-yaml-features) - - [Anchors](#anchors) -- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml) -- [Skipping builds](#skipping-builds) -- [Examples](#examples) - - - ---- - ## .gitlab-ci.yml From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML) -- cgit v1.2.1 From b24555b8f41228a22d43d6876bb307bd83b9209e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Sep 2016 08:26:48 +0200 Subject: Use stage instead of type --- app/views/projects/triggers/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml index 7f3de47d7df..867754bdf5e 100644 --- a/app/views/projects/triggers/index.html.haml +++ b/app/views/projects/triggers/index.html.haml @@ -48,7 +48,7 @@ %pre :plain trigger: - type: deploy + stage: deploy script: - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" %h5.prepend-top-default -- cgit v1.2.1 From 03bed0fbd31b2edc9702ba690bae8c5fc53eb5e5 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 6 Sep 2016 10:12:55 +0300 Subject: Clean up search result classes --- lib/gitlab/project_search_results.rb | 5 ----- lib/gitlab/search_results.rb | 9 --------- lib/gitlab/snippet_search_results.rb | 4 ---- spec/lib/gitlab/search_results_spec.rb | 18 ------------------ spec/lib/gitlab/snippet_search_results_spec.rb | 6 ------ 5 files changed, 42 deletions(-) diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 183bd10d6a3..5b9cfaeb2f8 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -28,11 +28,6 @@ module Gitlab end end - def total_count - @total_count ||= issues_count + merge_requests_count + blobs_count + - notes_count + wiki_blobs_count + commits_count - end - def blobs_count @blobs_count ||= blobs.count end diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index f8ab2b1f09e..2690938fe82 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -27,11 +27,6 @@ module Gitlab end end - def total_count - @total_count ||= projects_count + issues_count + merge_requests_count + - milestones_count - end - def projects_count @projects_count ||= projects.count end @@ -48,10 +43,6 @@ module Gitlab @milestones_count ||= milestones.count end - def empty? - total_count.zero? - end - private def projects diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb index e0e74ff8359..9e01f02029c 100644 --- a/lib/gitlab/snippet_search_results.rb +++ b/lib/gitlab/snippet_search_results.rb @@ -20,10 +20,6 @@ module Gitlab end end - def total_count - @total_count ||= snippet_titles_count + snippet_blobs_count - end - def snippet_titles_count @snippet_titles_count ||= snippet_titles.count end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 8a656ab0ee9..dfbefad6367 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -12,12 +12,6 @@ describe Gitlab::SearchResults do let!(:milestone) { create(:milestone, project: project, title: 'foo') } let(:results) { described_class.new(user, Project.all, 'foo') } - describe '#total_count' do - it 'returns the total amount of search hits' do - expect(results.total_count).to eq(4) - end - end - describe '#projects_count' do it 'returns the total amount of projects' do expect(results.projects_count).to eq(1) @@ -42,18 +36,6 @@ describe Gitlab::SearchResults do end end - describe '#empty?' do - it 'returns true when there are no search results' do - allow(results).to receive(:total_count).and_return(0) - - expect(results.empty?).to eq(true) - end - - it 'returns false when there are search results' do - expect(results.empty?).to eq(false) - end - end - describe 'confidential issues' do let(:project_1) { create(:empty_project) } let(:project_2) { create(:empty_project) } diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb index e86b9ef6a63..b661a894c0c 100644 --- a/spec/lib/gitlab/snippet_search_results_spec.rb +++ b/spec/lib/gitlab/snippet_search_results_spec.rb @@ -5,12 +5,6 @@ describe Gitlab::SnippetSearchResults do let(:results) { described_class.new(Snippet.all, 'foo') } - describe '#total_count' do - it 'returns the total amount of search hits' do - expect(results.total_count).to eq(2) - end - end - describe '#snippet_titles_count' do it 'returns the amount of matched snippet titles' do expect(results.snippet_titles_count).to eq(1) -- cgit v1.2.1 From 20cbdf8065b4564855521cb5deda924a2458b962 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Sep 2016 10:36:04 +0200 Subject: Clean up and refactor the triggers views --- app/views/projects/triggers/index.html.haml | 130 ++++++++++++++++------------ 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml index 867754bdf5e..f6e0b0a7c8a 100644 --- a/app/views/projects/triggers/index.html.haml +++ b/app/views/projects/triggers/index.html.haml @@ -4,65 +4,89 @@ .col-lg-3 %h4.prepend-top-0 = page_title - %p - Triggers can force a specific branch or tag to rebuild with an API call. + %p.prepend-top-20 + Triggers can force a specific branch or tag to get rebuilt with an API call. + %p.append-bottom-0 + = succeed '.' do + Learn more in the + = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank' .col-lg-9 - %h5.prepend-top-0 - Your triggers - - if @triggers.any? - .table-responsive - %table.table - %thead - %th Token - %th Last used - %th - = render partial: 'trigger', collection: @triggers, as: :trigger - - else - %p.settings-message.text-center.append-bottom-default - No triggers have been created yet. Add one using the button below. + .panel.panel-default + .panel-heading + %h4.panel-title + Manage your project's triggers + .panel-body + - if @triggers.any? + .table-responsive + %table.table + %thead + %th + %strong Token + %th + %strong Last used + %th + = render partial: 'trigger', collection: @triggers, as: :trigger + - else + %p.settings-message.text-center.append-bottom-default + No triggers have been created yet. Add one using the button below. - = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f| - = f.submit "Add Trigger", class: 'btn btn-success' + = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f| + = f.submit "Add trigger", class: 'btn btn-success' - %h5.prepend-top-default - Use CURL + .panel-footer - %p.light - Copy the token above, set your branch or tag name, and that reference will be rebuilt. + %p + In the following examples, you can see the exact API call you need to + make in order to rebuild a specific + %code ref + (branch or tag) with a trigger token. + %p + All you need to do is replace the + %code TOKEN + and + %code REF_NAME + with the trigger token and the branch or tag name respectively. - %pre - :plain - curl -X POST \ - -F token=TOKEN \ - -F ref=REF_NAME \ - #{builds_trigger_url(@project.id)} - %h5.prepend-top-default - Use .gitlab-ci.yml + %h5.prepend-top-default + Use cURL - %p.light - In the - %code .gitlab-ci.yml - of the dependent project, include the following snippet. - The project will rebuild at the end of the build. + %p.light + Copy one of the tokens above, set your branch or tag name, and that + reference will be rebuilt. - %pre - :plain - trigger: - stage: deploy - script: - - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" - %h5.prepend-top-default - Pass build variables + %pre + :plain + curl -X POST \ + -F token=TOKEN \ + -F ref=REF_NAME \ + #{builds_trigger_url(@project.id)} + %h5.prepend-top-default + Use .gitlab-ci.yml - %p.light - Add - %code variables[VARIABLE]=VALUE - to an API request. Variable values can be used to distinguish between triggered builds and normal builds. + %p.light + In the + %code .gitlab-ci.yml + of another project, include the following snippet. + The project will be rebuilt at the end of the build. - %pre.append-bottom-0 - :plain - curl -X POST \ - -F token=TOKEN \ - -F "ref=REF_NAME" \ - -F "variables[RUN_NIGHTLY_BUILD]=true" \ - #{builds_trigger_url(@project.id)} + %pre + :plain + trigger_build: + stage: deploy + script: + - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" + %h5.prepend-top-default + Pass build variables + + %p.light + Add + %code variables[VARIABLE]=VALUE + to an API request. Variable values can be used to distinguish between triggered builds and normal builds. + + %pre.append-bottom-0 + :plain + curl -X POST \ + -F token=TOKEN \ + -F "ref=REF_NAME" \ + -F "variables[RUN_NIGHTLY_BUILD]=true" \ + #{builds_trigger_url(@project.id)} -- cgit v1.2.1 From 24ac1d9b2a72682988fe619a1298332fe996280f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Sep 2016 10:37:10 +0200 Subject: Replace the triggers docs with new images --- doc/ci/triggers/img/builds_page.png | Bin 33324 -> 76181 bytes doc/ci/triggers/img/trigger_single_build.png | Bin 2387 -> 21152 bytes doc/ci/triggers/img/trigger_variables.png | Bin 4433 -> 9315 bytes doc/ci/triggers/img/triggers_page.png | Bin 12943 -> 12002 bytes doc/ci/yaml/README.md | 4 +++- 5 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ci/triggers/img/builds_page.png b/doc/ci/triggers/img/builds_page.png index 2dee8ee6107..c2cf4b1852c 100644 Binary files a/doc/ci/triggers/img/builds_page.png and b/doc/ci/triggers/img/builds_page.png differ diff --git a/doc/ci/triggers/img/trigger_single_build.png b/doc/ci/triggers/img/trigger_single_build.png index baf3fc183d8..fa86f0fee3d 100644 Binary files a/doc/ci/triggers/img/trigger_single_build.png and b/doc/ci/triggers/img/trigger_single_build.png differ diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png index 908355c33a5..b2fcc65d304 100644 Binary files a/doc/ci/triggers/img/trigger_variables.png and b/doc/ci/triggers/img/trigger_variables.png differ diff --git a/doc/ci/triggers/img/triggers_page.png b/doc/ci/triggers/img/triggers_page.png index 69cec5cdebf..438f285ae2d 100644 Binary files a/doc/ci/triggers/img/triggers_page.png and b/doc/ci/triggers/img/triggers_page.png differ diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 86b94cf8f54..41d9112a618 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -139,7 +139,7 @@ Alias for [stages](#stages). Introduced in GitLab Runner v0.5.0. GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the -build environment. The variables are stored in the git repository and are meant +build environment. The variables are stored in the Git repository and are meant to store non-sensitive project configuration, for example: ```yaml @@ -154,6 +154,8 @@ thus allowing to fine tune them. Variables can be also defined on [job level](#job-variables). +[Learn more about variables.](../variables/README.md) + ### cache >**Note:** -- cgit v1.2.1 From c5e5614fe7bef000a4d8da1f7590d666b05b4e62 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Sep 2016 10:47:48 +0200 Subject: Add a reference to triggers docs in ci/yaml/README.md --- doc/ci/yaml/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 41d9112a618..ff4c8ddc54b 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1044,6 +1044,13 @@ test:mysql: You can see that the hidden keys are conveniently used as templates. +## Triggers + +Triggers can be used to force a rebuild of a specific branch, tag or commit, +with an API call. + +[Read more in the triggers documentation.](../triggers/README.md) + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. -- cgit v1.2.1 From 4c3bfd08dbd1fe3f7b86257ef95804bcd356268f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Sep 2016 10:58:11 +0200 Subject: Add Changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index f54a17e1ea2..a9783622f35 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -84,6 +84,7 @@ v 8.12.0 (unreleased) - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fixed invisible scroll controls on build page on iPhone - Fix error on raw build trace download for old builds stored in database !4822 + - Refactor the triggers page and documentation !6217 v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch -- cgit v1.2.1 From a7a212c13f04f094edb7d67355a26199ac4f6274 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Sep 2016 11:52:01 +0200 Subject: Fix failing rspec test for triggers --- spec/features/triggers_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 3cbc8253ad6..72354834c5a 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -12,7 +12,7 @@ describe 'Triggers' do context 'create a trigger' do before do - click_on 'Add Trigger' + click_on 'Add trigger' expect(@project.triggers.count).to eq(1) end -- cgit v1.2.1 From f6f06eae967cd512c0c573449ce4bd3e177a0f90 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 23 Jun 2016 12:35:04 +0200 Subject: Have CI test migration paths This commit adds a job to the pipeline to test if migration on seed data works. Even though the seed data is not perfect, it does give more guarentees that this will succeed in the real world too. seed_fu is used instead of dev:setup to save some time, as they both generate the data in the same way, this should be fine. --- .gitlab-ci.yml | 16 +++++++++++++++- CHANGELOG | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be5614520a5..d0805125a42 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -248,6 +248,21 @@ bundler:audit: script: - "bundle exec bundle-audit check --update --ignore OSVDB-115941" +migration paths: + stage: test + <<: *use-db + only: + - master@gitlab-org/gitlab-ce + script: + - git checkout HEAD . + - git fetch --tags + - git checkout v8.5.9 + - 'echo test: unix:/var/opt/gitlab/redis/redis.socket > config/resque.yml' + - bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" --retry=3 + - rake db:drop db:create db:schema:load db:seed_fu + - git checkout $CI_BUILD_REF + - rake db:migrate + coverage: stage: post-test services: [] @@ -263,7 +278,6 @@ coverage: - coverage/index.html - coverage/assets/ - # Notify slack in the end notify:slack: diff --git a/CHANGELOG b/CHANGELOG index f54a17e1ea2..642937d5940 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.12.0 (unreleased) - Add white background for no readme container (ClemMakesApps) - API: Expose issue confidentiality flag. (Robert Schilling) - Fix markdown anchor icon interaction (ClemMakesApps) + - Test migration paths from 8.5 until current release !4874 - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import -- cgit v1.2.1 From 657bb8650ee405e2ba3a554dddf6767621ea5c54 Mon Sep 17 00:00:00 2001 From: ubudzisz Date: Thu, 7 Jul 2016 11:02:57 +0200 Subject: render only commit title update CHANGELOG --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 07b6e1298e4..bed16907410 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -535,6 +535,10 @@ v 8.9.7 (unreleased) v 8.9.6 - Fix importing of events under notes for GitLab projects + - Added setting to set new users by default as external !4545 (Dravere) + - Add min value for project limit field on user's form !3622 (jastkand) + - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt) + - Render only commit message title in builds v 8.9.5 - Add more debug info to import/export and memory killer. !5108 -- cgit v1.2.1 From 0893911d62520822bbf1578783e76351b7cfaa37 Mon Sep 17 00:00:00 2001 From: ubudzisz Date: Thu, 4 Aug 2016 08:59:35 +0200 Subject: add js file show variables on-click show variables on-click show variables on-click remove = and add new js classes into sidebar.scss show variables by clicking button show variables by clicking button show variables by clicking button show variables by clicking button use id for a class add nicescroll add nicescroll --- CHANGELOG | 7 +++---- app/assets/javascripts/build_variables.js | 7 +++++++ app/assets/stylesheets/framework/sidebar.scss | 13 +++++++++++++ app/views/projects/builds/_sidebar.html.haml | 6 +++--- spec/views/projects/builds/show.html.haml_spec.rb | 14 ++++++++++---- 5 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/build_variables.js diff --git a/CHANGELOG b/CHANGELOG index bed16907410..16f5258a275 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -130,6 +130,9 @@ v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 - Use gitlab-workhorse 0.7.11 !5983 - Does not halt the GitHub import process when an error occurs. !5763 + - Show value variables onclick (Katarzyna Kobierska Ula Budziszewska) + +v 8.11.1 (unreleased) - Fix file links on project page when default view is Files !5933 - Fixed enter key in search input not working !5888 @@ -535,10 +538,6 @@ v 8.9.7 (unreleased) v 8.9.6 - Fix importing of events under notes for GitLab projects - - Added setting to set new users by default as external !4545 (Dravere) - - Add min value for project limit field on user's form !3622 (jastkand) - - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt) - - Render only commit message title in builds v 8.9.5 - Add more debug info to import/export and memory killer. !5108 diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js new file mode 100644 index 00000000000..3b77c1d4f37 --- /dev/null +++ b/app/assets/javascripts/build_variables.js @@ -0,0 +1,7 @@ +$(function(){ + $('.reveal-variables').off('click').on('click',function(){ + $('.js-build-variable').toggle(); + $('.js-build-value').toggle().niceScroll(); + $('.reveal-variables').show(); + }); +}); diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 015fe3debf9..6eae609555b 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -226,3 +226,16 @@ header.header-pinned-nav { .right-sidebar { border-left: 1px solid $border-color; } + +.js-build-variable { + color: #c7254e; + font-size: 100%; + border-radius: 5px; +} + +.js-build-value { + padding: 2px 4px; + font-size: 100%; + color: #000; + background-color: #fff; +} diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 5ce36a475a9..585cc310be3 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -90,12 +90,12 @@ - if @build.trigger_request.variables %p - %span.build-light-text Variables: + .btn.group.btn-group-justified.reveal-variables Reveal Variables - @build.trigger_request.variables.each do |key, value| - %code - #{key}=#{value} + .hide.js-build-variable #{key} + .hide.js-build-value #{value} .block .title diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 464051063d8..1dc2048e1bc 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -59,14 +59,20 @@ describe 'projects/builds/show' do end it 'shows trigger variables in separate lines' do - expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_1', 'TRIGGER_VALUE_1')) - expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_2', 'TRIGGER_VALUE_2')) + expect(rendered).to have_css('.js-build-variable', visible: false, text: variable_regexp_key('TRIGGER_KEY_1')) + expect(rendered).to have_css('.js-build-variable', visible: false, text: variable_regexp_key('TRIGGER_KEY_2')) + expect(rendered).to have_css('.js-build-value', visible: false, text: variable_regexp_value('TRIGGER_VALUE_1')) + expect(rendered).to have_css('.js-build-value', visible: false, text: variable_regexp_value('TRIGGER_VALUE_2')) end end private - def variable_regexp(key, value) - /\A#{Regexp.escape("#{key}=#{value}")}\Z/ + def variable_regexp_key(key) + /\A#{Regexp.escape("#{key}")}\Z/ + end + + def variable_regexp_value(value) + /\A#{Regexp.escape("#{value}")}\Z/ end end -- cgit v1.2.1 From 691644dc5be544af0b0793353ab7f151136f1198 Mon Sep 17 00:00:00 2001 From: ubudzisz Date: Tue, 30 Aug 2016 17:37:49 +0200 Subject: add feature test add feature test add feature test add feature test remove test to builds_spec remove feature test to builds_spec remove feature test to builds_spec add changes to methods and remove FactoryGirl add changes to methods and remove FactoryGirl adding changes to test and js file changes grammar update changelog rename file name update files after review update files after review update files after review --- CHANGELOG | 4 +--- app/assets/javascripts/build_variables.js | 7 ------- app/assets/javascripts/build_variables.js.es6 | 6 ++++++ app/assets/stylesheets/framework/sidebar.scss | 8 +++----- app/views/projects/builds/_sidebar.html.haml | 5 +++-- spec/features/projects/builds_spec.rb | 20 ++++++++++++++++++++ spec/views/projects/builds/show.html.haml_spec.rb | 18 ++++-------------- 7 files changed, 37 insertions(+), 31 deletions(-) delete mode 100644 app/assets/javascripts/build_variables.js create mode 100644 app/assets/javascripts/build_variables.js.es6 diff --git a/CHANGELOG b/CHANGELOG index 16f5258a275..982b4dd5e5a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -88,6 +88,7 @@ v 8.12.0 (unreleased) - Fixed invisible scroll controls on build page on iPhone - Fix error on raw build trace download for old builds stored in database !4822 - Refactor the triggers page and documentation !6217 + - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch @@ -130,9 +131,6 @@ v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 - Use gitlab-workhorse 0.7.11 !5983 - Does not halt the GitHub import process when an error occurs. !5763 - - Show value variables onclick (Katarzyna Kobierska Ula Budziszewska) - -v 8.11.1 (unreleased) - Fix file links on project page when default view is Files !5933 - Fixed enter key in search input not working !5888 diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js deleted file mode 100644 index 3b77c1d4f37..00000000000 --- a/app/assets/javascripts/build_variables.js +++ /dev/null @@ -1,7 +0,0 @@ -$(function(){ - $('.reveal-variables').off('click').on('click',function(){ - $('.js-build-variable').toggle(); - $('.js-build-value').toggle().niceScroll(); - $('.reveal-variables').show(); - }); -}); diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js.es6 new file mode 100644 index 00000000000..8d3e29794a1 --- /dev/null +++ b/app/assets/javascripts/build_variables.js.es6 @@ -0,0 +1,6 @@ +$(function(){ + $('.reveal-variables').off('click').on('click',function(){ + $('.js-build').toggle().niceScroll(); + $(this).hide(); + }); +}); diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 6eae609555b..c5367c5a097 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -228,14 +228,12 @@ header.header-pinned-nav { } .js-build-variable { - color: #c7254e; - font-size: 100%; - border-radius: 5px; + color: $code-color; } .js-build-value { padding: 2px 4px; font-size: 100%; - color: #000; - background-color: #fff; + color: $black; + background-color: $white-light; } diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 585cc310be3..ba5354a1690 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -94,8 +94,9 @@ - @build.trigger_request.variables.each do |key, value| - .hide.js-build-variable #{key} - .hide.js-build-value #{value} + .hide.js-build + .js-build-variable= key + .js-build-value= value .block .title diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index d5d571e49bc..ea9e33ce722 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -164,6 +164,26 @@ describe "Builds" do expect(page).to have_link 'Raw' end end + + describe 'Variables' do + before do + @trigger_request = create :ci_trigger_request_with_variables + @build = create :ci_build, pipeline: @commit, trigger_request: @trigger_request + visit namespace_project_build_path(@project.namespace, @project, @build) + end + + it 'shows variable key and value after click', js: true do + expect(page).to have_css('.reveal-variables') + expect(page).not_to have_css('.js-build-variable') + expect(page).not_to have_css('.js-build-value') + + find('.reveal-variables').click + + expect(page).not_to have_css('.reveal-variables') + expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') + expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') + end + end end describe "POST /:project/builds/:id/cancel" do diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 1dc2048e1bc..446ba3bfa14 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -59,20 +59,10 @@ describe 'projects/builds/show' do end it 'shows trigger variables in separate lines' do - expect(rendered).to have_css('.js-build-variable', visible: false, text: variable_regexp_key('TRIGGER_KEY_1')) - expect(rendered).to have_css('.js-build-variable', visible: false, text: variable_regexp_key('TRIGGER_KEY_2')) - expect(rendered).to have_css('.js-build-value', visible: false, text: variable_regexp_value('TRIGGER_VALUE_1')) - expect(rendered).to have_css('.js-build-value', visible: false, text: variable_regexp_value('TRIGGER_VALUE_2')) + expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_1') + expect(rendered).to have_css('.js-build-variable', visible: false, text: 'TRIGGER_KEY_2') + expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_1') + expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2') end end - - private - - def variable_regexp_key(key) - /\A#{Regexp.escape("#{key}")}\Z/ - end - - def variable_regexp_value(value) - /\A#{Regexp.escape("#{value}")}\Z/ - end end -- cgit v1.2.1 From 790d7cce968260b28e451483606c44ec340f6d80 Mon Sep 17 00:00:00 2001 From: ubudzisz Date: Tue, 6 Sep 2016 11:49:40 +0200 Subject: improve code after review --- app/views/projects/builds/_sidebar.html.haml | 2 +- spec/features/projects/builds_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index ba5354a1690..56306b05934 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -90,7 +90,7 @@ - if @build.trigger_request.variables %p - .btn.group.btn-group-justified.reveal-variables Reveal Variables + %button.btn.group.btn-group-justified.reveal-variables Reveal Variables - @build.trigger_request.variables.each do |key, value| diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index ea9e33ce722..d1685f95503 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -177,7 +177,7 @@ describe "Builds" do expect(page).not_to have_css('.js-build-variable') expect(page).not_to have_css('.js-build-value') - find('.reveal-variables').click + click_button 'Reveal Variables' expect(page).not_to have_css('.reveal-variables') expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1') -- cgit v1.2.1 From 651f4c8e9bae6d8e94dac02e51ef5c258c62aed0 Mon Sep 17 00:00:00 2001 From: ubudzisz Date: Tue, 6 Sep 2016 13:03:48 +0200 Subject: remove css selectors --- app/assets/stylesheets/framework/sidebar.scss | 11 ----------- app/assets/stylesheets/pages/builds.scss | 10 ++++++++++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index c5367c5a097..015fe3debf9 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -226,14 +226,3 @@ header.header-pinned-nav { .right-sidebar { border-left: 1px solid $border-color; } - -.js-build-variable { - color: $code-color; -} - -.js-build-value { - padding: 2px 4px; - font-size: 100%; - color: $black; - background-color: $white-light; -} diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 23255f34710..614405aa5c1 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -115,6 +115,16 @@ width: 100%; } + .js-build-variable { + color: $code-color; + } + + .js-build-value { + padding: 2px 4px; + color: $black; + background-color: $white-light; + } + .build-sidebar-header { padding: 0 $gl-padding $gl-padding; -- cgit v1.2.1