From 0ea7f32afe9ff806f912a25efb4e967ee5da1e10 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Oct 2016 15:45:39 +0800 Subject: [PATCH 01/12] Make cancelled pipelines being able to retry Closes #23326 --- app/models/ci/pipeline.rb | 2 +- spec/models/ci/pipeline_spec.rb | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4fdb5fef4fb..a78b33988be 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -152,7 +152,7 @@ module Ci def retryable? builds.latest.any? do |build| - build.failed? && build.retryable? + (build.failed? || build.canceled?) && build.retryable? end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 550a890797e..4e0ce10603d 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -88,24 +88,38 @@ describe Ci::Pipeline, models: true do context 'no failed builds' do before do - FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'success' + create_build('rspec', 'success') end - it 'be not retryable' do + it 'is not retryable' do is_expected.to be_falsey end + + context 'one canceled job' do + before do + create_build('rubocop', 'canceled') + end + + it 'is retryable' do + is_expected.to be_truthy + end + end end context 'with failed builds' do before do - FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'running' - FactoryGirl.create :ci_build, name: "rubocop", pipeline: pipeline, status: 'failed' + create_build('rspec', 'running') + create_build('rubocop', 'failed') end - it 'be retryable' do + it 'is retryable' do is_expected.to be_truthy end end + + def create_build(name, status) + create(:ci_build, name: name, status: status, pipeline: pipeline) + end end describe '#stages' do From 88328c5729182bf6289b5f34ed3eb2a23098df25 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Oct 2016 16:24:21 +0800 Subject: [PATCH 02/12] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f718fc88a..0f3f020f44b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Cancelled pipelines could be retried. !6927 - Updating verbiage on git basics to be more intuitive - Clarify documentation for Runners API (Gennady Trafimenkov) - The instrumentation for Banzai::Renderer has been restored From bfb20200e9d1e7edd82a27d18d849ffba043845a Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 17 Oct 2016 13:40:02 +0100 Subject: [PATCH 03/12] Add a be_like_time matcher and use it in specs The amount of precision times have in databases is variable, so we need tolerances when comparing in specs. It's better to have the tolerance defined in one place than several. --- spec/models/issue/metrics_spec.rb | 8 ++++---- spec/models/merge_request/metrics_spec.rb | 2 +- spec/requests/api/issues_spec.rb | 4 ++-- spec/requests/api/notes_spec.rb | 2 +- spec/services/create_deployment_service_spec.rb | 6 +++--- spec/services/git_push_service_spec.rb | 2 +- spec/support/matchers/be_like_time.rb | 13 +++++++++++++ spec/workers/pipeline_metrics_worker_spec.rb | 4 ++-- 8 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 spec/support/matchers/be_like_time.rb diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index e170b087ebc..2459a49f095 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -13,7 +13,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time) + expect(metrics.first_associated_with_milestone_at).to be_like_time(time) end it "does not record the second time an issue is associated with a milestone" do @@ -24,7 +24,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time) + expect(metrics.first_associated_with_milestone_at).to be_like_time(time) end end @@ -36,7 +36,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_added_to_board_at).to be_within(1.second).of(time) + expect(metrics.first_added_to_board_at).to be_like_time(time) end it "does not record the second time an issue is associated with a list label" do @@ -48,7 +48,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_added_to_board_at).to be_within(1.second).of(time) + expect(metrics.first_added_to_board_at).to be_like_time(time) end end end diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index a79dd215d41..255db41cb19 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -12,7 +12,7 @@ describe MergeRequest::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.merged_at).to be_within(1.second).of(time) + expect(metrics.merged_at).to be_like_time(time) end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index f840778ae9b..beed53d1e5c 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -694,7 +694,7 @@ describe API::API, api: true do title: 'new issue', labels: 'label, label2', created_at: creation_time expect(response).to have_http_status(201) - expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end end @@ -895,7 +895,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label3' - expect(Time.parse(json_response['updated_at'])).to be_within(1.second).of(update_time) + expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) end end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 063a8706e76..d58bedc3bf7 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -217,7 +217,7 @@ describe API::API, api: true do expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) - expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 5fe56e7725f..0b84c7262c3 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -201,7 +201,7 @@ describe CreateDeploymentService, services: true do time = Time.now Timecop.freeze(time) { service.execute } - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) end it "doesn't set the time if the deploy's environment is not 'production'" do @@ -227,13 +227,13 @@ describe CreateDeploymentService, services: true do time = Time.now Timecop.freeze(time) { service.execute } - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) # Current deploy service = described_class.new(project, user, params) Timecop.freeze(time + 12.hours) { service.execute } - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index dd2a9e9903a..8dda34c7a03 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -364,7 +364,7 @@ describe GitPushService, services: true do it 'sets the metric for referenced issues' do execute_service(project, user, @oldrev, @newrev, @ref) - expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_within(1.second).of(commit_time) + expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_like_time(commit_time) end it 'does not set the metric for non-referenced issues' do diff --git a/spec/support/matchers/be_like_time.rb b/spec/support/matchers/be_like_time.rb new file mode 100644 index 00000000000..1f27390eab7 --- /dev/null +++ b/spec/support/matchers/be_like_time.rb @@ -0,0 +1,13 @@ +RSpec::Matchers.define :be_like_time do |expected| + match do |actual| + expect(actual).to be_within(1.second).of(expected) + end + + description do + "within one second of #{expected}" + end + + failure_message do |actual| + "expected #{actual} to be within one second of #{expected}" + end +end diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb index 232478c9735..2c9e7c2cd02 100644 --- a/spec/workers/pipeline_metrics_worker_spec.rb +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -23,7 +23,7 @@ describe PipelineMetricsWorker do it 'records the build start time' do subject - expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(pipeline.started_at) + expect(merge_request.reload.metrics.latest_build_started_at).to be_like_time(pipeline.started_at) end it 'clears the build end time' do @@ -39,7 +39,7 @@ describe PipelineMetricsWorker do it 'records the build end time' do subject - expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(pipeline.finished_at) + expect(merge_request.reload.metrics.latest_build_finished_at).to be_like_time(pipeline.finished_at) end end end From 317e48193fe5d75d6671c900a765ead719e6b4df Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 12 Oct 2016 15:34:47 +0200 Subject: [PATCH 04/12] Fix the diff in the merge request view when converting a symlink to a regular file. In this specific case using file_path as a cache key is not enough, because there are two entries with the same path. Closes #21610. --- CHANGELOG.md | 1 + lib/gitlab/diff/file.rb | 4 ++++ lib/gitlab/diff/file_collection/merge_request_diff.rb | 10 +++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd37d9bcde6..015d520db0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list + - Fix the diff in the merge request view when converting a symlink to a regular file - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index e47df508ca2..ce85e5e0123 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -125,6 +125,10 @@ module Gitlab repository.blob_at(commit.id, file_path) end + + def cache_key + "#{file_path}-#{new_file}-#{deleted_file}-#{renamed_file}" + 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 index 36348b33943..dc4d47c878b 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -35,16 +35,16 @@ module Gitlab # 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 + # The content of the cache is a Hash where the key identifies the file and the values are Arrays of # hashes that represent serialized diff lines. # def cache_highlight!(diff_file) - file_path = diff_file.file_path + item_key = diff_file.cache_key - if highlight_cache[file_path] - highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path]) + if highlight_cache[item_key] + highlight_diff_file_from_cache!(diff_file, highlight_cache[item_key]) else - highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) + highlight_cache[item_key] = diff_file.highlighted_diff_lines.map(&:to_hash) end end From 22ef066862bffc443baf8a630b0752ce37dab01c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 11:30:06 +0200 Subject: [PATCH 05/12] Avoid race condition when expiring artifacts It may happen that job meant to remove expired artifacts will be executed asynchronously when, in the meantime, project associated with given build gets removed by another asynchronous job. In that case we should not remove artifacts because such build will be removed anyway, when project removal is complete. --- .../expire_build_instance_artifacts_worker.rb | 10 +++-- ...re_build_instance_artifacts_worker_spec.rb | 44 ++++++++++++++----- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb index 916c2e633c1..d9e2cc37bb3 100644 --- a/app/workers/expire_build_instance_artifacts_worker.rb +++ b/app/workers/expire_build_instance_artifacts_worker.rb @@ -2,10 +2,14 @@ class ExpireBuildInstanceArtifactsWorker include Sidekiq::Worker def perform(build_id) - build = Ci::Build.with_expired_artifacts.reorder(nil).find_by(id: build_id) - return unless build + build = Ci::Build + .with_expired_artifacts + .reorder(nil) + .find_by(id: build_id) - Rails.logger.info "Removing artifacts build #{build.id}..." + return unless build.try(:project) + + Rails.logger.info "Removing artifacts for build #{build.id}..." build.erase_artifacts! end end diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index 2b140f2ba28..d202b3de77e 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -6,28 +6,48 @@ describe ExpireBuildInstanceArtifactsWorker do let(:worker) { described_class.new } describe '#perform' do - before { build } - - subject! { worker.perform(build.id) } + before do + worker.perform(build.id) + end context 'with expired artifacts' do - let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } + let(:artifacts_expiry) { { artifacts_expire_at: Time.now - 7.days } } - it 'does expire' do - expect(build.reload.artifacts_expired?).to be_truthy + context 'when associated project is valid' do + let(:build) do + create(:ci_build, :artifacts, artifacts_expiry) + end + + it 'does expire' do + expect(build.reload.artifacts_expired?).to be_truthy + end + + it 'does remove files' do + expect(build.reload.artifacts_file.exists?).to be_falsey + end + + it 'does nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).to be_nil + end end - it 'does remove files' do - expect(build.reload.artifacts_file.exists?).to be_falsey - end + context 'when associated project was removed' do + let(:build) do + create(:ci_build, :artifacts, artifacts_expiry) do |build| + build.project.delete + end + end - it 'does nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).to be_nil + it 'does not remove artifacts' do + expect(build.reload.artifacts_file.exists?).to be_truthy + end end end context 'with not yet expired artifacts' do - let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } + let(:build) do + create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) + end it 'does not expire' do expect(build.reload.artifacts_expired?).to be_falsey From 81f6e6e7768bed7fb63b1a8516dd0d0daf14f5d9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 13:23:02 +0200 Subject: [PATCH 06/12] Add Changelog entry to fix for removing artifacts --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd37d9bcde6..45b17a9f219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.13.0 (2016-10-22) +v 8.13.0 (unreleased) + - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page From da07c2e4d3d382c05ec287ee60f639b870074fe7 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 16 Sep 2016 16:15:39 -0300 Subject: [PATCH 07/12] Add visibility level to project repository --- CHANGELOG.md | 1 + app/assets/javascripts/project_new.js | 38 +++++- app/assets/stylesheets/pages/projects.scss | 83 ++++-------- app/controllers/projects_controller.rb | 35 +++-- app/helpers/preferences_helper.rb | 16 ++- app/helpers/projects_helper.rb | 33 ++++- app/models/project_feature.rb | 19 ++- app/policies/project_policy.rb | 14 +- .../projects/_customize_workflow.html.haml | 8 ++ app/views/projects/_home_panel.html.haml | 5 +- app/views/projects/_wiki.html.haml | 19 +++ app/views/projects/edit.html.haml | 68 +++++----- app/views/projects/issues/_issues.html.haml | 2 +- app/views/projects/show.html.haml | 122 +++++++++--------- ...ository_access_level_to_project_feature.rb | 14 ++ db/schema.rb | 3 +- spec/controllers/projects_controller_spec.rb | 40 ++++++ spec/factories/projects.rb | 2 + .../projects/features_visibility_spec.rb | 30 +++++ .../import_export/safe_model_attributes.yml | 1 + spec/models/project_feature_spec.rb | 23 +++- 21 files changed, 400 insertions(+), 176 deletions(-) create mode 100644 app/views/projects/_customize_workflow.html.haml create mode 100644 app/views/projects/_wiki.html.haml create mode 100644 db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 37383ea1b72..44ddc1e03a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Remove redundant mixins (ClemMakesApps) - Added 'Download' button to the Snippets page (Justin DiPierro) + - Add visibility level to project repository - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 3cf41505814..478e82aa14d 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -4,9 +4,8 @@ this.ProjectNew = (function() { function ProjectNew() { this.toggleSettings = bind(this.toggleSettings, this); - this.$selects = $('.features select').filter(function () { - return $(this).data('field'); - }); + this.$selects = $('.features select'); + this.$repoSelects = this.$selects.filter('.js-repo-select'); $('.project-edit-container').on('ajax:before', (function(_this) { return function() { @@ -16,6 +15,7 @@ })(this)); this.toggleSettings(); this.toggleSettingsOnclick(); + this.toggleRepoVisibility(); } ProjectNew.prototype.toggleSettings = function() { @@ -43,6 +43,38 @@ } }; + ProjectNew.prototype.toggleRepoVisibility = function () { + var $repoAccessLevel = $('.js-repo-access-level select'); + + this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']") + .nextAll() + .hide(); + + $repoAccessLevel.off('change') + .on('change', function () { + var selectedVal = parseInt($repoAccessLevel.val()); + + this.$repoSelects.each(function () { + var $this = $(this), + repoSelectVal = parseInt($this.val()); + + $this.find('option').show(); + + if (selectedVal < repoSelectVal) { + $this.val(selectedVal); + } + + $this.find("option[value='" + selectedVal + "']").nextAll().hide(); + }); + + if (selectedVal) { + this.$repoSelects.removeClass('disabled'); + } else { + this.$repoSelects.addClass('disabled'); + } + }.bind(this)); + }; + return ProjectNew; })(); diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index d30f02340b9..1062d7effb0 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -761,62 +761,6 @@ pre.light-well { .dropdown-menu { width: 300px; } - - &.from .compare-dropdown-toggle { - width: 237px; - } - - &.to .compare-dropdown-toggle { - width: 254px; - } - - .dropdown-toggle-text { - display: block; - height: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - } -} - -.compare-ellipsis { - display: inline; -} - -@media (max-width: $screen-xs-max) { - .compare-form-group { - .input-group { - width: 100%; - - & > .compare-dropdown-toggle { - width: 100%; - } - } - - .dropdown-menu { - width: 100%; - } - } - - .compare-switch-container { - text-align: center; - padding: 0 0 $gl-padding; - - .commits-compare-switch { - float: none; - } - } - - .compare-ellipsis { - display: block; - text-align: center; - padding: 0 0 $gl-padding; - } - - .commits-compare-btn { - width: 100%; - } } .clearable-input { @@ -855,3 +799,30 @@ pre.light-well { border-bottom-right-radius: 0; } } + +.project-home-empty { + border-top: 0; + + .container-fluid { + background: none; + } + + p { + margin-left: auto; + margin-right: auto; + max-width: 650px; + } +} + +.project-feature-nested { + @media (min-width: $screen-sm-min) { + padding-left: 45px; + } +} + +.project-repo-select { + &.disabled { + opacity: 0.5; + pointer-events: none; + } +} diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 62916270172..76b730198d4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,4 +1,5 @@ class ProjectsController < Projects::ApplicationController + include IssuableCollections include ExtractsPath before_action :authenticate_user!, except: [:show, :activity, :refs] @@ -103,16 +104,7 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| format.html do @notification_setting = current_user.notification_settings_for(@project) if current_user - - if @project.repository_exists? - if @project.empty_repo? - render 'projects/empty' - else - render :show - end - else - render 'projects/no_repo' - end + render_landing_page end format.atom do @@ -285,6 +277,26 @@ class ProjectsController < Projects::ApplicationController private + # Render project landing depending of which features are available + # So if page is not availble in the list it renders the next page + # + # pages list order: repository readme, wiki home, issues list, customize workflow + def render_landing_page + if @project.feature_available?(:repository, current_user) + return render 'projects/no_repo' unless @project.repository_exists? + render 'projects/empty' if @project.empty_repo? + else + if @project.wiki_enabled? + @wiki_home = @project.wiki.find_page('home', params[:version_id]) + elsif @project.feature_available?(:issues, current_user) + @issues = issues_collection + @issues = @issues.page(params[:page]) + end + + render :show + end + end + def determine_layout if [:new, :create].include?(action_name.to_sym) 'application' @@ -308,7 +320,8 @@ class ProjectsController < Projects::ApplicationController project_feature_attributes: [ :issues_access_level, :builds_access_level, - :wiki_access_level, :merge_requests_access_level, :snippets_access_level + :wiki_access_level, :merge_requests_access_level, + :snippets_access_level, :repository_access_level ] } diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index c3832cf5d65..a46f2c6e17d 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -50,6 +50,20 @@ module PreferencesHelper end def default_project_view - current_user ? current_user.project_view : 'readme' + return 'readme' unless current_user + + user_view = current_user.project_view + + if @project.feature_available?(:repository, current_user) + user_view + elsif user_view == "activity" + "activity" + elsif @project.wiki_enabled? + "wiki" + elsif @project.feature_available?(:issues, current_user) + "projects/issues/issues" + else + "customize_workflow" + end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e667c9e4e2e..d26b4018be6 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -134,16 +134,35 @@ module ProjectsHelper options = project_feature_options if @project.private? + level = @project.project_feature.send(field) options.delete('Everyone with access') - highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED + highest_available_option = options.values.max if level == ProjectFeature::ENABLED end options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) - content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe + + content_tag( + :select, + options, + name: "project[project_feature_attributes][#{field}]", + id: "project_project_feature_attributes_#{field}", + class: "pull-right form-control #{repo_children_classes(field)}", + data: { field: field } + ).html_safe end private + def repo_children_classes(field) + needs_repo_check = [:merge_requests_access_level, :builds_access_level] + return unless needs_repo_check.include?(field) + + classes = "project-repo-select js-repo-select" + classes << " disabled" unless @project.feature_available?(:repository, current_user) + + classes + end + def get_project_nav_tabs(project, current_user) nav_tabs = [:home] @@ -155,12 +174,8 @@ module ProjectsHelper nav_tabs << :merge_requests end - if can?(current_user, :read_pipeline, project) - nav_tabs << :pipelines - end - if can?(current_user, :read_build, project) - nav_tabs << :builds + nav_tabs << :pipelines end if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) @@ -435,4 +450,8 @@ module ProjectsHelper 'Everyone with access' => ProjectFeature::ENABLED } end + + def project_child_container_class(view_path) + view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}" + end end diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 530f7d5a30e..b37ce1d3cf6 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -13,23 +13,26 @@ class ProjectFeature < ActiveRecord::Base # Enabled: enabled for everyone able to access the project # - # Permision levels + # Permission levels DISABLED = 0 PRIVATE = 10 ENABLED = 20 - FEATURES = %i(issues merge_requests wiki snippets builds) + FEATURES = %i(issues merge_requests wiki snippets builds repository) # Default scopes force us to unscope here since a service may need to check # permissions for a project in pending_delete # http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to belongs_to :project, -> { unscope(where: :pending_delete) } + validate :repository_children_level + default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :issues_access_level, value: ENABLED, allows_nil: false default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false default_value_for :snippets_access_level, value: ENABLED, allows_nil: false default_value_for :wiki_access_level, value: ENABLED, allows_nil: false + default_value_for :repository_access_level, value: ENABLED, allows_nil: false def feature_available?(feature, user) raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature) @@ -57,6 +60,18 @@ class ProjectFeature < ActiveRecord::Base private + # Validates builds and merge requests access level + # which cannot be higher than repository access level + def repository_children_level + validator = lambda do |field| + level = public_send(field) || ProjectFeature::ENABLED + not_allowed = level > repository_access_level + self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed + end + + %i(merge_requests_access_level builds_access_level).each(&validator) + end + def get_permission(user, level) case level when DISABLED diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index be4721d7a51..fbb3d4507d6 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -162,11 +162,13 @@ class ProjectPolicy < BasePolicy end def disabled_features! + repository_enabled = project.feature_available?(:repository, user) + unless project.feature_available?(:issues, user) cannot!(*named_abilities(:issue)) end - unless project.feature_available?(:merge_requests, user) + unless project.feature_available?(:merge_requests, user) && repository_enabled cannot!(*named_abilities(:merge_request)) end @@ -183,13 +185,21 @@ class ProjectPolicy < BasePolicy cannot!(*named_abilities(:wiki)) end - unless project.feature_available?(:builds, user) + unless project.feature_available?(:builds, user) && repository_enabled cannot!(*named_abilities(:build)) cannot!(*named_abilities(:pipeline)) cannot!(*named_abilities(:environment)) cannot!(*named_abilities(:deployment)) end + unless repository_enabled + cannot! :push_code + cannot! :push_code_to_protected_branches + cannot! :download_code + cannot! :fork_project + cannot! :read_commit_status + end + unless project.container_registry_enabled cannot!(*named_abilities(:container_image)) end diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml new file mode 100644 index 00000000000..d2c1e943db1 --- /dev/null +++ b/app/views/projects/_customize_workflow.html.haml @@ -0,0 +1,8 @@ +.row-content-block.project-home-empty + %div.text-center{ class: container_class } + %h4 + Customize your workflow! + %p + Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production! + - if can?(current_user, :admin_project, @project) + = link_to "Get started", edit_project_path(@project), class: "btn btn-success" diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 5590198a20e..d3987fc9c4f 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -22,5 +22,6 @@ = render 'projects/buttons/star' = render 'projects/buttons/fork' - .project-clone-holder - = render "shared/clone_panel" + - if @project.feature_available?(:repository, current_user) + .project-clone-holder + = render "shared/clone_panel" diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml new file mode 100644 index 00000000000..f00422dd7c0 --- /dev/null +++ b/app/views/projects/_wiki.html.haml @@ -0,0 +1,19 @@ +- if @wiki_home.present? + %div{ class: container_class } + .wiki-holder.prepend-top-default.append-bottom-default + .wiki + = preserve do + = render_wiki_content(@wiki_home) +- else + - can_create_wiki = can?(current_user, :create_wiki, @project) + .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] } + %div.text-center{ class: container_class } + %h4 + This project does not have a wiki homepage yet + - if can_create_wiki + %p + Add a homepage to your wiki that contains information about your project + %p + We recommend you + = link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home) + to your project's wiki and GitLab will show it here instead of this message. diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index c8f84b96cb7..fb776e3a3e7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -50,31 +50,25 @@ .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) + = feature_fields.label :repository_access_level, "Repository", class: 'label-light' + %span.help-block Push files to be stored in this project + .col-md-3.js-repo-access-level + = project_feature_access_select(:repository_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) + .col-sm-12 + .row + .col-md-9.project-feature-nested + = 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.project-feature-nested + = 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 @@ -83,17 +77,31 @@ .col-md-3 = project_feature_access_select(:snippets_access_level) + .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 :wiki_access_level, "Wiki", class: 'label-light' + %span.help-block Pages for project documentation + .col-md-3 + = project_feature_access_select(:wiki_access_level) + - if Gitlab.config.lfs.enabled && current_user.admin? - .row - .col-md-9 - = f.label :lfs_enabled, 'LFS', class: 'label-light' - %span.help-block + .checkbox + = f.label :lfs_enabled do + = f.check_box :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') - .col-md-3 - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' } - - if Gitlab.config.registry.enabled + - if Gitlab.config.lfs.enabled && current_user.admin? .form-group .checkbox = f.label :container_registry_enabled do diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index a2c31c0b4c5..a4b752ad86d 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,5 +1,5 @@ %ul.content-list.issues-list.issuable-list - = render @issues + = render partial: "projects/issues/issue", collection: @issues - if @issues.blank? %li .nothing-here-block No issues to show diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index ea4deb6cb28..ba16c641462 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -12,72 +12,74 @@ = render 'projects/last_push' = render "home_panel" -%nav.project-stats{ class: (container_class) } - %ul.nav - %li - = link_to project_files_path(@project) do - Files (#{repository_size}) - %li - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do - #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) - %li - = link_to namespace_project_branches_path(@project.namespace, @project) do - #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) - %li - = link_to namespace_project_tags_path(@project.namespace, @project) do - #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) - - - if default_project_view != 'readme' && @repository.readme +- if @project.feature_available?(:repository, current_user) + %nav.project-stats{ class: container_class } + %ul.nav %li - = link_to 'Readme', readme_path(@project) - - - if @repository.changelog + = link_to project_files_path(@project) do + Files (#{repository_size}) %li - = link_to 'Changelog', changelog_path(@project) - - - if @repository.license_blob + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) %li - = link_to license_short_name(@project), license_path(@project) - - - if @repository.contribution_guide + = link_to namespace_project_branches_path(@project.namespace, @project) do + #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) %li - = link_to 'Contribution guide', contribution_guide_path(@project) + = link_to namespace_project_tags_path(@project.namespace, @project) do + #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) - - if @repository.gitlab_ci_yml - %li - = link_to 'CI configuration', ci_configuration_path(@project) + - if default_project_view != 'readme' && @repository.readme + %li + = link_to 'Readme', readme_path(@project) - - if current_user && can_push_branch?(@project, @project.default_branch) - - unless @repository.changelog - %li.missing - = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do - Add Changelog - - unless @repository.license_blob - %li.missing - = link_to add_special_file_path(@project, file_name: 'LICENSE') do - Add License - - unless @repository.contribution_guide - %li.missing - = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do - Add Contribution guide - - unless @repository.gitlab_ci_yml - %li.missing - = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do - Set Up CI + - if @repository.changelog + %li + = link_to 'Changelog', changelog_path(@project) - %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" + - if @repository.license_blob + %li + = link_to license_short_name(@project), license_path(@project) - = render 'projects/buttons/download', project: @project, ref: @ref - = render 'projects/buttons/dropdown' + - if @repository.contribution_guide + %li + = link_to 'Contribution guide', contribution_guide_path(@project) - = render 'shared/notifications/button', notification_setting: @notification_setting -- if @repository.commit - .project-last-commit{ class: container_class } - = render 'projects/last_commit', commit: @repository.commit, project: @project + - if @repository.gitlab_ci_yml + %li + = link_to 'CI configuration', ci_configuration_path(@project) + + - if current_user && can_push_branch?(@project, @project.default_branch) + - unless @repository.changelog + %li.missing + = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do + Add Changelog + - unless @repository.license_blob + %li.missing + = link_to add_special_file_path(@project, file_name: 'LICENSE') do + Add License + - unless @repository.contribution_guide + %li.missing + = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do + Add Contribution guide + - unless @repository.gitlab_ci_yml + %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', project: @project, ref: @ref + = render 'projects/buttons/dropdown' + + = render 'shared/notifications/button', notification_setting: @notification_setting + - if @repository.commit + .project-last-commit{ class: container_class } + = render 'projects/last_commit', commit: @repository.commit, project: @project %div{ class: container_class } - if @project.archived? @@ -86,5 +88,7 @@ = icon("exclamation-triangle fw") Archived project! Repository is read-only - %div{class: "project-show-#{default_project_view}"} - = render default_project_view + - view_path = default_project_view + + %div{ class: project_child_container_class(view_path) } + = render view_path diff --git a/db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb b/db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb new file mode 100644 index 00000000000..7b33da3ea11 --- /dev/null +++ b/db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb @@ -0,0 +1,14 @@ +class AddRepositoryAccessLevelToProjectFeature < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column_with_default(:project_features, :repository_access_level, :integer, default: ProjectFeature::ENABLED) + end + + def down + remove_column :project_features, :repository_access_level + end +end diff --git a/db/schema.rb b/db/schema.rb index a362fd8f228..51ac0fbaeb5 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: 20161007133303) do +ActiveRecord::Schema.define(version: 20161012180455) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -830,6 +830,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.integer "builds_access_level" t.datetime "created_at" t.datetime "updated_at" + t.integer "repository_access_level", default: 20, null: false end add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index da0fdce39db..8eefa284ba0 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -41,6 +41,46 @@ describe ProjectsController do end end end + + describe "when project repository is disabled" do + render_views + + before do + project.team << [user, :developer] + project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) + end + + it 'shows wiki homepage' do + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template('projects/_wiki') + end + + it 'shows issues list page if wiki is disabled' do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template('projects/issues/_issues') + end + + it 'shows customize workflow page if wiki and issues are disabled' do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) + + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template("projects/_customize_workflow") + end + + it 'shows activity if enabled by user' do + user.update_attribute(:project_view, 'activity') + + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template("projects/_activity") + end + end end context "project with empty repo" do diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 719ef17f57e..4065e2defbc 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -45,6 +45,7 @@ FactoryGirl.define do snippets_access_level ProjectFeature::ENABLED issues_access_level ProjectFeature::ENABLED merge_requests_access_level ProjectFeature::ENABLED + repository_access_level ProjectFeature::ENABLED end after(:create) do |project, evaluator| @@ -55,6 +56,7 @@ FactoryGirl.define do snippets_access_level: evaluator.snippets_access_level, issues_access_level: evaluator.issues_access_level, merge_requests_access_level: evaluator.merge_requests_access_level, + repository_access_level: evaluator.repository_access_level ) end end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 9b487e350f2..1d4484a9edd 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -2,8 +2,11 @@ require 'spec_helper' include WaitForAjax describe 'Edit Project Settings', feature: true do + include WaitForAjax + let(:member) { create(:user) } let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') } + let!(:issue) { create(:issue, project: project) } let(:non_member) { create(:user) } describe 'project features visibility selectors', js: true do @@ -119,4 +122,31 @@ describe 'Edit Project Settings', feature: true do end end end + + describe 'repository visibility', js: true do + before do + project.team << [member, :master] + login_as(member) + visit edit_namespace_project_path(project.namespace, project) + end + + it "disables repository related features" do + select "Disabled", from: "project_project_feature_attributes_repository_access_level" + + expect(find(".edit-project")).to have_selector("select.disabled", count: 2) + end + + it "shows empty features project homepage" do + select "Disabled", from: "project_project_feature_attributes_repository_access_level" + select "Disabled", from: "project_project_feature_attributes_issues_access_level" + select "Disabled", from: "project_project_feature_attributes_wiki_access_level" + + click_button "Save changes" + wait_for_ajax + + visit namespace_project_path(project.namespace, project) + + expect(page).to have_content "Customize your workflow!" + end + end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 8bccd313d6c..8c8be66df9f 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -307,6 +307,7 @@ ProjectFeature: - wiki_access_level - snippets_access_level - builds_access_level +- repository_access_level - created_at - updated_at ProtectedBranch::MergeAccessLevel: diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index 8d554a01be5..a55d43ab2f9 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -5,7 +5,7 @@ describe ProjectFeature do let(:user) { create(:user) } describe '#feature_available?' do - let(:features) { %w(issues wiki builds merge_requests snippets) } + let(:features) { %w(issues wiki builds merge_requests snippets repository) } context 'when features are disabled' do it "returns false" do @@ -64,6 +64,27 @@ describe ProjectFeature do end end + context 'repository related features' do + before do + project.project_feature.update_attributes( + merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::PRIVATE + ) + end + + it "does not allow repository related features have higher level" do + features = %w(builds merge_requests) + project_feature = project.project_feature + + features.each do |feature| + field = "#{feature}_access_level".to_sym + project_feature.update_attribute(field, ProjectFeature::ENABLED) + expect(project_feature.valid?).to be_falsy + end + end + end + describe '#*_enabled?' do let(:features) { %w(wiki builds merge_requests) } From 6a16697ad29bc2136411faf2431baa94f2a599e0 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 13 Oct 2016 13:01:27 +0200 Subject: [PATCH 08/12] Execute specific named route method from toggle_award_url helper method --- CHANGELOG.md | 1 + app/helpers/award_emoji_helper.rb | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dbe1832de0..81c25445ff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add Issue Board API support (andrebsguedes) - Allow the Koding integration to be configured through the API - Add new issue button to each list on Issues Board + - Execute specific named route method from toggle_award_url helper method - Added soft wrap button to repository file/blob editor - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - Show the time ago a merge request was deployed to an environment diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb index aa134cea31c..493f14f6f9d 100644 --- a/app/helpers/award_emoji_helper.rb +++ b/app/helpers/award_emoji_helper.rb @@ -1,9 +1,12 @@ module AwardEmojiHelper def toggle_award_url(awardable) - if @project - url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) + return url_for([:toggle_award_emoji, awardable]) unless @project + + if awardable.is_a?(Note) + # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (6.5x) + toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace_id, project_id: @project.id, id: awardable.id) else - url_for([:toggle_award_emoji, awardable]) + url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) end end end From b63b13f930804c83682f6424b8dddf2236649d6d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 11:15:44 +0200 Subject: [PATCH 09/12] Fix Changelog entry for fix to CI artifacts erase [ci skip] --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b17a9f219..a2b8641d12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.13.0 (2016-10-22) -v 8.13.0 (unreleased) - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) From 4622f7e298edeb7211b94f3634f79dfc78db3918 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:53:30 +0200 Subject: [PATCH 10/12] Reload pipeline settings when saving new settings --- app/views/projects/pipelines_settings/show.html.haml | 2 +- spec/features/pipelines_settings_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index 8c7222bfe3d..0740e9b56ab 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -7,7 +7,7 @@ .col-lg-9 %h5.prepend-top-0 Pipelines - = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project), remote: true, authenticity_token: true do |f| + = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f| %fieldset.builds-feature - unless @repository.gitlab_ci_yml .form-group diff --git a/spec/features/pipelines_settings_spec.rb b/spec/features/pipelines_settings_spec.rb index dcc364a3d01..76cb240ea98 100644 --- a/spec/features/pipelines_settings_spec.rb +++ b/spec/features/pipelines_settings_spec.rb @@ -24,11 +24,12 @@ feature "Pipelines settings", feature: true do context 'for master' do given(:role) { :master } - scenario 'be allowed to change' do + scenario 'be allowed to change', js: true do fill_in('Test coverage parsing', with: 'coverage_regex') click_on 'Save changes' expect(page.status_code).to eq(200) + expect(page).to have_button('Save changes', disabled: false) expect(page).to have_field('Test coverage parsing', with: 'coverage_regex') end end From b8a49e574a5cf513042f8e12d536d25fe08da801 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:58:11 +0200 Subject: [PATCH 11/12] Move specs for project pipeline settings page --- spec/features/{ => projects/settings}/pipelines_settings_spec.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/features/{ => projects/settings}/pipelines_settings_spec.rb (100%) diff --git a/spec/features/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb similarity index 100% rename from spec/features/pipelines_settings_spec.rb rename to spec/features/projects/settings/pipelines_settings_spec.rb From e01e2ad5d3371ee6ebdceedf023e1052aa9e6c06 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:59:16 +0200 Subject: [PATCH 12/12] Add Changelog entry for pipeline settings button fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90379e011d6..a33c837a1b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.13.0 (2016-10-22) + - Fix save button on project pipeline settings page. (!6955) - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel)