From dbd6b2dc0ad4a379490cbb1ea23eee9d90526163 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 8 Sep 2016 19:23:30 +0100 Subject: Changed build header username area to use the full name with the username as the tooltip --- app/views/projects/builds/_user.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/builds/_user.html.haml b/app/views/projects/builds/_user.html.haml index 2642de8021d..75101b78652 100644 --- a/app/views/projects/builds/_user.html.haml +++ b/app/views/projects/builds/_user.html.haml @@ -1,4 +1,5 @@ by %a{ href: user_path(@build.user) } = image_tag avatar_icon(@build.user, 24), class: "avatar s24" - %strong= @build.user.to_reference + %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } + = @build.user.name -- cgit v1.2.1 From f099e36c181d3f089a18dab20b5aa0cc982da0a1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 8 Sep 2016 21:00:24 +0100 Subject: Change username full name back to only username on xs viewports --- app/views/projects/builds/_user.html.haml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/projects/builds/_user.html.haml b/app/views/projects/builds/_user.html.haml index 75101b78652..83f299da651 100644 --- a/app/views/projects/builds/_user.html.haml +++ b/app/views/projects/builds/_user.html.haml @@ -1,5 +1,7 @@ by %a{ href: user_path(@build.user) } - = image_tag avatar_icon(@build.user, 24), class: "avatar s24" - %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } - = @build.user.name + %span.hidden-xs + = image_tag avatar_icon(@build.user, 24), class: "avatar s24" + %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } + = @build.user.name + %strong.visible-xs-inline= @build.user.to_reference -- cgit v1.2.1 From 735b2b5769cb170b466bd2f5ddc116a997ac75a5 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 10:45:42 +0100 Subject: Adds link to close environment in a merge request --- app/assets/stylesheets/pages/merge_requests.scss | 9 +++++++++ app/views/projects/merge_requests/widget/_heading.html.haml | 3 +++ 2 files changed, 12 insertions(+) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..f2c2e06ad3e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -177,6 +177,15 @@ .ci-coverage { float: right; } + + .close-env-container { + color: $gl-text-color; + float: right; + + a { + color: $gl-text-color; + } + } } .mr_source_commit, diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index b5f5e11d4c3..c56283836a9 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -57,3 +57,6 @@ - if external_url = link_to external_url, target: '_blank' do = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) + %span.close-env-container + = link_to '#', class: 'close-evn-link' do + = icon('stop-circle-o', text: 'Stop environment') \ No newline at end of file -- cgit v1.2.1 From 10ea760251af3bcb38f03ca07df8728b0f125c12 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 13:59:31 +0100 Subject: Adds new line in the end of the file --- app/views/projects/merge_requests/widget/_heading.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index c56283836a9..7d9ed7dd5a1 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -59,4 +59,4 @@ = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) %span.close-env-container = link_to '#', class: 'close-evn-link' do - = icon('stop-circle-o', text: 'Stop environment') \ No newline at end of file + = icon('stop-circle-o', text: 'Stop environment') -- cgit v1.2.1 From b60e8309c58fc24c6aaa86adc7c8ddab2aac54e0 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 14:11:04 +0100 Subject: Adds variables to controller --- app/controllers/projects/environments_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 58678f96879..d5de9e796df 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -6,7 +6,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController before_action :environment, only: [:show, :edit, :update, :destroy] def index + @scope = params[:scope] @environments = project.environments + + # TODO: fix the values of this vars to show the correct results + @available_environments_count = project.environments.count + @stopped_environments_count = project.environments.count end def show -- cgit v1.2.1 From 08e708579213b5ee2ed80f1fd8d3966a897ed6ff Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 14:11:41 +0100 Subject: Adds tabs to environments list --- app/views/projects/environments/index.html.haml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index b3eb5b0011a..393f9e0d57b 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -5,6 +5,19 @@ %div{ class: container_class } - if can?(current_user, :create_environment, @project) && !@environments.blank? .top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to project_environments_path(@project) do + Availabe + %span.badge.js-avaibale-environments-count + = number_with_delimiter(@available_environments_count) + + %li{class: ('active' if @scope == 'stopped')} + = link_to project_environments_path(@project, scope: :stopped) do + Stopped + %span.badge.js-stopped-environments-count + = number_with_delimiter(@stopped_environments_count) + .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment -- cgit v1.2.1 From bc49974d94c8ddec59fbc3ebaf16a0e00269e647 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 21:26:33 +0100 Subject: Adds stop environment button to environments list --- app/assets/stylesheets/pages/environments.scss | 8 ++++++++ app/views/projects/deployments/_actions.haml | 5 +++++ app/views/projects/environments/_environment.html.haml | 5 +++-- app/views/projects/environments/show.html.haml | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index d01c60ee6ab..2e14bdb0eb3 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -24,6 +24,14 @@ .branch-name { color: $gl-dark-link-color; } + + .close-env-link { + color: $table-text-gray; + + .close-env-icon { + font-size: 14px; + } + } } .table.builds.environments { diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 16d134eb6b6..a1d9da285c0 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -14,6 +14,11 @@ = custom_icon('icon_play') %span= action.name.humanize + - if :can_be_stopped? + .inline + %a.close-env-link.btn + = icon('stop', class: 'close-env-icon') + - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do - if deployment.last? diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 36a6162a5a8..27b7254fcfb 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -1,4 +1,5 @@ - last_deployment = environment.last_deployment +- can_be_stopped = true %tr.environment %td @@ -20,5 +21,5 @@ - if last_deployment #{time_ago_with_tooltip(last_deployment.created_at)} - %td - = render 'projects/deployments/actions', deployment: last_deployment + %td.hidden-xs + = render 'projects/deployments/actions', deployment: last_deployment, can_be_stopped: can_be_stopped diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 8f8c1c4ce22..6412c809d52 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -30,7 +30,7 @@ %th Commit %th Build %th - %th + %th.hidden-xs = render @deployments -- cgit v1.2.1 From ed975fdcd4bb2ecb47753e2e1c017175c211adbe Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 21:33:50 +0100 Subject: Replaces destroy button with close one --- app/views/projects/environments/show.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 6412c809d52..5ca35bb92b1 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -10,7 +10,8 @@ .nav-controls - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete + / TODO: Confirm if the method is :delete + = link_to 'Close', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? .blank-state.blank-state-no-icon -- cgit v1.2.1 From 34a980851ebdaeff1734c2adb031253fdfe7b46f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 21:41:36 +0100 Subject: Adds close button to builds list --- app/views/projects/deployments/_actions.haml | 7 +++---- app/views/projects/deployments/_deployment.html.haml | 2 +- app/views/projects/environments/_environment.html.haml | 3 +-- app/views/projects/environments/index.html.haml | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index a1d9da285c0..01b33be719f 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -14,10 +14,9 @@ = custom_icon('icon_play') %span= action.name.humanize - - if :can_be_stopped? - .inline - %a.close-env-link.btn - = icon('stop', class: 'close-env-icon') + .inline + %a.close-env-link.btn + = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index cd95841ca5a..fce7b1cb34e 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -14,5 +14,5 @@ %td #{time_ago_with_tooltip(deployment.created_at)} - %td + %td.hidden-xs = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 27b7254fcfb..2e9c395f08a 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -1,5 +1,4 @@ - last_deployment = environment.last_deployment -- can_be_stopped = true %tr.environment %td @@ -22,4 +21,4 @@ #{time_ago_with_tooltip(last_deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: last_deployment, can_be_stopped: can_be_stopped + = render 'projects/deployments/actions', deployment: last_deployment diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 393f9e0d57b..949f0855777 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -42,5 +42,5 @@ %th Last Deployment %th Commit %th - %th + %th.hidden-xs = render @environments -- cgit v1.2.1 From f82d6c180d171d09d6822aaffb447b4c048749a6 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 22:33:37 +0100 Subject: Updates failing test --- spec/features/environments_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 4309a726917..3b38a7f5007 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -175,12 +175,12 @@ feature 'Environments', feature: true do before do visit namespace_project_environment_path(project.namespace, project, environment) end - + context 'when logged as master' do given(:role) { :master } - scenario 'does delete environment' do - click_link 'Destroy' + scenario 'does close environment' do + click_link 'Close' expect(page).not_to have_link(environment.name) end end @@ -188,8 +188,8 @@ feature 'Environments', feature: true do context 'when logged as developer' do given(:role) { :developer } - scenario 'does not have a Destroy link' do - expect(page).not_to have_link('Destroy') + scenario 'does not have a Close link' do + expect(page).not_to have_link('Close') end end end -- cgit v1.2.1 From 2c01b19f8f55c7105506cdbe964d24d0a3c6dfc4 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 10:49:17 +0100 Subject: Fixes space between buttons --- app/views/projects/deployments/_actions.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 01b33be719f..6e5e1288722 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -19,7 +19,7 @@ = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) - = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do + = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do - if deployment.last? Re-deploy - else -- cgit v1.2.1 From 3f85c3ef1629870e22f6a676585e8de80e5120c3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 13:10:50 +0200 Subject: Initial support for closing environments --- app/models/ci/build.rb | 4 ++++ app/models/deployment.rb | 13 +++++++++++++ app/models/environment.rb | 18 ++++++++++++++++++ app/services/create_deployment_service.rb | 12 +++++++++++- db/migrate/20161006104309_add_state_to_environment.rb | 9 +++++++++ lib/gitlab/ci/config/node/environment.rb | 4 +++- 6 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20161006104309_add_state_to_environment.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5dbf66173de..aea881d9aa8 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -110,6 +110,10 @@ module Ci project.builds_enabled? && commands.present? && manual? && skipped? end + def closes_environment?(name) + environment == name && options.fetch(:environment, {}).fetch(:close, false) + end + def play(current_user = nil) # Try to queue a current build if self.enqueue diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 82b27b78229..070c76339b1 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -77,6 +77,19 @@ class Deployment < ActiveRecord::Base take end + def close_action + return nil unless manual_actions + + @close_action ||= + manual_actions.find do |manual_action| + manual_action.try(:closes_environment?, deployable.environment) + end + end + + def closeable? + close_action.present? + end + private def ref_path diff --git a/app/models/environment.rb b/app/models/environment.rb index f0f3ee23223..6ec498ea2b7 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -19,6 +19,24 @@ class Environment < ActiveRecord::Base allow_nil: true, addressable_url: true + delegate :closeable?, :close_action, to: :last_deployment, allow_nil: true + + scope :opened, -> { where(state: [:opened]) } + scope :closed, -> { where(state: [:closed]) } + + state_machine :state, initial: :opened do + event :close do + transition opened: :closed + end + + event :reopen do + transition closed: :opened + end + + state :opened + state :closed + end + def last_deployment deployments.last end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 799ad3e1bd0..c87542e57a2 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -4,6 +4,13 @@ class CreateDeploymentService < BaseService def execute(deployable = nil) environment = find_or_create_environment + if close? + environment.close + return + end + + environment.reopen + deployment = project.deployments.create( environment: environment, ref: params[:ref], @@ -14,7 +21,6 @@ class CreateDeploymentService < BaseService ) deployment.update_merge_request_metrics! - deployment end @@ -44,6 +50,10 @@ class CreateDeploymentService < BaseService options[:url] end + def close? + options[:close] + end + def options params[:options] || {} end diff --git a/db/migrate/20161006104309_add_state_to_environment.rb b/db/migrate/20161006104309_add_state_to_environment.rb new file mode 100644 index 00000000000..2a6fd1a6a93 --- /dev/null +++ b/db/migrate/20161006104309_add_state_to_environment.rb @@ -0,0 +1,9 @@ +class AddStateToEnvironment < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :environments, :state, :string + end +end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index d388ab6b879..daa115f9017 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -8,7 +8,7 @@ module Gitlab class Environment < Entry include Validatable - ALLOWED_KEYS = %i[name url] + ALLOWED_KEYS = %i[name url close] validations do validate do @@ -35,6 +35,8 @@ module Gitlab length: { maximum: 255 }, addressable_url: true, allow_nil: true + + validates :close, boolean: true, allow_nil: true end end -- cgit v1.2.1 From fd4e0703e81de31aaabdfc983b013382841fcaa3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 13:13:47 +0200 Subject: Add play type --- app/models/ci/build.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index aea881d9aa8..09c5223c60d 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -110,8 +110,12 @@ module Ci project.builds_enabled? && commands.present? && manual? && skipped? end + def close_environment? + options.fetch(:environment, {}).fetch(:close, false) + end + def closes_environment?(name) - environment == name && options.fetch(:environment, {}).fetch(:close, false) + environment == name && close_environment? end def play(current_user = nil) @@ -125,6 +129,16 @@ module Ci end end + def play_type + return nil unless playable? + + if close_environment? + :close + else + :play + end + end + def retryable? project.builds_enabled? && commands.present? && complete? end -- cgit v1.2.1 From c319cc5ab5064459aaf803ee719a08e9663726ea Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 13:45:45 +0200 Subject: Make environments to be close able --- .../projects/environments_controller.rb | 11 ++++---- app/models/project.rb | 2 +- app/views/projects/deployments/_actions.haml | 8 ++++-- .../projects/environments/_environment.html.haml | 2 +- app/views/projects/environments/index.html.haml | 32 +++++++++++----------- app/views/projects/environments/show.html.haml | 7 +++-- .../merge_requests/widget/_heading.html.haml | 5 ++-- .../20161006104309_add_state_to_environment.rb | 10 +++++-- 8 files changed, 45 insertions(+), 32 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index d5de9e796df..4fe8c3a1889 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -7,11 +7,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController def index @scope = params[:scope] - @environments = project.environments - - # TODO: fix the values of this vars to show the correct results - @available_environments_count = project.environments.count - @stopped_environments_count = project.environments.count + @all_environments = project.environments + @environments = + case @scope + when 'closed' then @all_environments.closed + else @all_environments.opened + end end def show diff --git a/app/models/project.rb b/app/models/project.rb index ecd742a17d5..12705f9ae48 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1302,7 +1302,7 @@ class Project < ActiveRecord::Base environment_ids.where(ref: ref) end - environments.where(id: environment_ids).select do |environment| + environments.opened.where(id: environment_ids).select do |environment| environment.includes_commit?(commit) end end diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 6e5e1288722..2d9c229ed5b 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -14,9 +14,11 @@ = custom_icon('icon_play') %span= action.name.humanize - .inline - %a.close-env-link.btn - = icon('stop', class: 'close-env-icon') + - if local_assigns.fetch(:allow_close, false) && deployment.closeable? + .inline + %a.close-env-link.btn + = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 2e9c395f08a..205392b1e81 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -21,4 +21,4 @@ #{time_ago_with_tooltip(last_deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: last_deployment + = render 'projects/deployments/actions', deployment: last_deployment, allow_close: environment.opened? diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 949f0855777..702cccd6ab3 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -3,26 +3,26 @@ = render "projects/pipelines/head" %div{ class: container_class } - - if can?(current_user, :create_environment, @project) && !@environments.blank? - .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to project_environments_path(@project) do - Availabe - %span.badge.js-avaibale-environments-count - = number_with_delimiter(@available_environments_count) - - %li{class: ('active' if @scope == 'stopped')} - = link_to project_environments_path(@project, scope: :stopped) do - Stopped - %span.badge.js-stopped-environments-count - = number_with_delimiter(@stopped_environments_count) + .top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to project_environments_path(@project) do + Availabe + %span.badge.js-avaibale-environments-count + = number_with_delimiter(@all_environments.opened.count) - .nav-controls + %li{class: ('active' if @scope == 'closed')} + = link_to project_environments_path(@project, scope: :stopped) do + Stopped + %span.badge.js-stopped-environments-count + = number_with_delimiter(@all_environments.closed.count) + + .nav-controls + - if can?(current_user, :create_environment, @project) && !@all_environments.blank? = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment - - if @environments.blank? + - if @all_environments.blank? .blank-state.blank-state-no-icon %h2.blank-state-title You don't have any environments right now. diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 5ca35bb92b1..5f4a0ef082c 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -2,6 +2,8 @@ - page_title "Environments" = render "projects/pipelines/head" +- last_deployment = @environment.last_deployment + %div{ class: container_class } .top-area .col-md-9 @@ -10,8 +12,9 @@ .nav-controls - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - / TODO: Confirm if the method is :delete - = link_to 'Close', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete + - if @environment.opened? && last_deployment.try(:close_action) + = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? .blank-state.blank-state-no-icon diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 7d9ed7dd5a1..a92f0f9f481 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -58,5 +58,6 @@ = link_to external_url, target: '_blank' do = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) %span.close-env-container - = link_to '#', class: 'close-evn-link' do - = icon('stop-circle-o', text: 'Stop environment') + - if environment.closeable? + = link_to [:play, @project.namespace.becomes(Namespace), @project, environment.close_action], method: :post, class: 'close-evn-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop-circle-o', text: 'Stop environment') diff --git a/db/migrate/20161006104309_add_state_to_environment.rb b/db/migrate/20161006104309_add_state_to_environment.rb index 2a6fd1a6a93..83dff07069f 100644 --- a/db/migrate/20161006104309_add_state_to_environment.rb +++ b/db/migrate/20161006104309_add_state_to_environment.rb @@ -1,9 +1,15 @@ class AddStateToEnvironment < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + DOWNTIME = false - def change - add_column :environments, :state, :string + def up + add_column_with_default(:environments, :state, :string, default: :opened) + end + + def down + remove_column(:environments, :state) end end -- cgit v1.2.1 From 0e1f39d8cee3a6d23fccb195f8257178df840805 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 14:16:03 +0200 Subject: Allow to close environments --- app/views/projects/deployments/_actions.haml | 5 ++--- app/views/projects/environments/index.html.haml | 4 ++-- app/views/projects/environments/show.html.haml | 1 - db/fixtures/development/14_pipelines.rb | 1 + db/schema.rb | 5 +++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 2d9c229ed5b..7b7e37353e3 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -16,9 +16,8 @@ - if local_assigns.fetch(:allow_close, false) && deployment.closeable? .inline - %a.close-env-link.btn - = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do - = icon('stop', class: 'close-env-icon') + = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 702cccd6ab3..11e2ff506b4 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -7,12 +7,12 @@ %ul.nav-links %li{class: ('active' if @scope.nil?)} = link_to project_environments_path(@project) do - Availabe + Available %span.badge.js-avaibale-environments-count = number_with_delimiter(@all_environments.opened.count) %li{class: ('active' if @scope == 'closed')} - = link_to project_environments_path(@project, scope: :stopped) do + = link_to project_environments_path(@project, scope: :closed) do Stopped %span.badge.js-stopped-environments-count = number_with_delimiter(@all_environments.closed.count) diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 5f4a0ef082c..4981b95a19b 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -14,7 +14,6 @@ = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - if @environment.opened? && last_deployment.try(:close_action) = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? .blank-state.blank-state-no-icon diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 803cbca584d..d3fabe111a1 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -17,6 +17,7 @@ class Gitlab::Seeder::Pipelines { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running }, { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled }, { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success }, + { name: 'close staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped, options: { environment: { close: true } } }, { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] diff --git a/db/schema.rb b/db/schema.rb index ad62c249b3f..b9dc5b5f895 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: 20160926145521) do +ActiveRecord::Schema.define(version: 20161006104309) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -392,11 +392,12 @@ ActiveRecord::Schema.define(version: 20160926145521) do create_table "environments", force: :cascade do |t| t.integer "project_id" - t.string "name", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" t.string "external_url" t.string "environment_type" + t.string "state", default: "opened", null: false end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree -- cgit v1.2.1 From 443619300d067a83cbd53872635c2c3cfd1f6655 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 23 Sep 2016 15:18:33 +0100 Subject: Added `issuable_filters_present` to check for active filters before rendering the reset button Added tests --- app/helpers/issuables_helper.rb | 4 ++++ app/views/shared/issuable/_filter.html.haml | 5 +++-- spec/features/issues/reset_filters_spec.rb | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 692fadd505f..03b2db1bc91 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -124,6 +124,10 @@ module IssuablesHelper end end + def issuable_filters_present + params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name] + end + def issuables_count_for_state(issuable_type, state) issuables_finder = public_send("#{issuable_type}_finder") issuables_finder.params[:state] = state diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 31620297be0..6b43a76c404 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -29,8 +29,9 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - .filter-item.inline.reset-filters - %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters + - if issuable_filters_present + .filter-item.inline.reset-filters + %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters .pull-right - if boards_page diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index f4d0f13c3d5..c9a3ecf16ea 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -75,6 +75,14 @@ feature 'Issues filter reset button', feature: true, js: true do end end + context 'when no filters have been applied' do + it 'the reset link should not be visible' do + visit_issues(project) + expect(page).to have_css('.issue', count: 2) + expect(page).not_to have_css '.reset_filters' + end + end + def reset_filters find('.reset-filters').click end -- cgit v1.2.1 From 594c320851afbf4c8dd1c78600a0195f12d6ce41 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 21:44:29 +0100 Subject: Adds tests to verify if tabs are rendered --- spec/features/environments_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 3b38a7f5007..99246589eae 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -18,11 +18,26 @@ feature 'Environments', feature: true do before do visit namespace_project_environments_path(project.namespace, project) end + + context 'shows two tabs' do + scenario 'does show Available tab with link' do + expect(page).to have_link('Available') + end + + scenario 'does show Stopped tab with link' do + expect(page).to have_link('Stopped') + end + end context 'without environments' do scenario 'does show no environments' do expect(page).to have_content('You don\'t have any environments right now.') end + + scenario 'does show 0 as counter for environments in both tabs' do + expect(page.find('.js-avaibale-environments-count').text).to eq('0') + expect(page.find('.js-stopped-environments-count').text).to eq('0') + end end context 'with environments' do -- cgit v1.2.1 From 7d12683de51721e75b314b29272f4f024f7f0655 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 11 Oct 2016 11:03:11 +0100 Subject: Fixes broken tests --- spec/features/environments_spec.rb | 47 ++++++++++++++++++++-- .../merge_when_build_succeeds_spec.rb | 9 +++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 99246589eae..a8244ca89c6 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -46,6 +46,14 @@ feature 'Environments', feature: true do scenario 'does show environment name' do expect(page).to have_link(environment.name) end + + scenario 'does show number of opened environments in Availabe tab' do + expect(page.find('.js-avaibale-environments-count').text).to eq('1') + end + + scenario 'does show number of closed environments in Stopped tab' do + expect(page.find('.js-stopped-environments-count').text).to eq('0') + end context 'without deployments' do scenario 'does show no deployments' do @@ -76,6 +84,16 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + scenario 'does show close button' do + # TODO: Add test to verify if close button is visible + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end + + scenario 'does allow to close environment' do + # TODO: Add test to verify if close environment works + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end end end end @@ -137,6 +155,16 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + scenario 'does show close button' do + # TODO: Add test to verify if close button is visible + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end + + scenario 'does allow to close environment' do + # TODO: Add test to verify if close environment works + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end end end end @@ -194,9 +222,22 @@ feature 'Environments', feature: true do context 'when logged as master' do given(:role) { :master } - scenario 'does close environment' do - click_link 'Close' - expect(page).not_to have_link(environment.name) + scenario 'does not have a Close link' do + expect(page).not_to have_link('Close') + end + + context 'when environment is opened and can be closed' do + let(:project) { create(:project) } + let(:environment) { create(:environment, project: project) } + + let!(:deployment) do + create(:deployment, environment: environment, sha: project.commit('master').id) + end + + scenario 'does have a Close link' do + # TODO: Add missing validation. In order to have Close link + # this must be true: last_deployment.try(:close_action) + end end end diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 60bc07bd1a0..2c1a45af596 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -79,6 +79,15 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end + context 'Has Environment' do + let(:environment) { create(:environment, project: project) } + + it 'does show link to close the environment' do + # TODO add test to verify if the button is visible when this condition + # is met: if environment.closeable? + end + end + def visit_merge_request(merge_request) visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) end -- cgit v1.2.1 From 04593581037bca7a7c3b00c719404e610c158cc1 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 19:32:10 +0200 Subject: API: Fix Sytem hooks delete behavior --- doc/api/system_hooks.md | 7 ++----- lib/api/system_hooks.rb | 10 ++++------ spec/requests/api/system_hooks_spec.rb | 7 ++++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index 1802fae14fe..073e99b7147 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -98,11 +98,8 @@ Example response: ## Delete system hook -Deletes a system hook. This is an idempotent API function and returns `200 OK` -even if the hook is not available. - -If the hook is deleted, a JSON object is returned. An error is raised if the -hook is not found. +Deletes a system hook. It returns `200 OK` if the hooks is deleted and +`404 Not Found` if the hook is not found. --- diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 2e76b91051f..794e34874f4 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -56,12 +56,10 @@ module API requires :id, type: Integer, desc: 'The ID of the system hook' end delete ":id" do - begin - hook = SystemHook.find(params[:id]) - present hook.destroy, with: Entities::Hook - rescue - # SystemHook raises an Error if no hook with id found - end + hook = SystemHook.find_by(id: params[:id]) + not_found!('System hook') unless hook + + present hook.destroy, with: Entities::Hook end end end diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 1ce2658569e..f8a1aed5441 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -73,9 +73,10 @@ describe API::API, api: true do end.to change { SystemHook.count }.by(-1) end - it "returns success if hook id not found" do - delete api("/hooks/12345", admin) - expect(response).to have_http_status(200) + it 'returns 404 if the system hook does not exist' do + delete api('/hooks/12345', admin) + + expect(response).to have_http_status(404) end end end -- cgit v1.2.1 From db9c03bf4426346c631a6cd366a84626ead8d6e3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 11:33:24 +0200 Subject: Add environment stop action [ci skip] --- app/controllers/projects/environments_controller.rb | 6 +++++- app/controllers/projects/merge_requests_controller.rb | 1 + app/models/ci/build.rb | 10 ---------- config/routes/project.rb | 6 +++++- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 4fe8c3a1889..2ec316a1ebd 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -2,7 +2,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:edit, :update, :destroy] + before_action :authorize_update_environment!, only: [:edit, :update, :stop, :destroy] before_action :environment, only: [:show, :edit, :update, :destroy] def index @@ -44,6 +44,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController end end + def stop + + end + def destroy if @environment.destroy flash[:notice] = 'Environment was successfully removed.' diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9207c954335..1c1938f957b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -416,6 +416,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController id: environment.id, name: environment.name, url: namespace_project_environment_path(project.namespace, project, environment), + stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.closeable?), external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index fd762b8c5ce..6f3e83976e7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -125,16 +125,6 @@ module Ci end end - def play_type - return nil unless playable? - - if close_environment? - :close - else - :play - end - end - def retryable? project.builds_enabled? && commands.present? && complete? end diff --git a/config/routes/project.rb b/config/routes/project.rb index 2cd8c60794a..d73f76cd091 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -318,7 +318,11 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - resources :environments + resources :environments do + member do + post :stop + end + end resource :cycle_analytics, only: [:show] -- cgit v1.2.1 From 92d12fab66e66358e8fb9204870053e4bab66b71 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 11:44:08 +0100 Subject: Updates MR template to include stop link --- app/assets/javascripts/merge_request_widget.js.es6 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index fcadc4bc515..fb55d13a223 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -17,6 +17,12 @@ View on <%- external_url_formatted %> + + + + Stop environment + + `; @@ -205,6 +211,11 @@ if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); + + if (!environment.stop_url) { + $('.js-close-env-link', $template).remove(); + } + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = $.timeago(environment.deployed_at) + '.'; } else { -- cgit v1.2.1 From 6cdbb27ec3cf72ce6728986909aa3df54b7a26c6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:45:31 +0200 Subject: Refactor code to use available and stopped statuses and refactor views to use separate renders --- .../projects/environments_controller.rb | 8 ++-- .../projects/merge_requests_controller.rb | 2 +- app/models/ci/build.rb | 8 ---- app/models/deployment.rb | 15 ++++--- app/models/environment.rb | 20 +++++----- app/models/project.rb | 2 +- app/services/create_deployment_service.rb | 14 +++---- app/views/projects/deployments/_actions.haml | 46 +++++++--------------- .../projects/deployments/_deployment.html.haml | 4 +- app/views/projects/deployments/_rollback.haml | 6 +++ .../projects/environments/_environment.html.haml | 6 ++- .../projects/environments/_external_url.html.haml | 3 ++ app/views/projects/environments/_stop.html.haml | 5 +++ app/views/projects/environments/index.html.haml | 10 ++--- app/views/projects/environments/show.html.haml | 10 +++-- db/fixtures/development/14_pipelines.rb | 4 +- .../20161006104309_add_state_to_environment.rb | 2 +- .../20161017095000_add_properties_to_deployment.rb | 29 ++++++++++++++ db/schema.rb | 11 +----- 19 files changed, 111 insertions(+), 94 deletions(-) create mode 100644 app/views/projects/deployments/_rollback.haml create mode 100644 app/views/projects/environments/_external_url.html.haml create mode 100644 app/views/projects/environments/_stop.html.haml create mode 100644 db/migrate/20161017095000_add_properties_to_deployment.rb diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 2ec316a1ebd..40da5be2e49 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -10,8 +10,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController @all_environments = project.environments @environments = case @scope - when 'closed' then @all_environments.closed - else @all_environments.opened + when 'stopped' then @all_environments.stopped + else @all_environments.available end end @@ -45,7 +45,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def stop - + action = @environment.stop_action + new_action = action.active? ? action : action.play(current_user) + redirect_to [project.namespace.become(Namespace), project, new_action] end def destroy diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 1c1938f957b..86e12660c23 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -416,7 +416,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController id: environment.id, name: environment.name, url: namespace_project_environment_path(project.namespace, project, environment), - stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.closeable?), + stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.stoppable?), external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6f3e83976e7..87475119b23 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -106,14 +106,6 @@ module Ci project.builds_enabled? && commands.present? && manual? && skipped? end - def close_environment? - options.fetch(:environment, {}).fetch(:close, false) - end - - def closes_environment?(name) - environment == name && close_environment? - end - def play(current_user = nil) # Try to queue a current build if self.enqueue diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 054d54f124e..18d9e96301c 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,6 +11,8 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true + store :properties, accessors: [:on_stop] + after_save :create_ref def commit @@ -34,7 +36,7 @@ class Deployment < ActiveRecord::Base end def manual_actions - deployable.try(:other_actions) + @manual_actions ||= deployable.try(:other_actions) end def includes_commit?(commit) @@ -84,17 +86,14 @@ class Deployment < ActiveRecord::Base take end - def close_action + def stop_action return nil unless manual_actions - @close_action ||= - manual_actions.find do |manual_action| - manual_action.try(:closes_environment?, deployable.environment) - end + @stop_action ||= manual_actions.find_by(name: on_stop) end - def closeable? - close_action.present? + def stoppable? + on_stop.present? && stop_action.present? end def formatted_deployment_time diff --git a/app/models/environment.rb b/app/models/environment.rb index 07f14a7ad8d..93e7dedd6f8 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -19,22 +19,22 @@ class Environment < ActiveRecord::Base allow_nil: true, addressable_url: true - delegate :closeable?, :close_action, to: :last_deployment, allow_nil: true + delegate :stoppable?, :stop_action, to: :last_deployment, allow_nil: true - scope :opened, -> { where(state: [:opened]) } - scope :closed, -> { where(state: [:closed]) } + scope :available, -> { where(state: [:available]) } + scope :stopped, -> { where(state: [:stopped]) } - state_machine :state, initial: :opened do - event :close do - transition opened: :closed + state_machine :state, initial: :available do + event :start do + transition stopped: :available end - event :reopen do - transition closed: :opened + event :stop do + transition available: :stopped end - state :opened - state :closed + state :available + state :stopped end def last_deployment diff --git a/app/models/project.rb b/app/models/project.rb index 2e8073733d4..197edabe821 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1292,7 +1292,7 @@ class Project < ActiveRecord::Base environment_ids.where(ref: ref) end - environments.opened.where(id: environment_ids).select do |environment| + environments.available.where(id: environment_ids).select do |environment| environment.includes_commit?(commit) end end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 923de58b244..47c740addb0 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -8,12 +8,12 @@ class CreateDeploymentService < BaseService @deployable = deployable @environment = prepare_environment - if close? - @environment.close + if stop? + @environment.stop return end - @environment.reopen + @environment.start deploy.tap do |deployment| deployment.update_merge_request_metrics! @@ -61,10 +61,6 @@ class CreateDeploymentService < BaseService options[:url] end - def close? - options[:close] - end - def options params[:options] || {} end @@ -72,4 +68,8 @@ class CreateDeploymentService < BaseService def variables params[:variables] || [] end + + def stop? + params[:options].fetch(:stop, false) + end end diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 387ff018073..58a214bdbd1 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -1,33 +1,15 @@ -- if can?(current_user, :create_deployment, deployment) && deployment.deployable - .pull-right - - - external_url = deployment.environment.external_url - - if external_url - = link_to external_url, target: '_blank', class: 'btn external-url' do - = icon('external-link') - - - actions = deployment.manual_actions - - if actions.present? - .inline - .dropdown - %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} - = custom_icon('icon_play') - = icon('caret-down') - %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 - = custom_icon('icon_play') - %span= action.name.humanize +- if can?(current_user, :create_deployment, deployment) + - actions = deployment.manual_actions + - if actions.present? + .inline + .dropdown + %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} + = custom_icon('icon_play') + = icon('caret-down') + %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 + = custom_icon('icon_play') + %span= action.name.humanize - - if local_assigns.fetch(:allow_close, false) && deployment.closeable? - .inline - = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do - = icon('stop', class: 'close-env-icon') - - - if local_assigns.fetch(:allow_rollback, false) - = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do - - if deployment.last? - Re-deploy - - else - Rollback diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index ca0005abd0c..9238f232c7e 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -17,4 +17,6 @@ #{time_ago_with_tooltip(deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true + .pull-right + = render 'projects/deployments/actions', deployment: deployment + = render 'projects/deployments/rollback', deployment: deployment diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml new file mode 100644 index 00000000000..5941e01c6f1 --- /dev/null +++ b/app/views/projects/deployments/_rollback.haml @@ -0,0 +1,6 @@ +- if can?(current_user, :create_deployment, deployment) && deployment.deployable + = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do + - if deployment.last? + Re-deploy + - else + Rollback diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 19c536897bd..b75d5df4150 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -28,4 +28,8 @@ #{time_ago_with_tooltip(last_deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: last_deployment, allow_close: environment.opened? + .pull-right + = render 'projects/environments/external_url', environment: environment + = render 'projects/deployments/actions', deployment: last_deployment + = render 'projects/environments/stop', environment: environment + = render 'projects/deployments/rollback', deployment: last_deployment diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml new file mode 100644 index 00000000000..6255e4baea0 --- /dev/null +++ b/app/views/projects/environments/_external_url.html.haml @@ -0,0 +1,3 @@ +- if environment.external_url + = link_to environment.external_url, target: '_blank', class: 'btn external-url' do + = icon('external-link') diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml new file mode 100644 index 00000000000..6ed6aee141b --- /dev/null +++ b/app/views/projects/environments/_stop.html.haml @@ -0,0 +1,5 @@ +- if environment.available? && environment.stoppable? + .inline + = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, + class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop', class: 'close-env-icon') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index f0b64a8775b..70185176222 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -8,14 +8,14 @@ %li{class: ('active' if @scope.nil?)} = link_to project_environments_path(@project) do Available - %span.badge.js-avaibale-environments-count - = number_with_delimiter(@all_environments.opened.count) + %span.badge.js-available-environments-count + = number_with_delimiter(@all_environments.available.count) - %li{class: ('active' if @scope == 'closed')} - = link_to project_environments_path(@project, scope: :closed) do + %li{class: ('active' if @scope == 'stopped')} + = link_to project_environments_path(@project, scope: :stopped) do Stopped %span.badge.js-stopped-environments-count - = number_with_delimiter(@all_environments.closed.count) + = number_with_delimiter(@all_environments.stopped.count) .nav-controls - if can?(current_user, :create_environment, @project) && !@all_environments.blank? diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index c4209d499ad..3b4d0395db0 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -2,18 +2,20 @@ - page_title "Environments" = render "projects/pipelines/head" -- last_deployment = @environment.last_deployment - %div{ class: container_class } .top-area .col-md-9 %h3.page-title= @environment.name.capitalize .col-md-3 .nav-controls + - if can?(current_user, :read_environmnet, @environment) + = render 'projects/environments/external_url', environment: @environment + - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - - if @environment.opened? && last_deployment.try(:close_action) - = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + - if @environment.available? && @environment.stoppable? + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete .deployments-container - if @deployments.blank? diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index d3fabe111a1..08ad3097d34 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -16,8 +16,8 @@ class Gitlab::Seeder::Pipelines { name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending }, { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running }, { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled }, - { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success }, - { name: 'close staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped, options: { environment: { close: true } } }, + { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, options: { environment: { on_stop: 'stop staging' } } }, + { name: 'stop staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped }, { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] diff --git a/db/migrate/20161006104309_add_state_to_environment.rb b/db/migrate/20161006104309_add_state_to_environment.rb index 83dff07069f..ccb546654f9 100644 --- a/db/migrate/20161006104309_add_state_to_environment.rb +++ b/db/migrate/20161006104309_add_state_to_environment.rb @@ -6,7 +6,7 @@ class AddStateToEnvironment < ActiveRecord::Migration DOWNTIME = false def up - add_column_with_default(:environments, :state, :string, default: :opened) + add_column_with_default(:environments, :state, :string, default: :available) end def down diff --git a/db/migrate/20161017095000_add_properties_to_deployment.rb b/db/migrate/20161017095000_add_properties_to_deployment.rb new file mode 100644 index 00000000000..6371166a4d2 --- /dev/null +++ b/db/migrate/20161017095000_add_properties_to_deployment.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 AddPropertiesToDeployment < 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 :deployments, :properties, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index ba2cf686fa9..b574f4d4a6a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -398,22 +398,13 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "environments", force: :cascade do |t| -<<<<<<< HEAD - t.integer "project_id" - t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.string "external_url" - t.string "environment_type" - t.string "state", default: "opened", null: false -======= t.integer "project_id" t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" t.string "external_url" t.string "environment_type" ->>>>>>> origin/master + t.string "state", default: "available", null: false end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree -- cgit v1.2.1 From 5f98d059396dc8c0faab4defd0414049c909b6c1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:46:00 +0200 Subject: Add `action` and `on_stop` to `environment` in .gitlab-ci.yml --- app/services/create_deployment_service.rb | 22 +++++++++------------- lib/gitlab/ci/config/node/environment.rb | 14 +++++++++++--- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 47c740addb0..2e859a30cac 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -6,14 +6,12 @@ class CreateDeploymentService < BaseService ActiveRecord::Base.transaction do @deployable = deployable - @environment = prepare_environment + @environment = environment + @environment.external_url = expanded_url if expanded_url + @environment.state_event = action + @environment.save - if stop? - @environment.stop - return - end - - @environment.start + return if @environment.stopped? deploy.tap do |deployment| deployment.update_merge_request_metrics! @@ -37,10 +35,8 @@ class CreateDeploymentService < BaseService deployable: @deployable) end - def prepare_environment - project.environments.find_or_create_by(name: expanded_name) do |environment| - environment.external_url = expanded_url - end + def environment + @environment ||= project.environments.find_or_create_by(name: expanded_name) end def expanded_name @@ -69,7 +65,7 @@ class CreateDeploymentService < BaseService params[:variables] || [] end - def stop? - params[:options].fetch(:stop, false) + def action + params[:options].fetch(:action, 'start') end end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index daa115f9017..1c1d07843b1 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -8,7 +8,7 @@ module Gitlab class Environment < Entry include Validatable - ALLOWED_KEYS = %i[name url close] + ALLOWED_KEYS = %i[name url action on_stop] validations do validate do @@ -36,7 +36,11 @@ module Gitlab addressable_url: true, allow_nil: true - validates :close, boolean: true, allow_nil: true + validates :action, + inclusion: { in: %w[start stop], message: 'should be start or stop, ' }, + allow_nil: true + + validates :on_stop, string: true, allow_nil: true end end @@ -56,9 +60,13 @@ module Gitlab value[:url] end + def action + value[:action] || 'start' + end + def value case @config - when String then { name: @config } + when String then { name: @config, action: 'start' } when Hash then @config else {} end -- cgit v1.2.1 From 021263916c3c1560a461ab0d519a309a6f918800 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:48:19 +0200 Subject: Save `on_stop` in deployment --- app/services/create_deployment_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 2e859a30cac..5e6745cdd4e 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -32,7 +32,8 @@ class CreateDeploymentService < BaseService tag: params[:tag], sha: params[:sha], user: current_user, - deployable: @deployable) + deployable: @deployable, + on_stop: options.fetch(:on_stop, nil)) end def environment -- cgit v1.2.1 From a8cc76e2a82ed225b246a31d45d41d5ca52a529e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 11:48:56 +0100 Subject: Renames button to show 'Stop' instead of 'Close' --- app/views/projects/environments/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index c4209d499ad..6507768ddc5 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -13,7 +13,7 @@ - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - if @environment.opened? && last_deployment.try(:close_action) - = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Stop', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post .deployments-container - if @deployments.blank? -- cgit v1.2.1 From 53d79469b0dd2b3c054dabe685231747352aee9f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:51:37 +0200 Subject: Update `db/schema.rb` --- db/schema.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index b574f4d4a6a..f568a5ec699 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: 20161017095000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -380,6 +380,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" + t.text "properties" end add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree -- cgit v1.2.1 From 18bb0a5696cd701d3a77c059fabb5d2f798f3a83 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 15:34:30 +0200 Subject: Add on_stop column [ci skip] --- app/models/deployment.rb | 2 -- .../20161017095000_add_properties_to_deployment.rb | 22 +--------------------- db/schema.rb | 2 +- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 18d9e96301c..f6cccae4334 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,8 +11,6 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true - store :properties, accessors: [:on_stop] - after_save :create_ref def commit diff --git a/db/migrate/20161017095000_add_properties_to_deployment.rb b/db/migrate/20161017095000_add_properties_to_deployment.rb index 6371166a4d2..f620ee0de1c 100644 --- a/db/migrate/20161017095000_add_properties_to_deployment.rb +++ b/db/migrate/20161017095000_add_properties_to_deployment.rb @@ -1,29 +1,9 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class AddPropertiesToDeployment < 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 :deployments, :properties, :text + add_column :deployments, :on_stop, :string end end diff --git a/db/schema.rb b/db/schema.rb index f568a5ec699..d4e35626e9b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -380,7 +380,7 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" - t.text "properties" + t.string "on_stop" end add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree -- cgit v1.2.1 From 50d3cc2b677dac855a6270f5ffed7085df8edcf8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 15:40:18 +0200 Subject: Remove destroy from environments [ci skip] --- app/controllers/projects/environments_controller.rb | 16 +++------------- config/routes/project.rb | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 40da5be2e49..efdfbd24cae 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -2,8 +2,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:edit, :update, :stop, :destroy] - before_action :environment, only: [:show, :edit, :update, :destroy] + before_action :authorize_update_environment!, only: [:edit, :update, :stop] + before_action :environment, only: [:show, :edit, :update, :stop] def index @scope = params[:scope] @@ -47,17 +47,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController def stop action = @environment.stop_action new_action = action.active? ? action : action.play(current_user) - redirect_to [project.namespace.become(Namespace), project, new_action] - end - - def destroy - if @environment.destroy - flash[:notice] = 'Environment was successfully removed.' - else - flash[:alert] = 'Failed to remove environment.' - end - - redirect_to namespace_project_environments_path(project.namespace, project) + redirect_to [project.namespace.becomes(Namespace), project, new_action] end private diff --git a/config/routes/project.rb b/config/routes/project.rb index d73f76cd091..5e01d9a9e8f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -318,7 +318,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - resources :environments do + resources :environments, except: [:destroy] do member do post :stop end -- cgit v1.2.1 From 409fee3300e387a781008155b6a4682098fb3d68 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 14:51:33 +0100 Subject: Updates close to say stop in confirm message --- app/assets/javascripts/merge_request_widget.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index fb55d13a223..639859ab96f 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -18,7 +18,7 @@ - + Stop environment -- cgit v1.2.1 From f7f6e0c07f6066d1c7d5d7d467e5200453e2a233 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 14:54:27 +0100 Subject: Adds entry to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38765b347d0..15699bcf273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling + - Delete dynamic environments (aka Review Apps) - Make guests unable to view MRs on private projects ## 8.12.7 -- cgit v1.2.1 From b8fd2dddb29d0b3d2a117c7234e19ce25ced168c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 15:08:01 +0100 Subject: Changes after review --- app/views/projects/environments/_stop.html.haml | 2 +- app/views/projects/environments/index.html.haml | 6 +++--- app/views/projects/environments/show.html.haml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index 6ed6aee141b..ccda591590a 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,5 +1,5 @@ - if environment.available? && environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, - class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do = icon('stop', class: 'close-env-icon') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 70185176222..eeac6e3be1a 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -16,9 +16,9 @@ Stopped %span.badge.js-stopped-environments-count = number_with_delimiter(@all_environments.stopped.count) - - .nav-controls - - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + + - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 3b4d0395db0..d765ddb97ce 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -14,7 +14,7 @@ - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - if @environment.available? && @environment.stoppable? - = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete .deployments-container -- cgit v1.2.1 From 9b790f1cf97157240178601c62d2e557a404503e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 16:13:19 +0200 Subject: Improve after code review --- app/assets/javascripts/merge_request_widget.js.es6 | 2 +- .../projects/environments_controller.rb | 11 ++-- app/models/deployment.rb | 3 +- app/models/environment.rb | 9 ++-- app/services/create_deployment_service.rb | 5 +- app/views/projects/environments/_stop.html.haml | 4 +- app/views/projects/environments/index.html.haml | 4 +- app/views/projects/environments/show.html.haml | 5 +- lib/gitlab/ci/config/node/environment.rb | 4 +- spec/lib/gitlab/ci/config/node/environment_spec.rb | 62 ++++++++++++++++++++++ 10 files changed, 86 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index fb55d13a223..639859ab96f 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -18,7 +18,7 @@ - + Stop environment diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index efdfbd24cae..86bc17a720a 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -8,11 +8,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController def index @scope = params[:scope] @all_environments = project.environments - @environments = - case @scope - when 'stopped' then @all_environments.stopped - else @all_environments.available - end + @environments = @scope == 'stopped' ? + @all_environments.stopped : @all_environments.available end def show @@ -45,9 +42,11 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def stop + return render_404 unless @environment.stoppable? + action = @environment.stop_action new_action = action.active? ? action : action.play(current_user) - redirect_to [project.namespace.becomes(Namespace), project, new_action] + redirect_to polymorphic_path([project.namespace.becomes(Namespace), project, new_action]) end private diff --git a/app/models/deployment.rb b/app/models/deployment.rb index f6cccae4334..1f8c5fb3d85 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -85,13 +85,14 @@ class Deployment < ActiveRecord::Base end def stop_action + return nil unless on_stop.present? return nil unless manual_actions @stop_action ||= manual_actions.find_by(name: on_stop) end def stoppable? - on_stop.present? && stop_action.present? + stop_action.present? end def formatted_deployment_time diff --git a/app/models/environment.rb b/app/models/environment.rb index 93e7dedd6f8..20da71ccb3f 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -19,10 +19,7 @@ class Environment < ActiveRecord::Base allow_nil: true, addressable_url: true - delegate :stoppable?, :stop_action, to: :last_deployment, allow_nil: true - - scope :available, -> { where(state: [:available]) } - scope :stopped, -> { where(state: [:stopped]) } + delegate :stop_action, to: :last_deployment, allow_nil: true state_machine :state, initial: :available do event :start do @@ -84,4 +81,8 @@ class Environment < ActiveRecord::Base external_url.gsub(/\A.*?:\/\//, '') end + + def stoppable? + available? && stop_action.present? + end end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 5e6745cdd4e..85c0bf72074 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -6,10 +6,11 @@ class CreateDeploymentService < BaseService ActiveRecord::Base.transaction do @deployable = deployable + @environment = environment @environment.external_url = expanded_url if expanded_url @environment.state_event = action - @environment.save + @environment.save! return if @environment.stopped? @@ -33,7 +34,7 @@ class CreateDeploymentService < BaseService sha: params[:sha], user: current_user, deployable: @deployable, - on_stop: options.fetch(:on_stop, nil)) + on_stop: options[:on_stop]) end def environment diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index 6ed6aee141b..c7dec086890 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,5 +1,5 @@ -- if environment.available? && environment.stoppable? +- if environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, - class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do = icon('stop', class: 'close-env-icon') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 70185176222..705a1360ec5 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -17,8 +17,8 @@ %span.badge.js-stopped-environments-count = number_with_delimiter(@all_environments.stopped.count) - .nav-controls - - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 3b4d0395db0..b6a1a7fc89e 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -13,9 +13,8 @@ - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - - if @environment.available? && @environment.stoppable? - = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete + - if @environment.stoppable? + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post .deployments-container - if @deployments.blank? diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index 1c1d07843b1..b392f272bd6 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -37,10 +37,10 @@ module Gitlab allow_nil: true validates :action, - inclusion: { in: %w[start stop], message: 'should be start or stop, ' }, + inclusion: { in: %w[start stop], message: 'should be start or stop' }, allow_nil: true - validates :on_stop, string: true, allow_nil: true + validates :on_stop, type: String, allow_nil: true end end diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb index df453223da7..430e18a816f 100644 --- a/spec/lib/gitlab/ci/config/node/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -87,6 +87,68 @@ describe Gitlab::Ci::Config::Node::Environment do end end + context 'when valid action is used' do + let(:config) do + { name: 'production', + action: 'start' } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + + context 'when invalid action is used' do + let(:config) do + { name: 'production', + action: false } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about invalid action' do + expect(entry.errors) + .to include 'environment action should be start or stop' + end + end + end + + context 'when on_stop is used' do + let(:config) do + { name: 'production', + on_stop: 'close_app' } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + + context 'when invalid on_stop is used' do + let(:config) do + { name: 'production', + on_stop: false } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about invalid action' do + expect(entry.errors) + .to include 'environment action should be start or stop' + end + end + end + context 'when variables are used for environment' do let(:config) do { name: 'review/$CI_BUILD_REF_NAME', -- cgit v1.2.1 From 25dd1712ca01d017fd3b9c2d230a62e82444b5a1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 16:23:17 +0200 Subject: Add specs to test on_stop and action on environments --- lib/gitlab/ci/config/node/environment.rb | 4 ++++ spec/lib/gitlab/ci/config/node/environment_spec.rb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index b392f272bd6..9a95ef43628 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -64,6 +64,10 @@ module Gitlab value[:action] || 'start' end + def on_stop + value[:on_stop] + end + def value case @config when String then { name: @config, action: 'start' } diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb index 430e18a816f..dbeb28c8aad 100644 --- a/spec/lib/gitlab/ci/config/node/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -101,7 +101,7 @@ describe Gitlab::Ci::Config::Node::Environment do context 'when invalid action is used' do let(:config) do { name: 'production', - action: false } + action: 'invalid' } end describe '#valid?' do @@ -144,7 +144,7 @@ describe Gitlab::Ci::Config::Node::Environment do describe '#errors' do it 'contains error about invalid action' do expect(entry.errors) - .to include 'environment action should be start or stop' + .to include 'environment on stop should be a string' end end end -- cgit v1.2.1 From 4a369185d77013b2138f2daf6d85b1358425e75c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 17:10:26 +0200 Subject: Work on specs --- app/services/create_deployment_service.rb | 4 +- spec/models/deployment_spec.rb | 46 +++++++++++++++++++ spec/models/environment_spec.rb | 32 +++++++++++++ spec/services/create_deployment_service_spec.rb | 61 ++++++++++++++++++++++++- 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 85c0bf72074..c55d2ed231a 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -9,7 +9,7 @@ class CreateDeploymentService < BaseService @environment = environment @environment.external_url = expanded_url if expanded_url - @environment.state_event = action + @environment.fire_state_event(action) @environment.save! return if @environment.stopped? @@ -68,6 +68,6 @@ class CreateDeploymentService < BaseService end def action - params[:options].fetch(:action, 'start') + options[:action] || 'start' end end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 01a4a53a264..ca594a320c0 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -48,4 +48,50 @@ describe Deployment, models: true do end end end + + describe '#stop_action' do + let(:build) { create(:ci_build) } + + subject { deployment.stop_action } + + context 'when no other actions' do + let(:deployment) { FactoryGirl.build(:deployment, deployable: build) } + + it { is_expected.to be_nil } + end + + context 'with other actions' do + let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + + context 'when matching action is defined' do + let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_other_app') } + + it { is_expected.to be_nil } + end + + context 'when no matching action is defined' do + let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') } + + it { is_expected.to eq(close_action) } + end + end + end + + describe '#stoppable?' do + subject { deployment.stoppable? } + + context 'when no other actions' do + let(:deployment) { build(:deployment) } + + it { is_expected.to be_falsey } + end + + context 'when matching action is defined' do + let(:build) { create(:ci_build) } + let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') } + let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index e172ee8e590..b019f2ddb77 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -8,6 +8,8 @@ describe Environment, models: true do it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) } + it { is_expected.to delegate_method(:stop_action).to(:last_deployment).as(:last) } + it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } it { is_expected.to validate_length_of(:name).is_within(0..255) } @@ -96,4 +98,34 @@ describe Environment, models: true do is_expected.to be_nil end end + + describe '#stoppable?' do + subject { environment.stoppable? } + + context 'when no other actions' do + it { is_expected.to be_falsey } + end + + context 'when matching action is defined' do + let(:build) { create(:ci_build) } + let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + + context 'when environment is available' do + before do + environment.start + end + + it { is_expected.to be_truthy } + end + + context 'when environment is stopped' do + before do + environment.stop + end + + it { is_expected.to be_falsey } + end + end + end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 5fe56e7725f..d4ceb83caf8 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -7,11 +7,13 @@ describe CreateDeploymentService, services: true do let(:service) { described_class.new(project, user, params) } describe '#execute' do + let(:options) { nil } let(:params) do { environment: 'production', ref: 'master', tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', + options: options } end @@ -28,7 +30,7 @@ describe CreateDeploymentService, services: true do end context 'when environment exist' do - before { create(:environment, project: project, name: 'production') } + let!(:environment) { create(:environment, project: project, name: 'production') } it 'does not create a new environment' do expect { subject }.not_to change { Environment.count } @@ -37,6 +39,46 @@ describe CreateDeploymentService, services: true do it 'does create a deployment' do expect(subject).to be_persisted end + + context 'and start action is defined' do + let(:options) { { action: 'start' } } + + context 'and environment is stopped' do + before do + environment.stop + end + + it 'makes environment available' do + subject + + expect(environment.reload).to be_available + end + + it 'does not create a deployment' do + expect(subject).not_to be_persisted + end + end + end + + context 'and stop action is defined' do + let(:options) { { action: 'stop' } } + + context 'and environment is available' do + before do + environment.start + end + + it 'makes environment stopped' do + subject + + expect(environment.reload).to be_stopped + end + + it 'does not create a deployment' do + expect(subject).to be_nil + end + end + end end context 'for environment with invalid name' do @@ -83,6 +125,23 @@ describe CreateDeploymentService, services: true do it 'does create a new deployment' do expect(subject).to be_persisted end + + context 'and environment exist' do + it 'does not create a new environment' do + expect { subject }.not_to change { Environment.count } + end + + it 'updates external url' do + subject + + expect(subject.environment.name).to eq('review-apps/feature-review-apps') + expect(subject.environment.external_url).to eq('http://feature-review-apps.review-apps.gitlab.com') + end + + it 'does create a new deployment' do + expect(subject).to be_persisted + end + end end context 'when project was removed' do -- cgit v1.2.1 From e9880722076081df1576a3fb01e2c30feb6208ba Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 21:06:10 +0200 Subject: Fix environments specs --- .../projects/environments_controller.rb | 5 +- app/models/environment.rb | 3 + app/services/create_deployment_service.rb | 2 +- .../projects/environments/_external_url.html.haml | 2 +- app/views/projects/environments/_stop.html.haml | 2 +- app/views/projects/environments/show.html.haml | 8 +-- spec/features/environments_spec.rb | 76 ++++++++++++++++------ spec/services/create_deployment_service_spec.rb | 8 ++- 8 files changed, 74 insertions(+), 32 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 86bc17a720a..02a659d3894 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,8 +1,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! - before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:edit, :update, :stop] + before_action :authorize_create_environment!, only: [:new, :create, :stop] + before_action :authorize_create_deployment!, only: [:stop] + before_action :authorize_update_environment!, only: [:edit, :update] before_action :environment, only: [:show, :edit, :update, :stop] def index diff --git a/app/models/environment.rb b/app/models/environment.rb index 20da71ccb3f..ff55e751f70 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -21,6 +21,9 @@ class Environment < ActiveRecord::Base delegate :stop_action, to: :last_deployment, allow_nil: true + scope :available, -> { with_state(:available) } + scope :stopped, -> { with_state(:stopped) } + state_machine :state, initial: :available do event :start do transition stopped: :available diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index c55d2ed231a..8ae15ad32f4 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -10,8 +10,8 @@ class CreateDeploymentService < BaseService @environment = environment @environment.external_url = expanded_url if expanded_url @environment.fire_state_event(action) - @environment.save! + return unless @environment.save return if @environment.stopped? deploy.tap do |deployment| diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml index 6255e4baea0..4c8fe1c271b 100644 --- a/app/views/projects/environments/_external_url.html.haml +++ b/app/views/projects/environments/_external_url.html.haml @@ -1,3 +1,3 @@ -- if environment.external_url +- if environment.external_url && can?(current_user, :read_environment, environment) = link_to environment.external_url, target: '_blank', class: 'btn external-url' do = icon('external-link') diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index c7dec086890..880f8c8c62c 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,4 +1,4 @@ -- if environment.stoppable? +- if can?(current_user, :create_deployment, environment) && environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index b6a1a7fc89e..bf082a05c39 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -8,13 +8,11 @@ %h3.page-title= @environment.name.capitalize .col-md-3 .nav-controls - - if can?(current_user, :read_environmnet, @environment) - = render 'projects/environments/external_url', environment: @environment - + = render 'projects/environments/external_url', environment: @environment - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - - if @environment.stoppable? - = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post + - if can?(current_user, :create_deployment, @environment) && @environment.stoppable? + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post .deployments-container - if @deployments.blank? diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 35f5706f920..1b51bf9ef66 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -35,7 +35,7 @@ feature 'Environments', feature: true do end scenario 'does show 0 as counter for environments in both tabs' do - expect(page.find('.js-avaibale-environments-count').text).to eq('0') + expect(page.find('.js-available-environments-count').text).to eq('0') expect(page.find('.js-stopped-environments-count').text).to eq('0') end end @@ -48,7 +48,7 @@ feature 'Environments', feature: true do end scenario 'does show number of opened environments in Availabe tab' do - expect(page.find('.js-avaibale-environments-count').text).to eq('1') + expect(page.find('.js-available-environments-count').text).to eq('1') end scenario 'does show number of closed environments in Stopped tab' do @@ -92,6 +92,14 @@ feature 'Environments', feature: true do scenario 'does show build name and id' do expect(page).to have_link("#{build.name} (##{build.id})") end + + scenario 'does not show stop button' do + expect(page).not_to have_selector('.close-env-link') + end + + scenario 'does not show external link button' do + expect(page).not_to have_css('external-url') + end context 'with external_url' do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } @@ -103,14 +111,27 @@ feature 'Environments', feature: true do end end - scenario 'does show close button' do - # TODO: Add test to verify if close button is visible - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? - end - - scenario 'does allow to close environment' do - # TODO: Add test to verify if close environment works - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + context 'with stop action' do + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + scenario 'does show stop button' do + expect(page).to have_selector('.close-env-link') + end + + scenario 'does allow to stop environment' do + first('.close-env-link').click + + expect(page).to have_content('close_app') + end + + context 'for reporter' do + let(:role) { :reporter } + + scenario 'does not show stop button' do + expect(page).not_to have_selector('.close-env-link') + end + end end end end @@ -160,6 +181,10 @@ feature 'Environments', feature: true do expect(page).to have_link('Re-deploy') end + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop') + end + context 'with manual action' do given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } @@ -178,20 +203,33 @@ feature 'Environments', feature: true do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } given(:build) { create(:ci_build, pipeline: pipeline) } given(:deployment) { create(:deployment, environment: environment, deployable: build) } - + scenario 'does show an external link button' do expect(page).to have_link(nil, href: environment.external_url) end end - scenario 'does show close button' do - # TODO: Add test to verify if close button is visible - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? - end - - scenario 'does allow to close environment' do - # TODO: Add test to verify if close environment works - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + context 'with stop action' do + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + scenario 'does show stop button' do + expect(page).to have_link('Stop') + end + + scenario 'does allow to stop environment' do + click_link('Stop') + + expect(page).to have_content('close_app') + end + + context 'for reporter' do + let(:role) { :reporter } + + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop') + end + end end end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index d4ceb83caf8..5a4562b939b 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -54,8 +54,8 @@ describe CreateDeploymentService, services: true do expect(environment.reload).to be_available end - it 'does not create a deployment' do - expect(subject).not_to be_persisted + it 'does create a deployment' do + expect(subject).to be_persisted end end end @@ -95,7 +95,7 @@ describe CreateDeploymentService, services: true do end it 'does not create a deployment' do - expect(subject).not_to be_persisted + expect(subject).to be_nil end end @@ -127,6 +127,8 @@ describe CreateDeploymentService, services: true do end context 'and environment exist' do + let!(:environment) { create(:environment, project: project, name: 'review-apps/feature-review-apps') } + it 'does not create a new environment' do expect { subject }.not_to change { Environment.count } end -- cgit v1.2.1 From 34e19b9b8dccd7cd2e6c2bb408e75c70f3b6f3b9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 22:01:56 +0200 Subject: Fix specs --- .../projects/environments_controller.rb | 2 +- spec/features/environments_spec.rb | 42 +------------------ .../merge_when_build_succeeds_spec.rb | 9 ----- .../merge_requests/widget_deployments_spec.rb | 47 ++++++++++++++++++---- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 02a659d3894..e243253c5f1 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,7 +1,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! - before_action :authorize_create_environment!, only: [:new, :create, :stop] + before_action :authorize_create_environment!, only: [:new, :create] before_action :authorize_create_deployment!, only: [:stop] before_action :authorize_update_environment!, only: [:edit, :update] before_action :environment, only: [:show, :edit, :update, :stop] diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 1b51bf9ef66..16c44e42f63 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -119,7 +119,7 @@ feature 'Environments', feature: true do expect(page).to have_selector('.close-env-link') end - scenario 'does allow to stop environment' do + scenario 'starts build when stop button clicked' do first('.close-env-link').click expect(page).to have_content('close_app') @@ -217,7 +217,7 @@ feature 'Environments', feature: true do expect(page).to have_link('Stop') end - scenario 'does allow to stop environment' do + scenario ' scenario 'does allow to stop environment' do' do click_link('Stop') expect(page).to have_content('close_app') @@ -277,42 +277,4 @@ feature 'Environments', feature: true do end end end - - describe 'when deleting existing environment' do - given(:environment) { create(:environment, project: project) } - - before do - visit namespace_project_environment_path(project.namespace, project, environment) - end - - context 'when logged as master' do - given(:role) { :master } - - scenario 'does not have a Close link' do - expect(page).not_to have_link('Close') - end - - context 'when environment is opened and can be closed' do - let(:project) { create(:project) } - let(:environment) { create(:environment, project: project) } - - let!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) - end - - scenario 'does have a Close link' do - # TODO: Add missing validation. In order to have Close link - # this must be true: last_deployment.try(:close_action) - end - end - end - - context 'when logged as developer' do - given(:role) { :developer } - - scenario 'does not have a Close link' do - expect(page).not_to have_link('Close') - end - end - end end diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 5d6ce6e1830..c3c3ab33872 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -101,15 +101,6 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).not_to have_link "Merge When Build Succeeds" end end - - context 'Has Environment' do - let(:environment) { create(:environment, project: project) } - - it 'does show link to close the environment' do - # TODO add test to verify if the button is visible when this condition - # is met: if environment.closeable? - end - end def visit_merge_request(merge_request) visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 8e23ec50d4a..0c8ee844b47 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -4,23 +4,56 @@ feature 'Widget Deployments Header', feature: true, js: true do include WaitForAjax describe 'when deployed to an environment' do - let(:project) { merge_request.target_project } - let(:merge_request) { create(:merge_request, :merged) } - let(:environment) { create(:environment, project: project) } - let!(:deployment) do + given(:user) { create(:user) } + given(:project) { merge_request.target_project } + given(:merge_request) { create(:merge_request, :merged) } + given(:environment) { create(:environment, project: project) } + given(:role) { :developer } + given!(:deployment) do create(:deployment, environment: environment, sha: project.commit('master').id) end + given!(:manual) { } - before do - login_as :admin + background do + login_as(user) + project.team << [user, role] visit namespace_project_merge_request_path(project.namespace, project, merge_request) end - it 'displays that the environment is deployed' do + scenario 'displays that the environment is deployed' do wait_for_ajax expect(page).to have_content("Deployed to #{environment.name}") expect(find('.ci_widget > span > span')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) end + + context 'with stop action' do + given(:pipeline) { create(:ci_pipeline, project: project) } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + background do + wait_for_ajax + end + + scenario 'does show stop button' do + expect(page).to have_link('Stop environment') + end + + scenario 'does start build when stop button clicked' do + click_link('Stop environment') + + expect(page).to have_content('close_app') + end + + context 'for reporter' do + given(:role) { :reporter } + + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop environment') + end + end + end end end -- cgit v1.2.1 From c3cf103fee47c056c7258d2921a34cf68a02022c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 22:10:43 +0100 Subject: Updates class name for consistency --- app/assets/javascripts/merge_request_widget.js.es6 | 4 ++-- app/assets/stylesheets/pages/environments.scss | 4 ++-- app/assets/stylesheets/pages/merge_requests.scss | 2 +- app/views/projects/environments/_stop.html.haml | 4 ++-- spec/features/environments_spec.rb | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 639859ab96f..3ff6851d59b 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -17,7 +17,7 @@ View on <%- external_url_formatted %> - + Stop environment @@ -213,7 +213,7 @@ if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); if (!environment.stop_url) { - $('.js-close-env-link', $template).remove(); + $('.js-stop-env-link', $template).remove(); } if (environment.deployed_at && environment.deployed_at_formatted) { diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 9d3492abfb5..12ee0a5dc3d 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -38,10 +38,10 @@ color: $gl-dark-link-color; } - .close-env-link { + .stop-env-link { color: $table-text-gray; - .close-env-icon { + .stop-env-icon { font-size: 14px; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f14f13494e0..e27a82ee5f1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -184,7 +184,7 @@ float: right; } - .close-env-container { + .stop-env-container { color: $gl-text-color; float: right; diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index 880f8c8c62c..69848123c17 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,5 +1,5 @@ - if can?(current_user, :create_deployment, environment) && environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, - class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do - = icon('stop', class: 'close-env-icon') + class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do + = icon('stop', class: 'stop-env-icon') diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 16c44e42f63..c4f230d2007 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -94,7 +94,7 @@ feature 'Environments', feature: true do end scenario 'does not show stop button' do - expect(page).not_to have_selector('.close-env-link') + expect(page).not_to have_selector('.stop-env-link') end scenario 'does not show external link button' do @@ -116,11 +116,11 @@ feature 'Environments', feature: true do given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } scenario 'does show stop button' do - expect(page).to have_selector('.close-env-link') + expect(page).to have_selector('.stop-env-link') end scenario 'starts build when stop button clicked' do - first('.close-env-link').click + first('.stop-env-link').click expect(page).to have_content('close_app') end @@ -129,7 +129,7 @@ feature 'Environments', feature: true do let(:role) { :reporter } scenario 'does not show stop button' do - expect(page).not_to have_selector('.close-env-link') + expect(page).not_to have_selector('.stop-env-link') end end end -- cgit v1.2.1 From 5a46e22a6521af8058078e00a8e6f2252b21e8f7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 22:12:26 +0100 Subject: Fixes typo --- spec/features/environments_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index c4f230d2007..7f67ff7df92 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -47,7 +47,7 @@ feature 'Environments', feature: true do expect(page).to have_link(environment.name) end - scenario 'does show number of opened environments in Availabe tab' do + scenario 'does show number of opened environments in Available tab' do expect(page.find('.js-available-environments-count').text).to eq('1') end -- cgit v1.2.1 From c4e0c051251a37b9afc5630b65701ae5a28de15b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 22:39:52 +0100 Subject: Fixes broken test --- spec/features/environments_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 7f67ff7df92..6cc13290f2c 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -217,7 +217,7 @@ feature 'Environments', feature: true do expect(page).to have_link('Stop') end - scenario ' scenario 'does allow to stop environment' do' do + scenario 'does allow to stop environment' do click_link('Stop') expect(page).to have_content('close_app') -- cgit v1.2.1 From bebfceb1df03e8afa10af5aead8e657654a14f01 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 23:40:44 +0200 Subject: Fix specs --- spec/features/environments_spec.rb | 18 +++++++++--------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/environment_spec.rb | 2 +- spec/models/environment_spec.rb | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 7f67ff7df92..7200e9ad4c1 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -18,7 +18,7 @@ feature 'Environments', feature: true do before do visit namespace_project_environments_path(project.namespace, project) end - + context 'shows two tabs' do scenario 'does show Available tab with link' do expect(page).to have_link('Available') @@ -33,7 +33,7 @@ feature 'Environments', feature: true do scenario 'does show no environments' do expect(page).to have_content('You don\'t have any environments right now.') end - + scenario 'does show 0 as counter for environments in both tabs' do expect(page.find('.js-available-environments-count').text).to eq('0') expect(page.find('.js-stopped-environments-count').text).to eq('0') @@ -46,7 +46,7 @@ feature 'Environments', feature: true do scenario 'does show environment name' do expect(page).to have_link(environment.name) end - + scenario 'does show number of opened environments in Available tab' do expect(page.find('.js-available-environments-count').text).to eq('1') end @@ -67,7 +67,7 @@ feature 'Environments', feature: true do scenario 'does show deployment SHA' do expect(page).to have_link(deployment.short_sha) end - + scenario 'does show deployment internal id' do expect(page).to have_content(deployment.iid) end @@ -88,7 +88,7 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end - + scenario 'does show build name and id' do expect(page).to have_link("#{build.name} (##{build.id})") end @@ -100,12 +100,12 @@ feature 'Environments', feature: true do scenario 'does not show external link button' do expect(page).not_to have_css('external-url') end - + context 'with external_url' do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } given(:build) { create(:ci_build, pipeline: pipeline) } given(:deployment) { create(:deployment, environment: environment, deployable: build) } - + scenario 'does show an external link button' do expect(page).to have_link(nil, href: environment.external_url) end @@ -198,7 +198,7 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end - + context 'with external_url' do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } given(:build) { create(:ci_build, pipeline: pipeline) } @@ -217,7 +217,7 @@ feature 'Environments', feature: true do expect(page).to have_link('Stop') end - scenario ' scenario 'does allow to stop environment' do' do + scenario 'does allow to stop environment' do click_link('Stop') expect(page).to have_content('close_app') diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 6dedd25e9d3..a46ff07f625 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -754,7 +754,7 @@ module Ci it 'does return production' do expect(builds.size).to eq(1) expect(builds.first[:environment]).to eq(environment) - expect(builds.first[:options]).to include(environment: { name: environment }) + expect(builds.first[:options]).to include(environment: { name: environment, action: "start" }) end end diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb index dbeb28c8aad..df925ff1afd 100644 --- a/spec/lib/gitlab/ci/config/node/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::Ci::Config::Node::Environment do describe '#value' do it 'returns valid hash' do - expect(entry.value).to eq(name: 'production') + expect(entry.value).to include(name: 'production') end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index b019f2ddb77..1c1fef57fc4 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -8,7 +8,7 @@ describe Environment, models: true do it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) } - it { is_expected.to delegate_method(:stop_action).to(:last_deployment).as(:last) } + it { is_expected.to delegate_method(:stop_action).to(:last_deployment) } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } -- cgit v1.2.1 From 829a708a970b31afdcda21fff072eda0c61dfd4c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 11:22:22 +0200 Subject: Fix remaining specs failures --- app/controllers/projects/merge_requests_controller.rb | 7 ++++++- app/views/projects/environments/index.html.haml | 2 +- app/views/projects/environments/show.html.haml | 2 +- spec/features/merge_requests/widget_deployments_spec.rb | 10 ++++++---- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 86e12660c23..7bac0a2b1c7 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -412,11 +412,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController project = environment.project deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + stop_url = + if environment.stoppable? && can?(current_user, :create_deployment, environment) + stop_namespace_project_environment_path(project.namespace, project, environment) + end + { id: environment.id, name: environment.name, url: namespace_project_environment_path(project.namespace, project, environment), - stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.stoppable?), + stop_url: stop_url, external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 705a1360ec5..8f555afcf11 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -23,7 +23,7 @@ New environment .environments-container - - if @environments.blank? + - if @all_environments.blank? .blank-state.blank-state-no-icon %h2.blank-state-title You don't have any environments right now. diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index bf082a05c39..bcac73d3698 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -3,7 +3,7 @@ = render "projects/pipelines/head" %div{ class: container_class } - .top-area + .top-area.adjust .col-md-9 %h3.page-title= @environment.name.capitalize .col-md-3 diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 0c8ee844b47..1be35b27d84 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -9,9 +9,8 @@ feature 'Widget Deployments Header', feature: true, js: true do given(:merge_request) { create(:merge_request, :merged) } given(:environment) { create(:environment, project: project) } given(:role) { :developer } - given!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) - end + given(:sha) { project.commit('master').id } + given!(:deployment) { create(:deployment, environment: environment, sha: sha) } given!(:manual) { } background do @@ -31,7 +30,10 @@ feature 'Widget Deployments Header', feature: true, js: true do given(:pipeline) { create(:ci_pipeline, project: project) } given(:build) { create(:ci_build, pipeline: pipeline) } given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } - given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + given(:deployment) do + create(:deployment, environment: environment, ref: merge_request.target_branch, + sha: sha, deployable: build, on_stop: 'close_app') + end background do wait_for_ajax -- cgit v1.2.1 From 0aa232704c5df68f0ed111e355a07cfaf241e8a9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 12:02:50 +0200 Subject: Add `stop!` to `environment` --- .../projects/environments_controller.rb | 11 ++++--- app/models/environment.rb | 6 ++++ spec/models/environment_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index e243253c5f1..5837f913d16 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -9,8 +9,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController def index @scope = params[:scope] @all_environments = project.environments - @environments = @scope == 'stopped' ? - @all_environments.stopped : @all_environments.available + @environments = + if @scope == 'stopped' then + @all_environments.stopped + else + @all_environments.available + end end def show @@ -45,8 +49,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController def stop return render_404 unless @environment.stoppable? - action = @environment.stop_action - new_action = action.active? ? action : action.play(current_user) + new_action = @environment.stop!(current_user) redirect_to polymorphic_path([project.namespace.becomes(Namespace), project, new_action]) end diff --git a/app/models/environment.rb b/app/models/environment.rb index ff55e751f70..d575f1dc73a 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -88,4 +88,10 @@ class Environment < ActiveRecord::Base def stoppable? available? && stop_action.present? end + + def stop!(current_user) + return unless stoppable? + + stop_action.play(current_user) + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 1c1fef57fc4..e1755f940b3 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -128,4 +128,41 @@ describe Environment, models: true do end end end + + describe '#stop!' do + let(:user) { create(:user) } + + subject { environment.stop!(user) } + + before do + expect(environment).to receive(:stoppable?).and_call_original + end + + context 'when no other actions' do + it { is_expected.to be_nil } + end + + context 'when matching action is defined' do + let(:build) { create(:ci_build) } + let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + context 'when action did not yet finish' do + let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } + + it 'returns the same action' do + is_expected.to eq(close_action) + is_expected.to include(user: user) + end + end + + context 'if action did finish' do + let!(:close_action) { create(:ci_build, :manual, :success, pipeline: build.pipeline, name: 'close_app') } + + it 'returns a new action of the same type' do + is_expected.to be_persisted + is_expected.to include(name: close_action.name, user: user) + end + end + end + end end -- cgit v1.2.1 From ad85928752ea6c41863c6b8225ff2279e3044bef Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 12:22:51 +0200 Subject: Add logical validation to gitlab-ci.yml --- .../projects/environments_controller.rb | 2 +- lib/ci/gitlab_ci_yaml_processor.rb | 30 ++++++++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 46 ++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 5837f913d16..ea22b2dcc15 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -10,7 +10,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController @scope = params[:scope] @all_environments = project.environments @environments = - if @scope == 'stopped' then + if @scope == 'stopped' @all_environments.stopped else @all_environments.available diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 2fd1fced65c..3e33c9399e2 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -109,6 +109,7 @@ module Ci validate_job_stage!(name, job) validate_job_dependencies!(name, job) + validate_job_environment!(name, job) end end @@ -150,6 +151,35 @@ module Ci end end + def validate_job_environment!(name, job) + return unless job[:environment] + return unless job[:environment].is_a?(Hash) + + environment = job[:environment] + validate_on_stop_job!(name, environment, environment[:on_stop]) + end + + def validate_on_stop_job!(name, environment, on_stop) + return unless on_stop + + on_stop_job = @jobs[on_stop.to_sym] + unless on_stop_job + raise ValidationError, "#{name} job: on_stop job #{on_stop} is not defined" + end + + unless on_stop_job[:environment] + raise ValidationError, "#{name} job: on_stop job #{on_stop} does not have environment defined" + end + + unless on_stop_job[:environment][:name] == environment[:name] + raise ValidationError, "#{name} job: on_stop job #{on_stop} have different environment name" + end + + unless on_stop_job[:environment][:action] == 'stop' + raise ValidationError, "#{name} job: on_stop job #{on_stop} needs to have action stop defined" + end + end + def process?(only_params, except_params, ref, tag, trigger_request) if only_params.present? return false unless matching?(only_params, ref, tag, trigger_request) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index a46ff07f625..84f21631719 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -796,6 +796,52 @@ module Ci expect { builds }.to raise_error("jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}") end end + + context 'when on_stop is specified' do + let(:review) { { stage: 'deploy', script: 'test', environment: { name: 'review', on_stop: 'close_review' } } } + let(:config) { { review: review, close_review: close_review }.compact } + + context 'with matching job' do + let(:close_review) { { stage: 'deploy', script: 'test', environment: { name: 'review', action: 'stop' } } } + + it 'does return a list of builds' do + expect(builds.size).to eq(2) + expect(builds.first[:environment]).to eq('review') + end + end + + context 'without matching job' do + let(:close_review) { nil } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review is not defined') + end + end + + context 'with close job without environment' do + let(:close_review) { { stage: 'deploy', script: 'test' } } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review does not have environment defined') + end + end + + context 'with close job for different environment' do + let(:close_review) { { stage: 'deploy', script: 'test', environment: 'production' } } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review have different environment name') + end + end + + context 'with close job without stop action' do + let(:close_review) { { stage: 'deploy', script: 'test', environment: { name: 'review' } } } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review needs to have action stop defined') + end + end + end end describe "Dependencies" do -- cgit v1.2.1 From c4e45d89d407016c89c5217cb91a7a82cf8710f1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 13:51:12 +0200 Subject: Fix remaining offenses --- spec/features/merge_requests/widget_deployments_spec.rb | 2 +- spec/models/environment_spec.rb | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 1be35b27d84..6676821b807 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -32,7 +32,7 @@ feature 'Widget Deployments Header', feature: true, js: true do given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:deployment) do create(:deployment, environment: environment, ref: merge_request.target_branch, - sha: sha, deployable: build, on_stop: 'close_app') + sha: sha, deployable: build, on_stop: 'close_app') end background do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index e1755f940b3..a94e6d0165f 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -150,8 +150,8 @@ describe Environment, models: true do let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } it 'returns the same action' do - is_expected.to eq(close_action) - is_expected.to include(user: user) + expect(subject).to eq(close_action) + expect(subject.user).to eq(user) end end @@ -160,7 +160,8 @@ describe Environment, models: true do it 'returns a new action of the same type' do is_expected.to be_persisted - is_expected.to include(name: close_action.name, user: user) + expect(subject.name).to eq(close_action.name) + expect(subject.user).to eq(user) end end end -- cgit v1.2.1 From 9c8c5e9dc050f32cec05f6903105ff34d726979b Mon Sep 17 00:00:00 2001 From: amaia Date: Mon, 17 Oct 2016 05:53:12 -0700 Subject: fix: commit messages being double-escaped in activies tab --- CHANGELOG.md | 1 + lib/banzai/filter/html_entity_filter.rb | 2 +- spec/lib/banzai/filter/html_entity_filter_spec.rb | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90379e011d6..5d6d5bc591c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fixes padding in all clipboard icons that have .btn class - Fix a typo in doc/api/labels.md + - Fix double-escaping in activities tab (Alexandre Maia) - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling - Make guests unable to view MRs on private projects diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb index e008fd428b0..f3bd587c28b 100644 --- a/lib/banzai/filter/html_entity_filter.rb +++ b/lib/banzai/filter/html_entity_filter.rb @@ -5,7 +5,7 @@ module Banzai # Text filter that escapes these HTML entities: & " < > class HtmlEntityFilter < HTML::Pipeline::TextFilter def call - ERB::Util.html_escape(text) + ERB::Util.html_escape_once(text) end end end diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb index 4c68ce6d6e4..f9e6bd609f0 100644 --- a/spec/lib/banzai/filter/html_entity_filter_spec.rb +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -11,4 +11,9 @@ describe Banzai::Filter::HtmlEntityFilter, lib: true do expect(output).to eq(escaped) end + + it 'does not double-escape' do + escaped = ERB::Util.html_escape("Merge branch 'blabla' into 'master'") + expect(filter(escaped)).to eq(escaped) + end end -- cgit v1.2.1 From bc493bd88e35deb9e228aadb029320506814f863 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 30 Aug 2016 15:25:41 -0500 Subject: Add pipelines tab to new MR --- app/views/projects/merge_requests/_new_submit.html.haml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index da6927879a4..3a670cb70dd 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,6 +18,7 @@ = f.hidden_field :target_branch .mr-compare.merge-request +<<<<<<< 4a8cb230c6ba28ccdcd6da91855005e4fa0c46bf - if @commits.empty? .commits-empty %h4 @@ -30,6 +31,10 @@ Commits %span.badge= @commits.size - if @pipeline + %li.builds-tab + = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do + Pipelines + %span.badge= @statuses.size %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds @@ -47,6 +52,8 @@ - if @pipeline #builds.builds.tab-pane = render "projects/merge_requests/show/builds" + #pipelines.pipelines.tab-pane + = render "projects/merge_requests/show/pipelines" .mr-loading-status = spinner -- cgit v1.2.1 From 04c03d9d5ce1b6240ad0a7f500b2faeba453401e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 10:22:04 -0500 Subject: Fix pipeline tab content display and badge count --- app/views/projects/merge_requests/show/_pipelines.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml index afe3f3430c6..db0ba88417b 100644 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ b/app/views/projects/merge_requests/show/_pipelines.html.haml @@ -1 +1 @@ -= render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true += render "projects/commit/pipelines_list", pipelines: @pipeline, link_to_commit: true -- cgit v1.2.1 From 4945959b7c3c930778d1b88367d5e60fd06b52a0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 08:46:53 -0500 Subject: Fix variable to show multiple pipelines for MR --- app/views/projects/merge_requests/_new_submit.html.haml | 1 - app/views/projects/merge_requests/show/_pipelines.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 3a670cb70dd..bfe461e6943 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,7 +18,6 @@ = f.hidden_field :target_branch .mr-compare.merge-request -<<<<<<< 4a8cb230c6ba28ccdcd6da91855005e4fa0c46bf - if @commits.empty? .commits-empty %h4 diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml index db0ba88417b..afe3f3430c6 100644 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ b/app/views/projects/merge_requests/show/_pipelines.html.haml @@ -1 +1 @@ -= render "projects/commit/pipelines_list", pipelines: @pipeline, link_to_commit: true += render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true -- cgit v1.2.1 From 27c762b8db3e2b6896ef88ea1a078745ae33909d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 11:48:36 +0200 Subject: Remove obsolete variable assigned to MR widget view --- app/controllers/projects/merge_requests_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a39b47b6d95..cfa463b6452 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -517,7 +517,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_widget_vars @pipeline = @merge_request.pipeline - @pipelines = [@pipeline].compact end def define_commit_vars -- cgit v1.2.1 From db0e700d2ff9a372a8bd903faf305c0debd9b372 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 12:55:18 +0200 Subject: Use all pipelines variable when creating merge request --- app/controllers/projects/merge_requests_controller.rb | 4 ++-- app/views/projects/merge_requests/_new_submit.html.haml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index cfa463b6452..bce89d0a9ad 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -558,8 +558,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline + @pipelines = @merge_request.all_pipelines + @statuses = @pipelines.first.statuses.relevant if @pipelines.any? @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count end diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index bfe461e6943..9c6f562f7db 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -29,11 +29,11 @@ = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do Commits %span.badge= @commits.size - - if @pipeline + - if @pipelines.any? %li.builds-tab = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do Pipelines - %span.badge= @statuses.size + %span.badge= @pipelines.size %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds @@ -48,7 +48,7 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - # This tab is always loaded via AJAX - - if @pipeline + - if @pipelines.any? #builds.builds.tab-pane = render "projects/merge_requests/show/builds" #pipelines.pipelines.tab-pane @@ -65,5 +65,5 @@ :javascript var merge_request = new MergeRequest({ action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", - buildsLoaded: "#{@pipeline ? 'true' : 'false'}" + buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}" }); -- cgit v1.2.1 From f7da506529ab0989069fe1c9d9bf9d819c13211d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 13:55:30 +0200 Subject: Extract pipeline vars in merge requests controller --- app/controllers/projects/merge_requests_controller.rb | 18 +++++++++++++----- app/views/projects/merge_requests/_show.html.haml | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index bce89d0a9ad..4df0648a502 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -483,13 +483,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController @noteable = @merge_request @commits_count = @merge_request.commits.count - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline - if @merge_request.locked_long_ago? @merge_request.unlock_mr @merge_request.close end + + define_pipelines_vars end # Discussion tab data is rendered on html responses of actions @@ -543,6 +542,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController ) end + def define_pipelines_vars + @pipelines = @merge_request.all_pipelines + + if @pipelines.any? + @pipeline = @pipelines.first + @statuses = @pipeline.statuses.relevant + end + end + def define_new_vars @noteable = @merge_request @@ -558,10 +566,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit - @pipelines = @merge_request.all_pipelines - @statuses = @pipelines.first.statuses.relevant if @pipelines.any? @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count + + define_pipelines_vars end def invalid_mr diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 662463bc72b..fb4afe3bff2 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -61,7 +61,7 @@ %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines - %span.badge= @merge_request.all_pipelines.size + %span.badge= @pipelines.size %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do Builds -- cgit v1.2.1 From d5b1d0ea501bb6eb0e18f63f4ddc0ba3f32d372a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 14:15:06 +0200 Subject: Use temporary compare commits when MR not persisted --- app/models/merge_request.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 8c6905a442d..88e46ecdc8b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -787,21 +787,19 @@ class MergeRequest < ActiveRecord::Base def all_pipelines return unless source_project - @all_pipelines ||= begin - sha = if persisted? - all_commits_sha - else - diff_head_sha - end - - source_project.pipelines.order(id: :desc). - where(sha: sha, ref: source_branch) - end + @all_pipelines ||= source_project.pipelines + .where(sha: all_commits_sha, ref: source_branch) + .order(id: :desc) end # Note that this could also return SHA from now dangling commits + # def all_commits_sha - merge_request_diffs.flat_map(&:commits_sha).uniq + if persisted? + merge_request_diffs.flat_map(&:commits_sha).uniq + else + compare_commits.reverse.map(&:id) + end end def merge_commit -- cgit v1.2.1 From ab8ef17fb2a2aa85a76d9106894280be9b910fda Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 15:31:21 +0200 Subject: Extend merge request tests for all commits method --- app/models/merge_request.rb | 7 +++-- spec/models/merge_request_spec.rb | 58 +++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 88e46ecdc8b..ced0c13b837 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -788,8 +788,8 @@ class MergeRequest < ActiveRecord::Base return unless source_project @all_pipelines ||= source_project.pipelines - .where(sha: all_commits_sha, ref: source_branch) - .order(id: :desc) + .where(sha: all_commits_sha, ref: source_branch) + .order(id: :desc) end # Note that this could also return SHA from now dangling commits @@ -798,7 +798,8 @@ class MergeRequest < ActiveRecord::Base if persisted? merge_request_diffs.flat_map(&:commits_sha).uniq else - compare_commits.reverse.map(&:id) + cached_commits = compare_commits.to_a.reverse.map(&:id) + cached_commits.any? ? cached_commits : [diff_head_sha] end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 91a423b670c..344f69a703e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -640,32 +640,56 @@ describe MergeRequest, models: true do end describe '#all_commits_sha' do - let(:all_commits_sha) do - subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq - end + context 'when merge request is persisted' do + let(:all_commits_sha) do + subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq + end - shared_examples 'returning all SHA' do - it 'returns all SHA from all merge_request_diffs' do - expect(subject.merge_request_diffs.size).to eq(2) - expect(subject.all_commits_sha).to eq(all_commits_sha) + shared_examples 'returning all SHA' do + it 'returns all SHA from all merge_request_diffs' do + expect(subject.merge_request_diffs.size).to eq(2) + expect(subject.all_commits_sha).to eq(all_commits_sha) + end end - end - context 'with a completely different branch' do - before do - subject.update(target_branch: 'v1.0.0') + context 'with a completely different branch' do + before do + subject.update(target_branch: 'v1.0.0') + end + + it_behaves_like 'returning all SHA' end - it_behaves_like 'returning all SHA' + context 'with a branch having no difference' do + before do + subject.update(target_branch: 'v1.1.0') + subject.reload # make sure commits were not cached + end + + it_behaves_like 'returning all SHA' + end end - context 'with a branch having no difference' do - before do - subject.update(target_branch: 'v1.1.0') - subject.reload # make sure commits were not cached + context 'when merge request is not persisted' do + context 'when compare commits are set in the service' do + let(:commit) { spy('commit') } + + subject do + build(:merge_request, compare_commits: [commit, commit]) + end + + it 'returns commits from compare commits temporary data' do + expect(subject.all_commits_sha).to eq [commit, commit] + end end - it_behaves_like 'returning all SHA' + context 'when compare commits are not set in the service' do + subject { build(:merge_request) } + + it 'returns array with diff head sha element only' do + expect(subject.all_commits_sha).to eq [subject.diff_head_sha] + end + end end end -- cgit v1.2.1 From 72d84e48511fcf88b1d9efb622eb37cdff95aa1c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:05:47 +0200 Subject: Improve code that creates a list of commits for MR --- app/models/merge_request.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ced0c13b837..fedc35102ef 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -797,9 +797,10 @@ class MergeRequest < ActiveRecord::Base def all_commits_sha if persisted? merge_request_diffs.flat_map(&:commits_sha).uniq + elsif compare_commits + compare_commits.to_a.reverse.map(&:id) else - cached_commits = compare_commits.to_a.reverse.map(&:id) - cached_commits.any? ? cached_commits : [diff_head_sha] + [diff_head_sha] end end -- cgit v1.2.1 From 3032a0ca891808c9aa1ba69d540bf97e40a84670 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 16:46:19 +0200 Subject: Merge two scenarios into one --- spec/features/environments_spec.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 7200e9ad4c1..b565586ee14 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -20,11 +20,8 @@ feature 'Environments', feature: true do end context 'shows two tabs' do - scenario 'does show Available tab with link' do + scenario 'shows "Available" and "Stopped" tab with links' do expect(page).to have_link('Available') - end - - scenario 'does show Stopped tab with link' do expect(page).to have_link('Stopped') end end @@ -47,11 +44,8 @@ feature 'Environments', feature: true do expect(page).to have_link(environment.name) end - scenario 'does show number of opened environments in Available tab' do + scenario 'does show number of available and stopped environments' do expect(page.find('.js-available-environments-count').text).to eq('1') - end - - scenario 'does show number of closed environments in Stopped tab' do expect(page.find('.js-stopped-environments-count').text).to eq('0') end -- cgit v1.2.1 From cc6d42861bed8fc4c050ee8906d77bdb49783de5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 18 Oct 2016 17:28:57 +0100 Subject: Backport git access spec changes from EE These were introduced in: --- spec/lib/gitlab/git_access_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index de68e32e5b4..a5aa387f4f7 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -185,6 +185,7 @@ describe Gitlab::GitAccess, lib: true do end end + # Run permission checks for a user def self.run_permission_checks(permissions_matrix) permissions_matrix.keys.each do |role| describe "#{role} access" do @@ -194,13 +195,12 @@ describe Gitlab::GitAccess, lib: true do else project.team << [user, role] end - end - - permissions_matrix[role].each do |action, allowed| - context action do - subject { access.push_access_check(changes[action]) } - it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + permissions_matrix[role].each do |action, allowed| + context action do + subject { access.push_access_check(changes[action]) } + it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + end end end end -- cgit v1.2.1 From 8989baaae7091832855b976b0eeda32d8b545dcb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 15:10:31 -0700 Subject: Fix broken award emoji for comments Closes #23506 --- app/helpers/award_emoji_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb index 493f14f6f9d..592ffe7b89f 100644 --- a/app/helpers/award_emoji_helper.rb +++ b/app/helpers/award_emoji_helper.rb @@ -4,7 +4,7 @@ module AwardEmojiHelper 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) + toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace, project_id: @project, id: awardable.id) else url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) end -- cgit v1.2.1 From b3fb7f5d5746d95db718d5b418f015b7e45ecbeb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 16:12:26 -0700 Subject: Add spec for toggling award emoji on issue notes --- spec/features/issues/award_emoji_spec.rb | 46 ++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 79cc50bc18e..ef00f209998 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe 'Awards Emoji', feature: true do + include WaitForAjax + let!(:project) { create(:project) } let!(:user) { create(:user) } @@ -16,20 +18,22 @@ describe 'Awards Emoji', feature: true do project: project) end + let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } + before do visit namespace_project_issue_path(project.namespace, project, issue) end it 'increments the thumbsdown emoji', js: true do find('[data-emoji="thumbsdown"]').click - sleep 2 + wait_for_ajax expect(thumbsdown_emoji).to have_text("1") end context 'click the thumbsup emoji' do it 'increments the thumbsup emoji', js: true do find('[data-emoji="thumbsup"]').click - sleep 2 + wait_for_ajax expect(thumbsup_emoji).to have_text("1") end @@ -41,7 +45,7 @@ describe 'Awards Emoji', feature: true do context 'click the thumbsdown emoji' do it 'increments the thumbsdown emoji', js: true do find('[data-emoji="thumbsdown"]').click - sleep 2 + wait_for_ajax expect(thumbsdown_emoji).to have_text("1") end @@ -49,13 +53,45 @@ describe 'Awards Emoji', feature: true do expect(thumbsup_emoji).to have_text("0") end end + + it 'toggles the smiley emoji on a note', js: true do + toggle_smiley_emoji(true) + + within('.note-awards') do + expect(find(emoji_counter)).to have_text("1") + end + + toggle_smiley_emoji(false) + + within('.note-awards') do + expect(page).not_to have_selector(emoji_counter) + end + end end def thumbsup_emoji - page.all('span.js-counter').first + page.all(emoji_counter).first end def thumbsdown_emoji - page.all('span.js-counter').last + page.all(emoji_counter).last + end + + def emoji_counter + 'span.js-counter' + end + + def toggle_smiley_emoji(status) + within('.note') do + find('.note-emoji-button').click + end + + unless status + first('[data-emoji="smiley"]').click + else + find('[data-emoji="smiley"]').click + end + + wait_for_ajax end end -- cgit v1.2.1 From 4e028d333bbbcafcd89b04778b0b37775b6015c4 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 19 Oct 2016 04:55:26 +0100 Subject: Return the title for id of 'No label' --- app/assets/javascripts/labels_select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index f1e719937c7..b4f6e70f694 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -266,7 +266,7 @@ }, fieldName: $dropdown.data('field-name'), id: function(label) { - if (label.id <= 0) return; + if (label.id <= 0) return label.title; if ($dropdown.hasClass('js-issuable-form-dropdown')) { return label.id; -- cgit v1.2.1 From 47b0edbe746d5ca2a1502d3f0bf0213fe1789567 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 21:26:20 -0700 Subject: Improve error logging of MergeService Relates to #23505 --- app/services/merge_requests/merge_service.rb | 14 ++++++++++---- spec/services/merge_requests/merge_service_spec.rb | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index b037780c431..38477c1f321 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -11,14 +11,14 @@ module MergeRequests def execute(merge_request) @merge_request = merge_request - return error('Merge request is not mergeable') unless @merge_request.mergeable? + return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable? merge_request.in_locked_state do if commit after_merge success else - error('Can not merge changes') + log_merge_error('Can not merge changes', true) end end end @@ -46,8 +46,8 @@ module MergeRequests merge_request.update(merge_error: e.message) false rescue StandardError => e - merge_request.update(merge_error: "Something went wrong during merge") - Rails.logger.error(e.message) + merge_request.update(merge_error: "Something went wrong during merge: #{e.message}") + log_merge_error(e.message) false ensure merge_request.update(in_progress_merge_commit_sha: nil) @@ -65,5 +65,11 @@ module MergeRequests def branch_deletion_user @merge_request.force_remove_source_branch? ? @merge_request.author : current_user end + + def log_merge_error(message, http_error = false) + Rails.logger.error("MergeService error: #{message}") + + error(message) if http_error + end end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index ee53e110aee..9163c0c104e 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -120,13 +120,13 @@ describe MergeRequests::MergeService, services: true do let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } it 'saves error if there is an exception' do - allow(service).to receive(:repository).and_raise("error") + allow(service).to receive(:repository).and_raise("error message") allow(service).to receive(:execute_hooks) service.execute(merge_request) - expect(merge_request.merge_error).to eq("Something went wrong during merge") + expect(merge_request.merge_error).to eq("Something went wrong during merge: error message") end it 'saves error if there is an PreReceiveError exception' do -- cgit v1.2.1 From 75b7ba3f7b89f10f7088b84b4594e747c571f016 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 21:59:57 -0700 Subject: Identify merge request project and IID in log message --- app/services/merge_requests/merge_service.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 38477c1f321..ab9056a3250 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -67,9 +67,15 @@ module MergeRequests end def log_merge_error(message, http_error = false) - Rails.logger.error("MergeService error: #{message}") + Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}") error(message) if http_error end + + def merge_request_info + project = merge_request.project + + "#{project.to_reference}#{merge_request.to_reference}" + end end end -- cgit v1.2.1 From c8c3cd82d5f93fd325a10fa1c059499a78175ddb Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 19 Oct 2016 09:32:39 +0200 Subject: Use Hash rocket syntax to fix cycle analytics under Ruby 2.1 Refers to #23510 --- app/views/projects/cycle_analytics/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 7f346df8797..ef24ec0cf29 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -5,7 +5,7 @@ #cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}} .bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"} - = icon('times', class: 'dismiss-icon', "@click": "dismissLanding()") + = icon('times', class: 'dismiss-icon', "@click" => "dismissLanding()") .row .col-sm-3.col-xs-12.svg-container = custom_icon('icon_cycle_analytics_splash') -- cgit v1.2.1 From 9a14b0bb6993349c568b6f819b43200ae6441e69 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Oct 2016 10:47:40 +0200 Subject: Fix name of feature that restricts builds traces --- app/views/projects/pipelines_settings/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index 0740e9b56ab..bebf0ccd54d 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -64,8 +64,8 @@ .checkbox = f.label :public_builds do = f.check_box :public_builds - %strong Public pipelines - .help-block Allow everyone to access pipelines for Public and Internal projects + %strong Public builds + .help-block Allow everyone to access builds traces for Public and Internal projects .form-group.append-bottom-default = f.label :runners_token, "Runners token", class: 'label-light' -- cgit v1.2.1 From 8cf45f63613276f47099def265ab4cde8eb0c758 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 19 Oct 2016 12:22:35 +0200 Subject: Revert "Merge branch 'gjlaubenstein/gitlab-ce-21712-change-issue-show-html-title'" This reverts commit 434d98b22a6381447a9c59cec16fc324ade198df, reversing changes made to b6a83be65f6711a3cf808400c549bdd3ec7eda74. --- CHANGELOG.md | 1 - app/views/projects/issues/edit.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 2 +- app/views/projects/merge_requests/edit.html.haml | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d317fd1db..f1ef9238c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,7 +105,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) - 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 diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml index 3a6fbbc7fbc..7cf1923456e 100644 --- a/app/views/projects/issues/edit.html.haml +++ b/app/views/projects/issues/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues" +- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues" %h3.page-title Edit Issue ##{@issue.iid} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 09347ad5fff..49c47a7dc6a 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@issue.to_reference} #{@issue.title}", "Issues" +- page_title "#{@issue.title} (##{@issue.iid})", "Issues" - page_description @issue.description - page_card_attributes @issue.card_attributes diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 662463bc72b..5c41db36e44 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" +- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes - content_for :page_specific_javascripts do diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 7c3ac6652ee..03159f123f3 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" +- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" %h3.page-title Edit Merge Request #{@merge_request.to_reference} -- cgit v1.2.1 From 31d369b796466a726bbfce9ab72df11c8644f945 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 19 Oct 2016 12:23:51 +0200 Subject: Use `to_reference` for Issue "show" and "edit" page titles Thanks to Greg Laubenstein for noticing in !6503 --- app/views/projects/issues/edit.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml index 7cf1923456e..1b7d878c38c 100644 --- a/app/views/projects/issues/edit.html.haml +++ b/app/views/projects/issues/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues" %h3.page-title Edit Issue ##{@issue.iid} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 49c47a7dc6a..6f3f238a436 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues" - page_description @issue.description - page_card_attributes @issue.card_attributes -- cgit v1.2.1 From c7282f89596293423c61d6676db60a9a347d09ef Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 10:45:23 +0200 Subject: Grapify the commit status API --- lib/api/commit_statuses.rb | 53 ++++++++++++++++--------------- spec/requests/api/commit_statuses_spec.rb | 4 +-- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index dfbdd597d29..f282a3b9cd6 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -6,17 +6,17 @@ module API resource :projects do before { authenticate! } - # Get a commit's statuses - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash - # ref (optional) - The ref - # stage (optional) - The stage - # name (optional) - The name - # all (optional) - Show all statuses, default: false - # Examples: - # GET /projects/:id/repository/commits/:sha/statuses + desc "Get a commit's statuses" do + success Entities::CommitStatus + end + params do + requires :id, type: String, desc: 'The ID of a project' + requires :sha, type: String, desc: 'The commit hash' + optional :ref, type: String, desc: 'The ref' + optional :stage, type: String, desc: 'The stage' + optional :name, type: String, desc: 'The name' + optional :all, type: String, desc: 'Show all statuses, default: false' + end get ':id/repository/commits/:sha/statuses' do authorize!(:read_commit_status, user_project) @@ -31,22 +31,23 @@ module API present paginate(statuses), with: Entities::CommitStatus end - # Post status to commit - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash - # ref (optional) - The ref - # state (required) - The state of the status. Can be: pending, running, success, failed or canceled - # target_url (optional) - The target URL to associate with this status - # description (optional) - A short description of the status - # name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default" - # Examples: - # POST /projects/:id/statuses/:sha + desc 'Post status to a commit' do + success Entities::CommitStatus + end + params do + requires :id, type: String, desc: 'The ID of a project' + requires :sha, type: String, desc: 'The commit hash' + requires :state, type: String, desc: 'The state of the status', + values: ['pending', 'running', 'success', 'failed', 'canceled'] + optional :ref, type: String, desc: 'The ref' + optional :target_url, type: String, desc: 'The target URL to associate with this status' + optional :description, type: String, desc: 'A short description of the status' + optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"' + optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"' + end post ':id/statuses/:sha' do authorize! :create_commit_status, user_project - required_attributes! [:state] - attrs = attributes_for_keys [:target_url, :description] + commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -68,7 +69,7 @@ module API status = GenericCommitStatus.running_or_pending.find_or_initialize_by( project: @project, pipeline: pipeline, user: current_user, name: name, ref: ref) - status.attributes = attrs + status.attributes = declared(params).slice(:target_url, :description) begin case params[:state].to_s diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 7aa7e85a9e2..335efc4db6c 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -196,7 +196,7 @@ describe API::CommitStatuses, api: true do end context 'reporter user' do - before { post api(post_url, reporter) } + before { post api(post_url, reporter), state: 'running' } it 'does not create commit status' do expect(response).to have_http_status(403) @@ -204,7 +204,7 @@ describe API::CommitStatuses, api: true do end context 'guest user' do - before { post api(post_url, guest) } + before { post api(post_url, guest), state: 'running' } it 'does not create commit status' do expect(response).to have_http_status(403) -- cgit v1.2.1 From fa075771a62a6fdb1c0ce505a14a4eee87ff55a0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 14:13:44 +0300 Subject: Refactor group_members_controller_spec Signed-off-by: Dmitriy Zaporozhets --- .../groups/group_members_controller_spec.rb | 120 ++++++--------------- spec/factories/group_members.rb | 1 + 2 files changed, 35 insertions(+), 86 deletions(-) diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index a0870891cf4..ad15b3f8f40 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -2,16 +2,10 @@ require 'spec_helper' describe Groups::GroupMembersController do let(:user) { create(:user) } + let(:group) { create(:group, :public) } - describe '#index' do - let(:group) { create(:group) } - - before do - group.add_owner(user) - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - end - - it 'renders index with group members' do + describe 'GET index' do + it 'renders index with 200 status code' do get :index, group_id: group expect(response).to have_http_status(200) @@ -19,74 +13,56 @@ describe Groups::GroupMembersController do end end - describe '#destroy' do - let(:group) { create(:group, :public) } + describe 'DELETE destroy' do + let(:member) { create(:group_member, :developer, group: group) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 403' do - delete :destroy, group_id: group, - id: 42 + delete :destroy, group_id: group, id: 42 expect(response).to have_http_status(403) end end context 'when member is found' do - let(:user) { create(:user) } - let(:group_user) { create(:user) } - let(:member) do - group.add_developer(group_user) - group.members.find_by(user_id: group_user) - end - context 'when user does not have enough rights' do - before do - group.add_developer(user) - sign_in(user) - end + before { group.add_developer(user) } it 'returns 403' do - delete :destroy, group_id: group, - id: member + delete :destroy, group_id: group, id: member expect(response).to have_http_status(403) - expect(group.users).to include group_user + expect(group.members).to include member end end context 'when user has enough rights' do - before do - group.add_owner(user) - sign_in(user) - end + before { group.add_owner(user) } it '[HTML] removes user from members' do - delete :destroy, group_id: group, - id: member + delete :destroy, group_id: group, id: member expect(response).to set_flash.to 'User was successfully removed from group.' expect(response).to redirect_to(group_group_members_path(group)) - expect(group.users).not_to include group_user + expect(group.members).not_to include member end it '[JS] removes user from members' do - xhr :delete, :destroy, group_id: group, - id: member + xhr :delete, :destroy, group_id: group, id: member expect(response).to be_success - expect(group.users).not_to include group_user + expect(group.members).not_to include member end end end end - describe '#leave' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } + describe 'DELETE leave' do + before { sign_in(user) } context 'when member is not found' do - before { sign_in(user) } - it 'returns 404' do delete :leave, group_id: group @@ -96,10 +72,7 @@ describe Groups::GroupMembersController do context 'when member is found' do context 'and is not an owner' do - before do - group.add_developer(user) - sign_in(user) - end + before { group.add_developer(user) } it 'removes user from members' do delete :leave, group_id: group @@ -111,10 +84,7 @@ describe Groups::GroupMembersController do end context 'and is an owner' do - before do - group.add_owner(user) - sign_in(user) - end + before { group.add_owner(user) } it 'cannot removes himself from the group' do delete :leave, group_id: group @@ -124,10 +94,7 @@ describe Groups::GroupMembersController do end context 'and is a requester' do - before do - group.request_access(user) - sign_in(user) - end + before { group.request_access(user) } it 'removes user from members' do delete :leave, group_id: group @@ -141,13 +108,8 @@ describe Groups::GroupMembersController do end end - describe '#request_access' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - before do - sign_in(user) - end + describe 'POST request_access' do + before { sign_in(user) } it 'creates a new GroupMember that is not a team member' do post :request_access, group_id: group @@ -159,53 +121,39 @@ describe Groups::GroupMembersController do end end - describe '#approve_access_request' do - let(:group) { create(:group, :public) } + describe 'POST approve_access_request' do + let(:member) { create(:group_member, :access_request, group: group) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 403' do - post :approve_access_request, group_id: group, - id: 42 + post :approve_access_request, group_id: group, id: 42 expect(response).to have_http_status(403) end end context 'when member is found' do - let(:user) { create(:user) } - let(:group_requester) { create(:user) } - let(:member) do - group.request_access(group_requester) - group.requesters.find_by(user_id: group_requester) - end - context 'when user does not have enough rights' do - before do - group.add_developer(user) - sign_in(user) - end + before { group.add_developer(user) } it 'returns 403' do - post :approve_access_request, group_id: group, - id: member + post :approve_access_request, group_id: group, id: member expect(response).to have_http_status(403) - expect(group.users).not_to include group_requester + expect(group.members).not_to include member end end context 'when user has enough rights' do - before do - group.add_owner(user) - sign_in(user) - end + before { group.add_owner(user) } it 'adds user to members' do - post :approve_access_request, group_id: group, - id: member + post :approve_access_request, group_id: group, id: member expect(response).to redirect_to(group_group_members_path(group)) - expect(group.users).to include group_requester + expect(group.members).to include member end end end diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index 795df5dfda9..080b2e75ea1 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -9,5 +9,6 @@ FactoryGirl.define do trait(:developer) { access_level GroupMember::DEVELOPER } trait(:master) { access_level GroupMember::MASTER } trait(:owner) { access_level GroupMember::OWNER } + trait(:access_request) { requested_at Time.now } end end -- cgit v1.2.1 From 67b96255d89de8e31234d68a3408adb8b9779dd9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 19 Oct 2016 14:03:31 +0200 Subject: Keep around commits only on pipeline create --- CHANGELOG.md | 1 + app/models/ci/pipeline.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d317fd1db..44c0b80bf6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Ability to resolve merge request conflicts with editor !6374 - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) + - Keep around commits only pipeline creation as pipeline data doesn't change over time - 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 diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e75fe6c222b..e84c91b417d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -19,7 +19,7 @@ module Ci validates_presence_of :status, unless: :importing? validate :valid_commit_sha, unless: :importing? - after_save :keep_around_commits, unless: :importing? + after_create :keep_around_commits, unless: :importing? delegate :stages, to: :statuses -- cgit v1.2.1 From 357c794a49843ca9984642ddd091612e3200a8e3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 15:13:59 +0300 Subject: Refactor project_members_controller_spec Signed-off-by: Dmitriy Zaporozhets --- .../projects/project_members_controller_spec.rb | 113 +++++++-------------- spec/factories/project_members.rb | 1 + 2 files changed, 36 insertions(+), 78 deletions(-) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 074f85157de..ea56bd72912 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -1,10 +1,11 @@ require('spec_helper') describe Projects::ProjectMembersController do - describe '#apply_import' do - let(:project) { create(:project) } + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + describe 'POST apply_import' do let(:another_project) { create(:project, :private) } - let(:user) { create(:user) } let(:member) { create(:user) } before do @@ -47,23 +48,19 @@ describe Projects::ProjectMembersController do end end - describe '#index' do - context 'when user is member' do - before do - project = create(:project, :private) - member = create(:user) - project.team << [member, :guest] - sign_in(member) - - get :index, namespace_id: project.namespace, project_id: project - end + describe 'GET index' do + it 'renders index with 200 status code' do + get :index, namespace_id: project.namespace, project_id: project - it { expect(response).to have_http_status(200) } + expect(response).to have_http_status(200) + expect(response).to render_template(:index) end end - describe '#destroy' do - let(:project) { create(:project, :public) } + describe 'DELETE destroy' do + let(:member) { create(:project_member, :developer, project: project) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 404' do @@ -76,18 +73,8 @@ describe Projects::ProjectMembersController do end context 'when member is found' do - let(:user) { create(:user) } - let(:team_user) { create(:user) } - let(:member) do - project.team << [team_user, :developer] - project.members.find_by(user_id: team_user.id) - end - context 'when user does not have enough rights' do - before do - project.team << [user, :developer] - sign_in(user) - end + before { project.team << [user, :developer] } it 'returns 404' do delete :destroy, namespace_id: project.namespace, @@ -95,15 +82,12 @@ describe Projects::ProjectMembersController do id: member expect(response).to have_http_status(404) - expect(project.users).to include team_user + expect(project.members).to include member end end context 'when user has enough rights' do - before do - project.team << [user, :master] - sign_in(user) - end + before { project.team << [user, :master] } it '[HTML] removes user from members' do delete :destroy, namespace_id: project.namespace, @@ -113,7 +97,7 @@ describe Projects::ProjectMembersController do expect(response).to redirect_to( namespace_project_project_members_path(project.namespace, project) ) - expect(project.users).not_to include team_user + expect(project.members).not_to include member end it '[JS] removes user from members' do @@ -122,19 +106,16 @@ describe Projects::ProjectMembersController do id: member expect(response).to be_success - expect(project.users).not_to include team_user + expect(project.members).not_to include member end end end end - describe '#leave' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } + describe 'DELETE leave' do + before { sign_in(user) } context 'when member is not found' do - before { sign_in(user) } - it 'returns 404' do delete :leave, namespace_id: project.namespace, project_id: project @@ -145,10 +126,7 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'and is not an owner' do - before do - project.team << [user, :developer] - sign_in(user) - end + before { project.team << [user, :developer] } it 'removes user from members' do delete :leave, namespace_id: project.namespace, @@ -161,11 +139,9 @@ describe Projects::ProjectMembersController do end context 'and is an owner' do - before do - project.update(namespace_id: user.namespace_id) - project.team << [user, :master, user] - sign_in(user) - end + let(:project) { create(:project, namespace: user.namespace) } + + before { project.team << [user, :master] } it 'cannot remove himself from the project' do delete :leave, namespace_id: project.namespace, @@ -176,10 +152,7 @@ describe Projects::ProjectMembersController do end context 'and is a requester' do - before do - project.request_access(user) - sign_in(user) - end + before { project.request_access(user) } it 'removes user from members' do delete :leave, namespace_id: project.namespace, @@ -194,13 +167,8 @@ describe Projects::ProjectMembersController do end end - describe '#request_access' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - before do - sign_in(user) - end + describe 'POST request_access' do + before { sign_in(user) } it 'creates a new ProjectMember that is not a team member' do post :request_access, namespace_id: project.namespace, @@ -215,8 +183,10 @@ describe Projects::ProjectMembersController do end end - describe '#approve' do - let(:project) { create(:project, :public) } + describe 'POST approve' do + let(:member) { create(:project_member, :access_request, project: project) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 404' do @@ -229,18 +199,8 @@ describe Projects::ProjectMembersController do end context 'when member is found' do - let(:user) { create(:user) } - let(:team_requester) { create(:user) } - let(:member) do - project.request_access(team_requester) - project.requesters.find_by(user_id: team_requester.id) - end - context 'when user does not have enough rights' do - before do - project.team << [user, :developer] - sign_in(user) - end + before { project.team << [user, :developer] } it 'returns 404' do post :approve_access_request, namespace_id: project.namespace, @@ -248,15 +208,12 @@ describe Projects::ProjectMembersController do id: member expect(response).to have_http_status(404) - expect(project.users).not_to include team_requester + expect(project.members).not_to include member end end context 'when user has enough rights' do - before do - project.team << [user, :master] - sign_in(user) - end + before { project.team << [user, :master] } it 'adds user to members' do post :approve_access_request, namespace_id: project.namespace, @@ -266,7 +223,7 @@ describe Projects::ProjectMembersController do expect(response).to redirect_to( namespace_project_project_members_path(project.namespace, project) ) - expect(project.users).to include team_requester + expect(project.members).to include member end end end diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index 1ddb305a8af..c21927640d1 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -8,5 +8,6 @@ FactoryGirl.define do trait(:reporter) { access_level ProjectMember::REPORTER } trait(:developer) { access_level ProjectMember::DEVELOPER } trait(:master) { access_level ProjectMember::MASTER } + trait(:access_request) { requested_at Time.now } end end -- cgit v1.2.1 From 43da118f85d1e53770d106905be3358c6452c37e Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 19 Oct 2016 10:40:25 -0500 Subject: Bump `omniauth-saml` to 1.7.0 to include security fixes and metadata support for IdP auto-configuration. --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 05166b6a828..46245ab62d1 100644 --- a/Gemfile +++ b/Gemfile @@ -29,7 +29,7 @@ gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos -gem 'omniauth-saml', '~> 1.6.0' +gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index a9892d1c130..442184b9228 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -473,9 +473,9 @@ GEM omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-saml (1.6.0) + omniauth-saml (1.7.0) omniauth (~> 1.3) - ruby-saml (~> 1.3) + ruby-saml (~> 1.4) omniauth-shibboleth (1.2.1) omniauth (>= 1.0.0) omniauth-twitter (1.2.1) @@ -635,7 +635,7 @@ GEM crack (~> 0.4) ruby-prof (0.16.2) ruby-progressbar (1.8.1) - ruby-saml (1.3.0) + ruby-saml (1.4.1) nokogiri (>= 1.5.10) ruby_parser (3.8.2) sexp_processor (~> 4.1) @@ -915,7 +915,7 @@ DEPENDENCIES omniauth-gitlab (~> 1.0.0) omniauth-google-oauth2 (~> 0.4.1) omniauth-kerberos (~> 0.3.0) - omniauth-saml (~> 1.6.0) + omniauth-saml (~> 1.7.0) omniauth-shibboleth (~> 1.2.0) omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) -- cgit v1.2.1 From 8e4301d982ce28d80e711865ac294a98ddce3ec6 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 6 Oct 2016 19:05:27 -0300 Subject: Prevent wrong markdown on issue ids when project has Jira service activated --- CHANGELOG.md | 1 + app/models/external_issue.rb | 5 -- app/models/project.rb | 4 ++ .../project_services/issue_tracker_service.rb | 6 ++ app/models/project_services/jira_service.rb | 5 ++ lib/banzai/filter/abstract_reference_filter.rb | 16 ++++- .../filter/external_issue_reference_filter.rb | 29 +++++---- lib/banzai/filter/issue_reference_filter.rb | 8 +++ .../filter/external_issue_reference_filter_spec.rb | 72 +++++++++++++++++++--- .../banzai/filter/issue_reference_filter_spec.rb | 17 +---- spec/models/external_issue_spec.rb | 15 ----- spec/models/project_services/jira_service_spec.rb | 9 +++ .../project_services/redmine_service_spec.rb | 8 +++ spec/services/git_push_service_spec.rb | 60 +++++++++++------- spec/services/merge_requests/merge_service_spec.rb | 12 ++++ .../issue_tracker_service_shared_example.rb | 15 +++++ 16 files changed, 199 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d317fd1db..c4fcca1b016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.6 + - Ignore references to internal issues when using external issues tracker - Ability to resolve merge request conflicts with editor !6374 - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index b7894c99846..fd9a8c1b8b7 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -29,11 +29,6 @@ class ExternalIssue @project end - # Pattern used to extract `JIRA-123` issue references from text - def self.reference_pattern - @reference_pattern ||= %r{(?\b([A-Z][A-Z0-9_]+-)\d+)} - end - def to_reference(_from_project = nil) id end diff --git a/app/models/project.rb b/app/models/project.rb index db7301219e5..a645c9b29cc 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -664,6 +664,10 @@ class Project < ActiveRecord::Base end end + def issue_reference_pattern + issues_tracker.reference_pattern + end + def default_issues_tracker? !external_issue_tracker end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index d1df6d0292f..b26ddd518d7 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -3,6 +3,12 @@ class IssueTrackerService < Service default_value_for :category, 'issue_tracker' + # Pattern used to extract links from comments + # Override this method on services that uses different patterns + def reference_pattern + @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?\d+)} + end + def default? default end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 97bcbacf2b9..f81b66fd219 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -13,6 +13,11 @@ class JiraService < IssueTrackerService before_update :reset_password + # {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1 + def reference_pattern + @reference_pattern ||= %r{(?\b([A-Z][A-Z0-9_]+-)\d+)} + end + def reset_password # don't reset the password if a new one is provided if api_url_changed? && !password_touched? diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index affe34394c2..cb213a76a05 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -208,8 +208,12 @@ module Banzai @references_per_project ||= begin refs = Hash.new { |hash, key| hash[key] = Set.new } - regex = Regexp.union(object_class.reference_pattern, - object_class.link_reference_pattern) + regex = + if uses_reference_pattern? + Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern) + else + object_class.link_reference_pattern + end nodes.each do |node| node.to_html.scan(regex) do @@ -295,6 +299,14 @@ module Banzai value end end + + # There might be special cases like filters + # that should ignore reference pattern + # eg: IssueReferenceFilter when using a external issues tracker + # In those cases this method should be overridden on the filter subclass + def uses_reference_pattern? + true + end end end end diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb index eaa702952cc..0d20be557a0 100644 --- a/lib/banzai/filter/external_issue_reference_filter.rb +++ b/lib/banzai/filter/external_issue_reference_filter.rb @@ -8,7 +8,7 @@ module Banzai # Public: Find `JIRA-123` issue references in text # - # ExternalIssueReferenceFilter.references_in(text) do |match, issue| + # ExternalIssueReferenceFilter.references_in(text, pattern) do |match, issue| # "##{issue}" # end # @@ -17,8 +17,8 @@ module Banzai # Yields the String match and the String issue reference. # # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(ExternalIssue.reference_pattern) do |match| + def self.references_in(text, pattern) + text.gsub(pattern) do |match| yield match, $~[:issue] end end @@ -27,7 +27,7 @@ module Banzai # Early return if the project isn't using an external tracker return doc if project.nil? || default_issues_tracker? - ref_pattern = ExternalIssue.reference_pattern + ref_pattern = issue_reference_pattern ref_start_pattern = /\A#{ref_pattern}\z/ each_node do |node| @@ -60,7 +60,7 @@ module Banzai def issue_link_filter(text, link_text: nil) project = context[:project] - self.class.references_in(text) do |match, id| + self.class.references_in(text, issue_reference_pattern) do |match, id| ExternalIssue.new(id, project) url = url_for_issue(id, project, only_path: context[:only_path]) @@ -82,18 +82,21 @@ module Banzai end def default_issues_tracker? - if RequestStore.active? - default_issues_tracker_cache[project.id] ||= - project.default_issues_tracker? - else - project.default_issues_tracker? - end + external_issues_cached(:default_issues_tracker?) + end + + def issue_reference_pattern + external_issues_cached(:issue_reference_pattern) end private - def default_issues_tracker_cache - RequestStore[:banzai_default_issues_tracker_cache] ||= {} + def external_issues_cached(attribute) + return project.public_send(attribute) unless RequestStore.active? + + cached_attributes = RequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} } + cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? + cached_attributes[project.id][attribute] end end end diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 54c5f9a71a4..4d1bc687696 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -4,6 +4,10 @@ module Banzai # issues that do not exist are ignored. # # This filter supports cross-project references. + # + # When external issues tracker like Jira is activated we should not + # use issue reference pattern, but we should still be able + # to reference issues from other GitLab projects. class IssueReferenceFilter < AbstractReferenceFilter self.reference_type = :issue @@ -11,6 +15,10 @@ module Banzai Issue end + def uses_reference_pattern? + context[:project].default_issues_tracker? + end + def find_object(project, iid) issues_per_project[project][iid] end 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 7116c09fb21..2f9343fadaf 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -7,12 +7,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do IssuesHelper end - let(:project) { create(:jira_project) } - - context 'JIRA issue references' do - let(:issue) { ExternalIssue.new('JIRA-123', project) } - let(:reference) { issue.to_reference } - + shared_examples_for "external issue tracker" do it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end @@ -20,6 +15,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Issue #{reference}" + expect(filter(act).to_html).to eq exp end end @@ -33,25 +29,30 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do it 'links to a valid reference' do doc = filter("Issue #{reference}") + issue_id = doc.css('a').first.attr("data-external-issue") + expect(doc.css('a').first.attr('href')) - .to eq helper.url_for_issue(reference, project) + .to eq helper.url_for_issue(issue_id, project) end it 'links to the external tracker' do doc = filter("Issue #{reference}") + link = doc.css('a').first.attr('href') + issue_id = doc.css('a').first.attr("data-external-issue") - expect(link).to eq "http://jira.example/browse/#{reference}" + expect(link).to eq(helper.url_for_issue(issue_id, project)) end it 'links with adjacent text' do doc = filter("Issue (#{reference}.)") + expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) end it 'includes a title attribute' do doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker" + expect(doc.css('a').first.attr('title')).to include("Issue in #{project.issues_tracker.title}") end it 'escapes the title attribute' do @@ -69,9 +70,60 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do it 'supports an :only_path context' do doc = filter("Issue #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + issue_id = doc.css('a').first["data-external-issue"] + + expect(link).to eq helper.url_for_issue(issue_id, project, only_path: true) + end + + context 'with RequestStore enabled' do + let(:reference_filter) { HTML::Pipeline.new([described_class]) } + + before { allow(RequestStore).to receive(:active?).and_return(true) } + + it 'queries the collection on the first call' do + expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original + expect_any_instance_of(Project).to receive(:issue_reference_pattern).once.and_call_original + + not_cached = reference_filter.call("look for #{reference}", { project: project }) + + expect_any_instance_of(Project).not_to receive(:default_issues_tracker?) + expect_any_instance_of(Project).not_to receive(:issue_reference_pattern) + + cached = reference_filter.call("look for #{reference}", { project: project }) - expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true) + # Links must be the same + expect(cached[:output].css('a').first[:href]).to eq(not_cached[:output].css('a').first[:href]) + end + end + end + + context "redmine project" do + let(:project) { create(:redmine_project) } + let(:issue) { ExternalIssue.new("#123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "jira project" do + let(:project) { create(:jira_project) } + let(:reference) { issue.to_reference } + + context "with right markdown" do + let(:issue) { ExternalIssue.new("JIRA-123", project) } + + it_behaves_like "external issue tracker" + end + + context "with wrong markdown" do + let(:issue) { ExternalIssue.new("#123", project) } + + it "ignores reference" do + exp = act = "Issue #{reference}" + expect(filter(act).to_html).to eq exp + end end end end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index fce86a9b6ad..a2025672ad9 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -25,9 +25,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do let(:reference) { issue.to_reference } it 'ignores valid references when using non-default tracker' do - expect_any_instance_of(described_class).to receive(:find_object). - with(project, issue.iid). - and_return(nil) + allow(project).to receive(:default_issues_tracker?).and_return(false) exp = act = "Issue #{reference}" expect(reference_filter(act).to_html).to eq exp @@ -199,19 +197,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end end - context 'referencing external issues' do - let(:project) { create(:redmine_project) } - - it 'renders internal issue IDs as external issue links' do - doc = reference_filter('#1') - link = doc.css('a').first - - expect(link.attr('data-reference-type')).to eq('external_issue') - expect(link.attr('title')).to eq('Issue in Redmine') - expect(link.attr('data-external-issue')).to eq('1') - end - end - describe '#issues_per_Project' do context 'using an internal issue tracker' do it 'returns a Hash containing the issues per project' do diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index 4fc3b065592..ebba6e14578 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -10,21 +10,6 @@ describe ExternalIssue, models: true do it { is_expected.to include_module(Referable) } end - describe '.reference_pattern' do - it 'allows underscores in the project name' do - expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' - end - - it 'allows numbers in the project name' do - expect(ExternalIssue.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' - end - - it 'requires the project name to begin with A-Z' do - expect(ExternalIssue.reference_pattern.match('3EXT_EXT-1234')).to eq nil - expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' - end - end - describe '#to_reference' do it 'returns a String reference to the object' do expect(issue.to_reference).to eq issue.id diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index b48a3176007..6ff32aea018 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -30,6 +30,15 @@ describe JiraService, models: true do end end + describe '#reference_pattern' do + it_behaves_like 'allows project key on reference pattern' + + it 'does not allow # on the code' do + expect(subject.reference_pattern.match('#123')).to be_nil + expect(subject.reference_pattern.match('1#23#12')).to be_nil + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index b8679cd2563..0a7b237a051 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -26,4 +26,12 @@ describe RedmineService, models: true do it { is_expected.not_to validate_presence_of(:new_issue_url) } end end + + describe '#reference_pattern' do + it_behaves_like 'allows project key on reference pattern' + + it 'does allow # on the reference' do + expect(subject.reference_pattern.match('#123')[:issue]).to eq('123') + end + end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8dda34c7a03..ad5170afc21 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -415,7 +415,7 @@ describe GitPushService, services: true do it "doesn't close issues when external issue tracker is in use" do allow_any_instance_of(Project).to receive(:default_issues_tracker?). and_return(false) - external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid) + external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid, reference_pattern: project.issue_reference_pattern) allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(external_issue_tracker) # The push still shouldn't create cross-reference notes. @@ -484,30 +484,46 @@ describe GitPushService, services: true do end context "closing an issue" do - let(:message) { "this is some work.\n\ncloses JIRA-1" } - - it "initiates one api call to jira server to close the issue" do - transition_body = { - transition: { - id: '2' - } - }.to_json - - execute_service(project, commit_author, @oldrev, @newrev, @ref ) - expect(WebMock).to have_requested(:post, jira_api_transition_url).with( - body: transition_body - ).once + let(:message) { "this is some work.\n\ncloses JIRA-1" } + let(:transition_body) { { transition: { id: '2' } }.to_json } + let(:comment_body) { { body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]." }.to_json } + + context "using right markdown" do + it "initiates one api call to jira server to close the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).to have_requested(:post, jira_api_transition_url).with( + body: transition_body + ).once + end + + it "initiates one api call to jira server to comment on the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).to have_requested(:post, jira_api_comment_url).with( + body: comment_body + ).once + end end - it "initiates one api call to jira server to comment on the issue" do - comment_body = { - body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]." - }.to_json + context "using wrong markdown" do + let(:message) { "this is some work.\n\ncloses #1" } - execute_service(project, commit_author, @oldrev, @newrev, @ref ) - expect(WebMock).to have_requested(:post, jira_api_comment_url).with( - body: comment_body - ).once + it "does not initiates one api call to jira server to close the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).not_to have_requested(:post, jira_api_transition_url).with( + body: transition_body + ) + end + + it "does not initiates one api call to jira server to comment on the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).not_to have_requested(:post, jira_api_comment_url).with( + body: comment_body + ).once + end end end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 9163c0c104e..f93d7732a9a 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -74,6 +74,18 @@ describe MergeRequests::MergeService, services: true do service.execute(merge_request) end + + context "wrong issue markdown" do + it 'does not close issues on JIRA issue tracker' do + jira_issue = ExternalIssue.new('#123', project) + commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") + allow(merge_request).to receive(:commits).and_return([commit]) + + expect_any_instance_of(JiraService).not_to receive(:close_issue) + + service.execute(merge_request) + end + end end end diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb index b6d7436c360..e70b3963d9d 100644 --- a/spec/support/issue_tracker_service_shared_example.rb +++ b/spec/support/issue_tracker_service_shared_example.rb @@ -5,3 +5,18 @@ RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) } it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) } end + +RSpec.shared_examples 'allows project key on reference pattern' do |url_attr| + it 'allows underscores in the project name' do + expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + end + + it 'allows numbers in the project name' do + expect(subject.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' + end + + it 'requires the project name to begin with A-Z' do + expect(subject.reference_pattern.match('3EXT_EXT-1234')).to eq nil + expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + end +end -- cgit v1.2.1 From ce78bdf96bf5744783f06a60d3c7078b4534390d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 18:46:47 +0300 Subject: Its time for 8.14 in master Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG.md | 2 ++ VERSION | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c866696889e..30b18ca84aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. +## 8.14.0 (2016-11-22) + ## 8.13.0 (2016-10-22) - Fix save button on project pipeline settings page. (!6955) diff --git a/VERSION b/VERSION index dff4cd02d5f..919f462addc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.13.0-pre +8.14.0-pre -- cgit v1.2.1 From 257c58ebeb2de8fe44f83c751b91e09c306aa588 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 18 Oct 2016 19:03:10 +0200 Subject: Set webkit-overflow-scrolling to auto for children of body. --- CHANGELOG.md | 1 + app/assets/stylesheets/framework/files.scss | 1 - app/assets/stylesheets/framework/layout.scss | 12 ++++++++++++ app/assets/stylesheets/pages/diff.scss | 1 - 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c866696889e..2c4d94c4cc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ Please view this file on the master branch, on stable branches it's out of date. - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling - Delete dynamic environments + - Fix buggy iOS tooltip layering behavior. - Make guests unable to view MRs on private projects ## 8.12.7 diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 13c1bbf0359..f49d7b92a00 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -167,7 +167,6 @@ */ &.code { padding: 0; - -webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987 } } } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 8bb047db2dd..7baa4296abf 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -27,3 +27,15 @@ body { .container-limited { max-width: $fixed-layout-width; } + + +/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch, +which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side +effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children +of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */ + +.navbar, +.page-gutter, +.page-with-sidebar { + -webkit-overflow-scrolling: auto; +} diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index bdc82a8f0f5..fe6421f8b3f 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -52,7 +52,6 @@ background: #fff; color: #333; border-radius: 0 0 3px 3px; - -webkit-overflow-scrolling: auto; .unfold { cursor: pointer; -- cgit v1.2.1 From d820c090ec85f8118e4cea75bd63d800e812ea25 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 12:04:38 -0300 Subject: Add GroupLabel model --- app/models/group.rb | 1 + app/models/group_label.rb | 5 +++++ db/migrate/20160919144305_add_type_to_labels.rb | 9 +++++++++ db/migrate/20160919145149_add_group_id_to_labels.rb | 13 +++++++++++++ db/schema.rb | 4 ++++ spec/models/group_label_spec.rb | 11 +++++++++++ spec/models/group_spec.rb | 1 + 7 files changed, 44 insertions(+) create mode 100644 app/models/group_label.rb create mode 100644 db/migrate/20160919144305_add_type_to_labels.rb create mode 100644 db/migrate/20160919145149_add_group_id_to_labels.rb create mode 100644 spec/models/group_label_spec.rb diff --git a/app/models/group.rb b/app/models/group.rb index a2f88cca828..00a595d2705 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -19,6 +19,7 @@ class Group < Namespace has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: :project has_many :notification_settings, dependent: :destroy, as: :source + has_many :labels, class_name: 'GroupLabel' validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :visibility_level_allowed_by_projects diff --git a/app/models/group_label.rb b/app/models/group_label.rb new file mode 100644 index 00000000000..a854d075820 --- /dev/null +++ b/app/models/group_label.rb @@ -0,0 +1,5 @@ +class GroupLabel < Label + belongs_to :group + + validates :group, presence: true +end diff --git a/db/migrate/20160919144305_add_type_to_labels.rb b/db/migrate/20160919144305_add_type_to_labels.rb new file mode 100644 index 00000000000..43aac7846d3 --- /dev/null +++ b/db/migrate/20160919144305_add_type_to_labels.rb @@ -0,0 +1,9 @@ +class AddTypeToLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :labels, :type, :string + end +end diff --git a/db/migrate/20160919145149_add_group_id_to_labels.rb b/db/migrate/20160919145149_add_group_id_to_labels.rb new file mode 100644 index 00000000000..05e21af0584 --- /dev/null +++ b/db/migrate/20160919145149_add_group_id_to_labels.rb @@ -0,0 +1,13 @@ +class AddGroupIdToLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_column :labels, :group_id, :integer + add_foreign_key :labels, :namespaces, column: :group_id, on_delete: :cascade + add_concurrent_index :labels, :group_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 5ce855fe08f..37f0be0e834 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -529,8 +529,11 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.string "description" t.integer "priority" t.text "description_html" + t.string "type" + t.integer "group_id" end + add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree add_index "labels", ["title"], name: "index_labels_on_title", using: :btree @@ -1213,6 +1216,7 @@ ActiveRecord::Schema.define(version: 20161017095000) do add_foreign_key "boards", "projects" add_foreign_key "issue_metrics", "issues", on_delete: :cascade + add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "lists", "boards" add_foreign_key "lists", "labels" add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb new file mode 100644 index 00000000000..a82d23bcc0b --- /dev/null +++ b/spec/models/group_label_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe GroupLabel, models: true do + describe 'relationships' do + it { is_expected.to belong_to(:group) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:group) } + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0b3ef9b98fd..ac862055ebc 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -12,6 +12,7 @@ describe Group, models: true do it { is_expected.to have_many(:project_group_links).dependent(:destroy) } it { is_expected.to have_many(:shared_projects).through(:project_group_links) } it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + it { is_expected.to have_many(:labels).class_name('GroupLabel') } describe '#members & #requesters' do let(:requester) { create(:user) } -- cgit v1.2.1 From 52e0c3b565b7b177abbf8ea3bc573651060179a2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 16:49:08 -0300 Subject: Add CRUD for Group Labels --- app/assets/javascripts/dispatcher.js.es6 | 2 + app/controllers/groups/labels_controller.rb | 67 ++++++++++++++++++++++++++++ app/helpers/labels_helper.rb | 28 +++++++++--- app/policies/group_policy.rb | 1 + app/views/groups/labels/_form.html.haml | 33 ++++++++++++++ app/views/groups/labels/_label.html.haml | 53 ++++++++++++++++++++++ app/views/groups/labels/_label_row.html.haml | 6 +++ app/views/groups/labels/destroy.js.haml | 2 + app/views/groups/labels/edit.html.haml | 8 ++++ app/views/groups/labels/index.html.haml | 24 ++++++++++ app/views/groups/labels/new.html.haml | 9 ++++ app/views/layouts/nav/_group.html.haml | 4 ++ config/routes/group.rb | 6 +++ 13 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 app/controllers/groups/labels_controller.rb create mode 100644 app/views/groups/labels/_form.html.haml create mode 100644 app/views/groups/labels/_label.html.haml create mode 100644 app/views/groups/labels/_label_row.html.haml create mode 100644 app/views/groups/labels/destroy.js.haml create mode 100644 app/views/groups/labels/edit.html.haml create mode 100644 app/views/groups/labels/index.html.haml create mode 100644 app/views/groups/labels/new.html.haml diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 73691f40c74..afc0d6f8c62 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -168,6 +168,8 @@ shortcut_handler = new ShortcutsNavigation(); new ShortcutsBlob(true); break; + case 'groups:labels:new': + case 'groups:labels:edit': case 'projects:labels:new': case 'projects:labels:edit': new Labels(); diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb new file mode 100644 index 00000000000..449298f51a8 --- /dev/null +++ b/app/controllers/groups/labels_controller.rb @@ -0,0 +1,67 @@ +class Groups::LabelsController < Groups::ApplicationController + include ToggleSubscriptionAction + + before_action :label, only: [:edit, :update, :destroy, :toggle_subscription] + before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] + + respond_to :html + + def index + @labels = @group.labels.page(params[:page]) + end + + def new + @label = @group.labels.new + end + + def create + @label = @group.labels.create(label_params) + + if @label.valid? + redirect_to group_labels_path(@group) + else + render :new + end + end + + def edit + end + + def update + if @label.update_attributes(label_params) + redirect_to group_labels_path(@group) + else + render :edit + end + end + + def destroy + @label.destroy + + respond_to do |format| + format.html do + redirect_to group_labels_path(@group), notice: 'Label was removed' + end + format.js + end + end + + protected + + def authorize_admin_labels! + return render_404 unless can?(current_user, :admin_label, @group) + end + + def authorize_read_labels! + return render_404 unless can?(current_user, :read_label, @group) + end + + def label + @label ||= @group.labels.find(params[:id]) + end + alias_method :subscribable_resource, :label + + def label_params + params.require(:label).permit(:title, :description, :color) + end +end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index b9f3d6c75c2..540eb6dd493 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -43,11 +43,29 @@ module LabelsHelper end end - def label_filter_path(project, label, type: issue) - send("namespace_project_#{type.to_s.pluralize}_path", - project.namespace, - project, - label_name: [label.name]) + def link_to_group_label(label, group: nil, type: :issue, tooltip: true, css_class: nil, &block) + group ||= @group || label.group + link = label_filter_path(group, label, type: type) + + if block_given? + link_to link, class: css_class, &block + else + link_to render_colored_label(label, tooltip: tooltip), link, class: css_class + end + end + + def label_filter_path(subject, label, type: issue) + case subject + when Project + send("namespace_project_#{type.to_s.pluralize}_path", + subject.namespace, + subject, + label_name: [label.name]) + when Group + send("#{type.to_s.pluralize}_group_path", + subject, + label_name: [label.name]) + end end def project_label_names diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 97ff6233968..b65fb68cd88 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -19,6 +19,7 @@ class GroupPolicy < BasePolicy if master can! :create_projects can! :admin_milestones + can! :admin_label end # Only group owner and administrators can admin group diff --git a/app/views/groups/labels/_form.html.haml b/app/views/groups/labels/_form.html.haml new file mode 100644 index 00000000000..008b5fb9ba1 --- /dev/null +++ b/app/views/groups/labels/_form.html.haml @@ -0,0 +1,33 @@ += form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| + = form_errors(@label) + + .form-group + = f.label :title, class: 'control-label' + .col-sm-10 + = f.text_field :title, class: "form-control", required: true, autofocus: true + .form-group + = f.label :description, class: 'control-label' + .col-sm-10 + = f.text_field :description, class: "form-control js-quick-submit" + .form-group + = f.label :color, "Background color", class: 'control-label' + .col-sm-10 + .input-group + .input-group-addon.label-color-preview   + = f.color_field :color, class: "form-control" + .help-block + Choose any color. + %br + Or you can choose one of suggested colors below + + .suggest-colors + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + + .form-actions + - if @label.persisted? + = f.submit 'Save changes', class: 'btn btn-save js-save-button' + - else + = f.submit 'Create Label', class: 'btn btn-create js-save-button' + = link_to "Cancel", group_labels_path(@group), class: 'btn btn-cancel' diff --git a/app/views/groups/labels/_label.html.haml b/app/views/groups/labels/_label.html.haml new file mode 100644 index 00000000000..b9aab76f057 --- /dev/null +++ b/app/views/groups/labels/_label.html.haml @@ -0,0 +1,53 @@ +- label_css_id = dom_id(label) +- open_issues_count = label.open_issues_count(current_user) +- open_merge_requests_count = label.open_merge_requests_count + +%li{id: label_css_id, data: { id: label.id } } + = render 'label_row', label: label + + .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown + %button.btn.btn-default.label-options-toggle{ data: { toggle: 'dropdown' } } + Options + %span.caret + .dropdown-menu.dropdown-menu-align-right + %ul + %li + = link_to_group_label(label, type: :merge_request) do + = pluralize open_merge_requests_count, 'merge request' + %li + = link_to_group_label(label) do + = pluralize open_issues_count, 'open issue' + - if current_user + %li.label-subscription{ data: { url: toggle_subscription_group_label_path(@group, label) } } + %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span= label_subscription_toggle_button_text(label) + - if can? current_user, :admin_label, @group + %li + = link_to 'Edit', edit_group_label_path(@group, label) + %li + = link_to 'Delete', group_label_path(@group, label), title: 'Delete', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + + .pull-right.hidden-xs.hidden-sm.hidden-md + = link_to_group_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = pluralize open_merge_requests_count, 'merge request' + = link_to_group_label(label, css_class: 'btn btn-transparent btn-action') do + = pluralize open_issues_count, 'open issue' + + - if current_user + .label-subscription.inline{ data: { url: toggle_subscription_group_label_path(@group, label) } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span.sr-only= label_subscription_toggle_button_text(label) + = icon('eye', class: 'label-subscribe-button-icon') + = icon('spinner spin', class: 'label-subscribe-button-loading') + + - if can? current_user, :admin_label, @group + = link_to edit_group_label_path(@group, label), title: 'Edit', class: 'btn btn-transparent btn-action', data: {toggle: 'tooltip'} do + %span.sr-only Edit + = icon('pencil-square-o') + = link_to group_label_path(@group, label), title: 'Delete', class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?', toggle: 'tooltip'} do + %span.sr-only Delete + = icon('trash-o') + + - if current_user + :javascript + new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/groups/labels/_label_row.html.haml b/app/views/groups/labels/_label_row.html.haml new file mode 100644 index 00000000000..e21fac25b01 --- /dev/null +++ b/app/views/groups/labels/_label_row.html.haml @@ -0,0 +1,6 @@ +%span.label-row + %span.label-name + = link_to_group_label(label, tooltip: false) + - if label.description + %span.label-description + = markdown(label.description, pipeline: :single_line) diff --git a/app/views/groups/labels/destroy.js.haml b/app/views/groups/labels/destroy.js.haml new file mode 100644 index 00000000000..3dfbfc77c0d --- /dev/null +++ b/app/views/groups/labels/destroy.js.haml @@ -0,0 +1,2 @@ +- if @group.labels.empty? + $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml new file mode 100644 index 00000000000..e247393abd5 --- /dev/null +++ b/app/views/groups/labels/edit.html.haml @@ -0,0 +1,8 @@ +- page_title 'Edit', @label.name, 'Labels' + +%h3.page-title + = icon('folder-open') + Edit Group Label +%hr + += render 'form', url: group_label_path(@group, @label) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml new file mode 100644 index 00000000000..d9f1d350cb3 --- /dev/null +++ b/app/views/groups/labels/index.html.haml @@ -0,0 +1,24 @@ +- page_title 'Labels' + +.top-area.adjust + .nav-text + Labels can be applied to issues and merge requests. + + .nav-controls + - if can?(current_user, :admin_label, @group) + = link_to new_group_label_path(@group), class: "btn btn-new" do + New label + +.labels + - hide = @group.labels.empty? || (params[:page].present? && params[:page] != '1') + .group-labels + %h5{ class: ('hide' if hide) } + = icon('folder-open') + Group Labels + - if @labels.present? + %ul.content-list.manage-labels-list.js-group-labels + = render partial: 'label', collection: @labels, as: :label + = paginate @labels, theme: 'gitlab' + - else + .nothing-here-block + No labels created yet. diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml new file mode 100644 index 00000000000..01a50607db4 --- /dev/null +++ b/app/views/groups/labels/new.html.haml @@ -0,0 +1,9 @@ +- page_title 'New Group Label' +- header_title group_title(@group, 'Labels', group_labels_path(@group)) + +%h3.page-title + = icon('folder-open') + New Group Label +%hr + += render 'form', url: group_labels_path diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 27ac1760166..f7edb47b666 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -13,6 +13,10 @@ = link_to activity_group_path(@group), title: 'Activity' do %span Activity + = nav_link(controller: [:group, :labels]) do + = link_to group_labels_path(@group), title: 'Labels' do + %span + Labels = nav_link(controller: [:group, :milestones]) do = link_to group_milestones_path(@group), title: 'Milestones' do %span diff --git a/config/routes/group.rb b/config/routes/group.rb index 06b464d79c8..7bb9aa50875 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -28,5 +28,11 @@ resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? Date: Mon, 19 Sep 2016 17:16:16 -0300 Subject: Add LabelsFinder --- app/finders/labels_finder.rb | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 app/finders/labels_finder.rb diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb new file mode 100644 index 00000000000..cf7018cf8a2 --- /dev/null +++ b/app/finders/labels_finder.rb @@ -0,0 +1,58 @@ +class LabelsFinder + def initialize(current_user, params = {}) + @current_user = current_user + @params = params + end + + def execute + items = init_collection + items = with_title(items) + sort(items) + end + + private + + attr_reader :current_user, :params + + def init_collection + label_ids = [] + label_ids << Label.where(group_id: projects.where.not(group: nil).select(:namespace_id)).select(:id) + label_ids << Label.where(project_id: projects).select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + Label.where("labels.id IN (#{union.to_sql})") + .reorder(title: :asc) + end + + def with_title(items) + items = items.where(title: title) if title.present? + items + end + + def sort(items) + items.reorder(title: :asc) + end + + def project_id + params[:project_id].presence + end + + def title + params[:title].presence + end + + def projects + return @projects if defined?(@projects) + + if project_id + @projects = ProjectsFinder.new.execute(current_user) + .where(id: project_id) + .reorder(nil) + else + @projects = Project.none + end + + @projects + end +end -- cgit v1.2.1 From 398ab263fd08a5d9d7b19c5b3d06f33814a474eb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 17:21:39 -0300 Subject: Allow users to apply group labels on Issues/MRs --- app/controllers/dashboard/labels_controller.rb | 5 ++++- app/controllers/projects/issues_controller.rb | 9 ++++++--- app/controllers/projects/labels_controller.rb | 2 +- .../projects/merge_requests_controller.rb | 5 ++++- app/finders/issuable_finder.rb | 4 +++- app/models/label.rb | 6 +++++- app/services/issuable_base_service.rb | 15 ++++++++++----- app/services/projects/autocomplete_service.rb | 2 +- app/views/shared/issuable/_filter.html.haml | 9 ++++----- app/views/shared/issuable/_form.html.haml | 2 +- spec/services/issues/create_service_spec.rb | 21 +++++++++++++++++++++ 11 files changed, 60 insertions(+), 20 deletions(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 2a88350a4ca..797f8503b2d 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,6 +1,9 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index - labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title) + labels = LabelsFinder.new(current_user, project_id: projects) + .execute + .select(:id, :title, :color) + .uniq(:title) respond_to do |format| format.json { render json: labels } diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 96041b07647..d85bc85092f 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -25,8 +25,7 @@ class Projects::IssuesController < Projects::ApplicationController def index @issues = issues_collection @issues = @issues.page(params[:page]) - - @labels = @project.labels.where(title: params[:label_name]) + @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence respond_to do |format| format.html @@ -45,11 +44,15 @@ class Projects::IssuesController < Projects::ApplicationController assignee_id: "" ) - @issue = @noteable = @project.issues.new(issue_params) + @issue = @noteable = @project.issues.new(issue_params) + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + respond_with(@issue) end def edit + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + respond_with(@issue) end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index a6626df4826..35224f965bf 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -17,7 +17,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @project.labels + render json: LabelsFinder.new(current_user, project_id: @project.id).execute end end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 04386258c19..c8970155497 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -40,7 +40,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.preload(:target_project) - @labels = @project.labels.where(title: params[:label_name]) + @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence respond_to do |format| format.html @@ -263,6 +263,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @source_project = @merge_request.source_project @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute end def update @@ -575,6 +576,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + define_pipelines_vars end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9f170428100..37151f8d134 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -274,8 +274,10 @@ class IssuableFinder items = items.without_label else items = items.with_label(label_names, params[:sort]) + if projects - items = items.where(labels: { project_id: projects }) + label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) + items = items.where(labels: { id: label_ids }) end end end diff --git a/app/models/label.rb b/app/models/label.rb index e8e12e2904e..295c5bfaf70 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -23,7 +23,7 @@ class Label < ActiveRecord::Base has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' validates :color, color: true, allow_blank: false - validates :project, presence: true, unless: Proc.new { |service| service.template? } + validates :project, presence: true, if: :project_label? # Don't allow ',' for label titles validates :title, @@ -127,6 +127,10 @@ class Label < ActiveRecord::Base private + def project_label? + type.blank? && !template? + end + def label_format_reference(format = :id) raise StandardError, 'Unknown format' unless [:id, :name].include?(format) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 57d521f2fea..b3e4f8dcf27 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -80,17 +80,18 @@ class IssuableBaseService < BaseService def filter_labels_in_param(key) return if params[key].to_a.empty? - params[key] = project.labels.where(id: params[key]).pluck(:id) + params[key] = available_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 + params[:label_ids] = labels.split(',').map do |label_name| + label = available_labels.find_by(title: title).select(:id) + label ||= project.labels.create(title: label_name.strip, color: Label::DEFAULT_COLOR) + + label.id end end @@ -111,6 +112,10 @@ class IssuableBaseService < BaseService new_label_ids end + def available_labels + LabelsFinder.new(current_user, project_id: @project.id).execute + end + def merge_slash_commands_into_params!(issuable) description, command_params = SlashCommands::InterpretService.new(project, current_user). diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index f578f8dbea2..015f2828921 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -13,7 +13,7 @@ module Projects end def labels - @project.labels.select([:title, :color]) + LabelsFinder.new(current_user, project_id: project.id).execute.select([:title, :color]) end def commands(noteable, type) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 31620297be0..8c2036a1cde 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -77,11 +77,10 @@ = hidden_field_tag :state_event, params[:state_event] .filter-item.inline = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save" - - - if !@labels.nil? - .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } - - if @labels.any? - = render "shared/labels_row", labels: @labels + - has_labels = @labels && @labels.any? + .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } + - if has_labels + = render 'shared/labels_row', labels: @labels :javascript new UsersSelect(); diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index a7944a60130..34c66a17303 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -95,7 +95,7 @@ .issuable-form-select-holder = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - - has_labels = issuable.project.labels.any? + - has_labels = @labels && @labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 1050502fa19..5c0331ebe66 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -67,6 +67,27 @@ describe Issues::CreateService, services: true do expect(Todo.where(attributes).count).to eq 1 end + context 'when label belongs to project group' do + let(:group) { create(:group) } + let(:group_labels) { create_pair(:group_label, group: group) } + + let(:opts) do + { + title: 'Title', + description: 'Description', + label_ids: group_labels.map(&:id) + } + end + + before do + project.update(group: group) + end + + it 'assigns group labels' do + expect(issue.labels).to match_array group_labels + end + end + context 'when label belongs to different project' do let(:label) { create(:label) } -- cgit v1.2.1 From bf9d928b45516e716b0f7f099361ca03aa1454f8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 17:34:27 -0300 Subject: Allow user to create a board list based on a group label --- app/controllers/projects/issues_controller.rb | 5 ++++- app/services/boards/lists/create_service.rb | 6 +++++- spec/services/boards/lists/create_service_spec.rb | 4 ++++ spec/services/boards/lists/generate_service_spec.rb | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index d85bc85092f..4f6d7ca80df 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -25,7 +25,10 @@ class Projects::IssuesController < Projects::ApplicationController def index @issues = issues_collection @issues = @issues.page(params[:page]) - @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence + + if params[:label_name].presence + @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute + end respond_to do |format| format.html diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index abc7aeece39..fe0d762ccd2 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -3,7 +3,7 @@ module Boards class CreateService < BaseService def execute(board) List.transaction do - label = project.labels.find(params[:label_id]) + label = available_labels.find(params[:label_id]) position = next_position(board) create_list(board, label, position) @@ -12,6 +12,10 @@ module Boards private + def available_labels + LabelsFinder.new(current_user, project_id: project.id).execute + end + def next_position(board) max_position = board.lists.movable.maximum(:position) max_position.nil? ? 0 : max_position.succ diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index e7806add916..a7e9efcf93f 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -9,6 +9,10 @@ describe Boards::Lists::CreateService, services: true do subject(:service) { described_class.new(project, user, label_id: label.id) } + before do + project.team << [user, :developer] + end + context 'when board lists is empty' do it 'creates a new list at beginning of the list' do list = service.execute(board) diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 8b2f5e81338..ed0337662af 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -8,6 +8,10 @@ describe Boards::Lists::GenerateService, services: true do subject(:service) { described_class.new(project, user) } + before do + project.team << [user, :developer] + end + context 'when board lists is empty' do it 'creates the default lists' do expect { service.execute(board) }.to change(board.lists, :count).by(2) -- cgit v1.2.1 From bdb7bf4b5188ffd68e54cbf671ba9ce1a4ffb1d1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:09:57 -0300 Subject: List group labels on project labels page --- app/assets/stylesheets/pages/labels.scss | 10 +- app/controllers/groups/labels_controller.rb | 20 +++- app/controllers/projects/application_controller.rb | 4 + app/controllers/projects/issues_controller.rb | 4 +- app/controllers/projects/labels_controller.rb | 11 ++- .../projects/merge_requests_controller.rb | 2 +- app/helpers/labels_helper.rb | 43 +++++++++ app/models/label.rb | 24 +++-- app/views/groups/labels/_form.html.haml | 2 +- app/views/groups/labels/_label.html.haml | 2 +- app/views/projects/labels/_form.html.haml | 2 +- app/views/projects/labels/_label.html.haml | 23 +++-- app/views/projects/labels/destroy.js.haml | 2 +- app/views/projects/labels/edit.html.haml | 3 +- app/views/projects/labels/index.html.haml | 53 +++++++---- app/views/projects/labels/new.html.haml | 3 +- app/views/shared/_label_row.html.haml | 3 +- .../projects/labels/update_prioritization_spec.rb | 104 +++++++++++++-------- 18 files changed, 226 insertions(+), 89 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 9bac6d46355..cbd009ccd07 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -140,6 +140,10 @@ color: $gray-light; } + .label-type { + opacity: 0.3; + } + li:hover { .draggable-handler { display: inline-block; @@ -148,7 +152,11 @@ } } -.other-labels { +.group-labels + .project-labels { + margin-top: 30px; +} + +.group-labels, .project-labels { .remove-priority { display: none; } diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 449298f51a8..0ec2fcda0ef 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -3,6 +3,7 @@ class Groups::LabelsController < Groups::ApplicationController before_action :label, only: [:edit, :update, :destroy, :toggle_subscription] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] + before_action :save_previous_label_path, only: [:edit] respond_to :html @@ -25,11 +26,12 @@ class Groups::LabelsController < Groups::ApplicationController end def edit + @previous_labels_path = previous_labels_path end def update if @label.update_attributes(label_params) - redirect_to group_labels_path(@group) + redirect_back_or_group_labels_path else render :edit end @@ -64,4 +66,20 @@ class Groups::LabelsController < Groups::ApplicationController def label_params params.require(:label).permit(:title, :description, :color) end + + def redirect_back_or_group_labels_path(options = {}) + redirect_to previous_labels_path, options + end + + def previous_labels_path + session.fetch(:previous_labels_path, fallback_path) + end + + def fallback_path + group_labels_path(@group) + end + + def save_previous_label_path + session[:previous_labels_path] = URI(request.referer || '').path + end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index b2ff36f6538..5daf4311cc8 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -44,6 +44,10 @@ class Projects::ApplicationController < ApplicationController @project end + def project_labels + @project_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + end + def repository @repository ||= project.repository end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4f6d7ca80df..1558426e1a4 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -48,13 +48,13 @@ class Projects::IssuesController < Projects::ApplicationController ) @issue = @noteable = @project.issues.new(issue_params) - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + @labels = project_labels respond_with(@issue) end def edit - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + @labels = project_labels respond_with(@issue) end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 35224f965bf..3db3c091da6 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -11,13 +11,15 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index + @project_labels = project_labels + @prioritized_labels = project_labels.prioritized + @group_labels = @project.group.labels.unprioritized if @project.group.present? @labels = @project.labels.unprioritized.page(params[:page]) - @prioritized_labels = @project.labels.prioritized respond_to do |format| format.html format.json do - render json: LabelsFinder.new(current_user, project_id: @project.id).execute + render json: @project_labels end end end @@ -68,6 +70,7 @@ class Projects::LabelsController < Projects::ApplicationController def destroy @label.destroy + @project_labels = project_labels respond_to do |format| format.html do @@ -80,6 +83,8 @@ class Projects::LabelsController < Projects::ApplicationController def remove_priority respond_to do |format| + label = project_labels.find(params[:id]) + if label.update_attribute(:priority, nil) format.json { render json: label } else @@ -92,7 +97,7 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do params[:label_ids].each_with_index do |label_id, index| - label = @project.labels.find_by_id(label_id) + label = project_labels.find_by_id(label_id) label.update_attribute(:priority, index) if label end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index c8970155497..291c3f64914 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -263,7 +263,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @source_project = @merge_request.source_project @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + @labels = project_labels end def update diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 540eb6dd493..3f0e502fbc9 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -68,6 +68,49 @@ module LabelsHelper end end + def can_admin_label(label) + subject = + case label + when GroupLabel then label.group + else label.project + end + + can?(current_user, :admin_label, subject) + end + + def edit_label_path(label) + case label + when GroupLabel then edit_group_label_path(label.group, label) + else edit_namespace_project_label_path(label.project.namespace, label.project, label) + end + end + + def destroy_label_path(label) + case label + when GroupLabel then group_label_path(label.group, label) + else namespace_project_label_path(label.project.namespace, label.project, label) + end + end + + def label_type_icon(label, options = {}) + title, icon = + case label + when GroupLabel then ['Group', 'folder-open'] + else ['Project', 'bookmark'] + end + + options[:class] ||= '' + options[:class] << ' has-tooltip js-label-type' + + content_tag :span, + class: options[:class], + data: { 'placement' => 'top' }, + title: title, + aria: { label: title } do + icon(icon, base: true) + end + end + def project_label_names @project.labels.pluck(:title) end diff --git a/app/models/label.rb b/app/models/label.rb index 295c5bfaf70..f43bebbf71b 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -101,16 +101,16 @@ class Label < ActiveRecord::Base end end - def open_issues_count(user = nil) - issues.visible_to_user(user).opened.count + def open_issues_count(user = nil, project = nil) + issues_count(user, project_id: project.try(:id) || project_id, state: 'opened') end - def closed_issues_count(user = nil) - issues.visible_to_user(user).closed.count + def closed_issues_count(user = nil, project = nil) + issues_count(user, project_id: project.try(:id) || project_id, state: 'closed') end - def open_merge_requests_count - merge_requests.opened.count + def open_merge_requests_count(user = nil, project = nil) + merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened') end def template? @@ -127,6 +127,18 @@ class Label < ActiveRecord::Base private + def issues_count(user, params = {}) + IssuesFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + .execute + .count + end + + def merge_requests_count(user, params = {}) + MergeRequestsFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + .execute + .count + end + def project_label? type.blank? && !template? end diff --git a/app/views/groups/labels/_form.html.haml b/app/views/groups/labels/_form.html.haml index 008b5fb9ba1..a0b44b0dcfb 100644 --- a/app/views/groups/labels/_form.html.haml +++ b/app/views/groups/labels/_form.html.haml @@ -30,4 +30,4 @@ = f.submit 'Save changes', class: 'btn btn-save js-save-button' - else = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to "Cancel", group_labels_path(@group), class: 'btn btn-cancel' + = link_to 'Cancel', @previous_labels_path, class: 'btn btn-cancel' diff --git a/app/views/groups/labels/_label.html.haml b/app/views/groups/labels/_label.html.haml index b9aab76f057..9faf90c303e 100644 --- a/app/views/groups/labels/_label.html.haml +++ b/app/views/groups/labels/_label.html.haml @@ -1,6 +1,6 @@ - label_css_id = dom_id(label) - open_issues_count = label.open_issues_count(current_user) -- open_merge_requests_count = label.open_merge_requests_count +- open_merge_requests_count = label.open_merge_requests_count(current_user) %li{id: label_css_id, data: { id: label.id } } = render 'label_row', label: label diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 6ab6ae50389..5f7be074f25 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -30,4 +30,4 @@ = f.submit 'Save changes', class: 'btn btn-save js-save-button' - else = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 71f7f354d72..2b7b79390f7 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -1,4 +1,7 @@ - label_css_id = dom_id(label) +- open_issues_count = label.open_issues_count(current_user, @project) +- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) + %li{id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label @@ -10,25 +13,25 @@ %ul %li = link_to_label(label, type: :merge_request) do - = pluralize label.open_merge_requests_count, 'merge request' + = pluralize open_merge_requests_count, 'merge request' %li = link_to_label(label) do - = pluralize label.open_issues_count(current_user), 'open issue' + = pluralize open_issues_count, 'open issue' - if current_user %li.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } %span= label_subscription_toggle_button_text(label) - - if can? current_user, :admin_label, @project + - if can_admin_label(label) %li - = link_to "Edit", edit_namespace_project_label_path(@project.namespace, @project, label) + = link_to 'Edit', edit_label_path(label) %li - = link_to "Delete", namespace_project_label_path(@project.namespace, @project, label), title: "Delete", method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} .pull-right.hidden-xs.hidden-sm.hidden-md = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do - = pluralize label.open_merge_requests_count, 'merge request' + = pluralize open_merge_requests_count, 'merge request' = link_to_label(label, css_class: 'btn btn-transparent btn-action') do - = pluralize label.open_issues_count(current_user), 'open issue' + = pluralize open_issues_count, 'open issue' - if current_user .label-subscription.inline{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } @@ -37,11 +40,11 @@ = icon('eye', class: 'label-subscribe-button-icon') = icon('spinner spin', class: 'label-subscribe-button-loading') - - if can? current_user, :admin_label, @project - = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do + - if can_admin_label(label) + = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') - = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do + = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do %span.sr-only Delete = icon('trash-o') diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml index d59563b122a..0b9937c4808 100644 --- a/app/views/projects/labels/destroy.js.haml +++ b/app/views/projects/labels/destroy.js.haml @@ -1,2 +1,2 @@ -- if @project.labels.size == 0 +- if @project_labels.none? $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 52b187e7e58..c9ec371c3e1 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -4,6 +4,7 @@ %div{ class: container_class } %h3.page-title - Edit Label + = icon('bookmark') + Edit Project Label %hr = render 'form' diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index db66a0edbd8..286b58f57c2 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -14,25 +14,38 @@ New label .labels - - if can?(current_user, :admin_label, @project) + - unless @project_labels.empty? -# Only show it in the first page - - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1') - .prioritized-labels{ class: ('hide' if hide) } - %h5 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } - %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - - if @prioritized_labels.present? - = render @prioritized_labels - .other-labels + - hide = params[:page].present? && params[:page] != '1' - if can?(current_user, :admin_label, @project) - %h5{ class: ('hide' if hide) } Other Labels - - if @labels.present? - %ul.content-list.manage-labels-list.js-other-labels - = render @labels - = paginate @labels, theme: 'gitlab' - - else - .nothing-here-block - - if can?(current_user, :admin_label, @project) - Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. - - else - No labels created + .prioritized-labels{ class: ('hide' if hide) } + %h5 Prioritized Labels + %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } + %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet + - if @prioritized_labels.present? + = render partial: 'label', collection: @prioritized_labels, as: :label + + .group-labels{ class: ('hide' if hide || @project.group.blank?) } + %h5 + = icon('folder-open') + Group Labels + %ul.content-list.manage-labels-list.js-group-labels + %p.empty-message{ class: ('hidden' unless @group_labels.empty?) } No group labels + - if @group_labels.present? + = render partial: 'label', collection: @group_labels, as: :label + + .project-labels + %h5{ class: ('hide' if hide) } + = icon('bookmark') + Project Labels + %ul.content-list.manage-labels-list.js-project-labels + %p.empty-message{ class: ('hidden' unless @labels.empty?) } No project labels + - if @labels.present? + = render @labels + = paginate @labels, theme: 'gitlab' + - else + .nothing-here-block + - if can?(current_user, :admin_label, @project) + Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. + - else + No labels created yet. diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index a1bb66cfb6c..a1e2df6c55d 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -4,6 +4,7 @@ %div{ class: container_class } %h3.page-title - New Label + = icon('bookmark') + New Project Label %hr = render 'form' diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 6f593e8dff9..751b2d1c158 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -3,13 +3,14 @@ .draggable-handler = icon('bars') .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label), - dom_id: dom_id(label) } } + dom_id: dom_id(label), type: label.type } } %button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' } = icon('star-o') %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' } = icon('star') %span.label-name = link_to_label(label, tooltip: false) + = label_type_icon(label, class: "#{'hidden' if label.priority.blank?}" ) - if label.description %span.label-description = markdown_field(label, :description) diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index cb7495da8eb..21896f0a787 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -3,76 +3,106 @@ require 'spec_helper' feature 'Prioritize labels', feature: true do include WaitForAjax - context 'when project belongs to user' do - let(:user) { create(:user) } - let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:wontfix) { create(:label, project: project, title: 'wontfix') } + let!(:feature) { create(:group_label, group: group, title: 'feature') } - scenario 'user can prioritize a label', js: true do - bug = create(:label, title: 'bug') - wontfix = create(:label, title: 'wontfix') - - project.labels << bug - project.labels << wontfix + context 'when user belongs to project team' do + before do + project.team << [user, :developer] login_as user + end + + scenario 'user can prioritize a group label', js: true do visit namespace_project_labels_path(project.namespace, project) expect(page).to have_content('No prioritized labels yet') - page.within('.other-labels') do + page.within('.group-labels') do first('.js-toggle-priority').click wait_for_ajax - expect(page).not_to have_content('bug') + expect(page).not_to have_content('feature') end page.within('.prioritized-labels') do expect(page).not_to have_content('No prioritized labels yet') - expect(page).to have_content('bug') + expect(page).to have_content('feature') end end - scenario 'user can unprioritize a label', js: true do - bug = create(:label, title: 'bug', priority: 1) - wontfix = create(:label, title: 'wontfix') + scenario 'user can unprioritize a group label', js: true do + feature.update(priority: 1) - project.labels << bug - project.labels << wontfix + visit namespace_project_labels_path(project.namespace, project) - login_as user + page.within('.prioritized-labels') do + expect(page).to have_content('feature') + + first('.js-toggle-priority').click + wait_for_ajax + expect(page).not_to have_content('bug') + end + + page.within('.group-labels') do + expect(page).to have_content('feature') + end + end + + scenario 'user can prioritize a project label', js: true do visit namespace_project_labels_path(project.namespace, project) - expect(page).to have_content('bug') + expect(page).to have_content('No prioritized labels yet') + + page.within('.project-labels') do + first('.js-toggle-priority').click + wait_for_ajax + expect(page).not_to have_content('bug') + end + + page.within('.prioritized-labels') do + expect(page).not_to have_content('No prioritized labels yet') + expect(page).to have_content('bug') + end + end + + scenario 'user can unprioritize a project label', js: true do + bug.update(priority: 1) + + visit namespace_project_labels_path(project.namespace, project) page.within('.prioritized-labels') do + expect(page).to have_content('bug') + first('.js-toggle-priority').click wait_for_ajax expect(page).not_to have_content('bug') end - page.within('.other-labels') do + page.within('.project-labels') do expect(page).to have_content('bug') expect(page).to have_content('wontfix') end end scenario 'user can sort prioritized labels and persist across reloads', js: true do - bug = create(:label, title: 'bug', priority: 1) - wontfix = create(:label, title: 'wontfix', priority: 2) - - project.labels << bug - project.labels << wontfix + bug.update(priority: 1) + feature.update(priority: 2) - login_as user visit namespace_project_labels_path(project.namespace, project) expect(page).to have_content 'bug' + expect(page).to have_content 'feature' expect(page).to have_content 'wontfix' # Sort labels - find("#label_#{bug.id}").drag_to find("#label_#{wontfix.id}") + find("#project_label_#{bug.id}").drag_to find("#group_label_#{feature.id}") page.within('.prioritized-labels') do - expect(first('li')).to have_content('wontfix') + expect(first('li')).to have_content('feature') expect(page.all('li').last).to have_content('bug') end @@ -80,7 +110,7 @@ feature 'Prioritize labels', feature: true do wait_for_ajax page.within('.prioritized-labels') do - expect(first('li')).to have_content('wontfix') + expect(first('li')).to have_content('feature') expect(page.all('li').last).to have_content('bug') end end @@ -88,28 +118,26 @@ feature 'Prioritize labels', feature: true do context 'as a guest' do it 'does not prioritize labels' do - user = create(:user) guest = create(:user) - project = create(:project, name: 'test', namespace: user.namespace) - - create(:label, title: 'bug') login_as guest + visit namespace_project_labels_path(project.namespace, project) + expect(page).to have_content 'bug' + expect(page).to have_content 'wontfix' + expect(page).to have_content 'feature' expect(page).not_to have_css('.prioritized-labels') end end context 'as a non signed in user' do it 'does not prioritize labels' do - user = create(:user) - project = create(:project, name: 'test', namespace: user.namespace) - - create(:label, title: 'bug') - visit namespace_project_labels_path(project.namespace, project) + expect(page).to have_content 'bug' + expect(page).to have_content 'wontfix' + expect(page).to have_content 'feature' expect(page).not_to have_css('.prioritized-labels') end end -- cgit v1.2.1 From 9463551ece6c12574559a4768943ab90db7f617b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:17:04 -0300 Subject: Validates uniqueness of title unless label is a template --- app/models/label.rb | 6 ++---- spec/models/label_spec.rb | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index f43bebbf71b..be0c20479d5 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -26,10 +26,8 @@ class Label < ActiveRecord::Base validates :project, presence: true, if: :project_label? # Don't allow ',' for label titles - validates :title, - presence: true, - format: { with: /\A[^,]+\z/ }, - uniqueness: { scope: :project_id } + validates :title, presence: true, format: { with: /\A[^,]+\z/ } + validates :title, uniqueness: true, unless: :template? before_save :nullify_priority diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 5a5d1a5d60c..894021dc8e6 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -19,6 +19,7 @@ describe Label, models: true do describe 'validation' do it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_uniqueness_of(:title) } it 'validates color code' do expect(label).not_to allow_value('G-ITLAB').for(:color) -- cgit v1.2.1 From 7f2e29ff3da54c4525dc55b4447fea2963e17fd3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:18:50 -0300 Subject: Remove unused method LabelsHelper#project_label_names --- app/helpers/labels_helper.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 3f0e502fbc9..9df8d37af9e 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -111,10 +111,6 @@ module LabelsHelper end end - def project_label_names - @project.labels.pluck(:title) - end - def render_colored_label(label, label_suffix = '', tooltip: true) label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label_color) -- cgit v1.2.1 From 476c26deb22a6e958dc3251e9771560b058a34a3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:59:26 -0300 Subject: Replace label references with links for group labels --- lib/banzai/filter/label_reference_filter.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 8f262ef3d8d..3a09912f1be 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -9,7 +9,7 @@ module Banzai end def find_object(project, id) - project.labels.find(id) + find_labels(project).find(id) end def self.references_in(text, pattern = Label.reference_pattern) @@ -35,7 +35,17 @@ module Banzai return unless project label_params = label_params(label_id, label_name) - project.labels.find_by(label_params) + find_labels(project).find_by(label_params) + end + + def find_labels(project) + label_ids = [] + label_ids << project.group.labels.select(:id) if project.group.present? + label_ids << project.labels.select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + object_class.where("labels.id IN (#{union.to_sql})") end # Parameters to pass to `Label.find_by` based on the given arguments @@ -60,7 +70,7 @@ module Banzai end def object_link_text(object, matches) - if context[:project] == object.project + if object.project.nil? || object.project == context[:project] LabelsHelper.render_colored_label(object) else LabelsHelper.render_colored_cross_project_label(object) -- cgit v1.2.1 From cf14482a5aceb62c178c19cc70e9354dc23dd9e1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 01:08:04 -0300 Subject: LabelsFinder inherits from UnionFinder --- app/finders/labels_finder.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index cf7018cf8a2..a27ff56309b 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -1,11 +1,11 @@ -class LabelsFinder +class LabelsFinder < UnionFinder def initialize(current_user, params = {}) @current_user = current_user @params = params end def execute - items = init_collection + items = find_union(label_ids, Label) items = with_title(items) sort(items) end @@ -14,15 +14,10 @@ class LabelsFinder attr_reader :current_user, :params - def init_collection + def label_ids label_ids = [] label_ids << Label.where(group_id: projects.where.not(group: nil).select(:namespace_id)).select(:id) - label_ids << Label.where(project_id: projects).select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - Label.where("labels.id IN (#{union.to_sql})") - .reorder(title: :asc) + label_ids << Label.where(project_id: projects.select(:id)).select(:id) end def with_title(items) -- cgit v1.2.1 From baf47a0bd0e0563cbc99b3ae4b1336b8b3b4380a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 11:03:41 -0300 Subject: Remove project_labels from Projects::ApplicationController --- app/controllers/concerns/issuable_actions.rb | 5 ++ app/controllers/projects/application_controller.rb | 4 -- app/controllers/projects/issues_controller.rb | 6 +- app/controllers/projects/labels_controller.rb | 28 ++++---- .../projects/merge_requests_controller.rb | 1 - app/views/projects/labels/destroy.js.haml | 2 +- app/views/projects/labels/index.html.haml | 10 +-- .../controllers/projects/labels_controller_spec.rb | 80 ++++++++++++++++------ 8 files changed, 88 insertions(+), 48 deletions(-) diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index bb32bc502e6..27f1e91d865 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -2,6 +2,7 @@ module IssuableActions extend ActiveSupport::Concern included do + before_action :labels, only: [:new, :edit] before_action :authorize_destroy_issuable!, only: :destroy before_action :authorize_admin_issuable!, only: :bulk_update end @@ -25,6 +26,10 @@ module IssuableActions private + def labels + @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + end + def authorize_destroy_issuable! unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable) return access_denied! diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 5daf4311cc8..b2ff36f6538 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -44,10 +44,6 @@ class Projects::ApplicationController < ApplicationController @project end - def project_labels - @project_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute - end - def repository @repository ||= project.repository end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 1558426e1a4..9f18c8c03df 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -47,15 +47,11 @@ class Projects::IssuesController < Projects::ApplicationController assignee_id: "" ) - @issue = @noteable = @project.issues.new(issue_params) - @labels = project_labels - + @issue = @noteable = @project.issues.new(issue_params) respond_with(@issue) end def edit - @labels = project_labels - respond_with(@issue) end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 3db3c091da6..33c3b7f79c2 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -3,23 +3,23 @@ class Projects::LabelsController < Projects::ApplicationController before_action :module_enabled before_action :label, only: [:edit, :update, :destroy] + before_action :labels, only: [:index] before_action :authorize_read_label! - before_action :authorize_admin_labels!, only: [ - :new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities - ] + before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, + :generate, :destroy, :remove_priority, + :set_priorities] respond_to :js, :html def index - @project_labels = project_labels - @prioritized_labels = project_labels.prioritized + @prioritized_labels = @labels.prioritized @group_labels = @project.group.labels.unprioritized if @project.group.present? - @labels = @project.labels.unprioritized.page(params[:page]) + @project_labels = @project.labels.unprioritized.page(params[:page]) respond_to do |format| format.html format.json do - render json: @project_labels + render json: @labels end end end @@ -38,7 +38,7 @@ class Projects::LabelsController < Projects::ApplicationController end else respond_to do |format| - format.html { render 'new' } + format.html { render :new } format.json { render json: { message: @label.errors.messages }, status: 400 } end end @@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController if @label.update_attributes(label_params) redirect_to namespace_project_labels_path(@project.namespace, @project) else - render 'edit' + render :edit end end @@ -70,7 +70,7 @@ class Projects::LabelsController < Projects::ApplicationController def destroy @label.destroy - @project_labels = project_labels + @labels = labels respond_to do |format| format.html do @@ -83,7 +83,7 @@ class Projects::LabelsController < Projects::ApplicationController def remove_priority respond_to do |format| - label = project_labels.find(params[:id]) + label = labels.find(params[:id]) if label.update_attribute(:priority, nil) format.json { render json: label } @@ -97,7 +97,7 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do params[:label_ids].each_with_index do |label_id, index| - label = project_labels.find_by_id(label_id) + label = labels.find_by_id(label_id) label.update_attribute(:priority, index) if label end end @@ -124,6 +124,10 @@ class Projects::LabelsController < Projects::ApplicationController end alias_method :subscribable_resource, :label + def labels + @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + end + def authorize_admin_labels! return render_404 unless can?(current_user, :admin_label, @project) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 291c3f64914..9171b47cda1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -263,7 +263,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @source_project = @merge_request.source_project @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names - @labels = project_labels end def update diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml index 0b9937c4808..8d09e2bda11 100644 --- a/app/views/projects/labels/destroy.js.haml +++ b/app/views/projects/labels/destroy.js.haml @@ -1,2 +1,2 @@ -- if @project_labels.none? +- if @labels.empty? $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 286b58f57c2..05282338493 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -14,7 +14,7 @@ New label .labels - - unless @project_labels.empty? + - unless @labels.empty? -# Only show it in the first page - hide = params[:page].present? && params[:page] != '1' - if can?(current_user, :admin_label, @project) @@ -39,10 +39,10 @@ = icon('bookmark') Project Labels %ul.content-list.manage-labels-list.js-project-labels - %p.empty-message{ class: ('hidden' unless @labels.empty?) } No project labels - - if @labels.present? - = render @labels - = paginate @labels, theme: 'gitlab' + %p.empty-message{ class: ('hidden' unless @project_labels.empty?) } No project labels + - if @project_labels.present? + = render @project_labels + = paginate @project_labels, theme: 'gitlab' - else .nothing-here-block - if can?(current_user, :admin_label, @project) diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 3492b6ffbbb..2b39f9cf0d1 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -1,52 +1,92 @@ require 'spec_helper' describe Projects::LabelsController do - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:user) { create(:user) } before do project.team << [user, :master] + sign_in(user) end describe 'GET #index' do - def create_label(attributes) - create(:label, attributes.merge(project: project)) - end + let!(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') } + let!(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') } + let!(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') } + let!(:label_4) { create(:label, project: project, priority: nil, title: 'Label 4') } + let!(:label_5) { create(:label, project: project, priority: nil, title: 'Label 5') } - before do - 15.times { |i| create_label(priority: (i % 3) + 1, title: "label #{15 - i}") } - 5.times { |i| create_label(title: "label #{100 - i}") } - - get :index, namespace_id: project.namespace.to_param, project_id: project.to_param - end + let!(:group_label_1) { create(:group_label, group: group, priority: 3, title: 'Group Label 1') } + let!(:group_label_2) { create(:group_label, group: group, priority: 1, title: 'Group Label 2') } + let!(:group_label_3) { create(:group_label, group: group, priority: nil, title: 'Group Label 3') } + let!(:group_label_4) { create(:group_label, group: group, priority: nil, title: 'Group Label 4') } context '@prioritized_labels' do - let(:prioritized_labels) { assigns(:prioritized_labels) } + before do + list_labels + end it 'contains only prioritized labels' do - expect(prioritized_labels).to all(have_attributes(priority: a_value > 0)) + expect(assigns(:prioritized_labels)).to all(have_attributes(priority: a_value > 0)) end it 'is sorted by priority, then label title' do - priorities_and_titles = prioritized_labels.pluck(:priority, :title) - - expect(priorities_and_titles.sort).to eq(priorities_and_titles) + expect(assigns(:prioritized_labels)).to match_array [group_label_2, label_1, label_3, group_label_1, label_2] end end - context '@labels' do - let(:labels) { assigns(:labels) } + context '@group_labels' do + it 'contains only group labels' do + list_labels + + expect(assigns(:group_labels)).to all(have_attributes(group_id: a_value > 0)) + end it 'contains only unprioritized labels' do - expect(labels).to all(have_attributes(priority: nil)) + list_labels + + expect(assigns(:group_labels)).to all(have_attributes(priority: nil)) end it 'is sorted by label title' do - titles = labels.pluck(:title) + list_labels - expect(titles.sort).to eq(titles) + expect(assigns(:group_labels)).to match_array [group_label_3, group_label_4] end + + it 'is nil when project does not belong to a group' do + project.update(namespace: create(:namespace)) + + list_labels + + expect(assigns(:group_labels)).to be_nil + end + end + + context '@project_labels' do + before do + list_labels + end + + it 'contains only project labels' do + list_labels + + expect(assigns(:project_labels)).to all(have_attributes(project_id: a_value > 0)) + end + + it 'contains only unprioritized labels' do + expect(assigns(:project_labels)).to all(have_attributes(priority: nil)) + end + + it 'is sorted by label title' do + expect(assigns(:project_labels)).to match_array [label_4, label_5] + end + end + + def list_labels + get :index, namespace_id: project.namespace.to_param, project_id: project.to_param end end end -- cgit v1.2.1 From b10e5764ac0765b556d64dfebb9dad948154e31a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 11:55:31 -0300 Subject: List only labels that belongs to the group on the group issues page --- app/controllers/dashboard/labels_controller.rb | 7 +------ app/controllers/groups/labels_controller.rb | 10 +++++++++- app/finders/labels_finder.rb | 19 ++++++++++--------- app/helpers/labels_helper.rb | 3 +++ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 797f8503b2d..05f7bc37952 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,12 +1,7 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index - labels = LabelsFinder.new(current_user, project_id: projects) - .execute - .select(:id, :title, :color) - .uniq(:title) - respond_to do |format| - format.json { render json: labels } + format.json { render json: LabelsFinder.new(current_user).execute } end end end diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 0ec2fcda0ef..0ebdee55c79 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -8,7 +8,15 @@ class Groups::LabelsController < Groups::ApplicationController respond_to :html def index - @labels = @group.labels.page(params[:page]) + respond_to do |format| + format.html do + @labels = @group.labels.page(params[:page]) + end + + format.json do + render json: LabelsFinder.new(current_user, group_id: @group.id).execute + end + end end def new diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index a27ff56309b..85ef9bea08d 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -20,13 +20,17 @@ class LabelsFinder < UnionFinder label_ids << Label.where(project_id: projects.select(:id)).select(:id) end + def sort(items) + items.reorder(title: :asc, type: :desc) + end + def with_title(items) items = items.where(title: title) if title.present? items end - def sort(items) - items.reorder(title: :asc) + def group_id + params[:group_id].presence end def project_id @@ -40,13 +44,10 @@ class LabelsFinder < UnionFinder def projects return @projects if defined?(@projects) - if project_id - @projects = ProjectsFinder.new.execute(current_user) - .where(id: project_id) - .reorder(nil) - else - @projects = Project.none - end + @projects = ProjectsFinder.new.execute(current_user) + @projects = @projects.joins(:namespace).where(namespaces: { id: group_id, type: 'Group' }) if group_id + @projects = @projects.where(id: project_id) if project_id + @projects = @projects.reorder(nil) @projects end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 9df8d37af9e..8e5321c05fa 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -172,7 +172,10 @@ module LabelsHelper end def labels_filter_path + return group_labels_path(@group, :json) if @group + project = @target_project || @project + if project namespace_project_labels_path(project.namespace, project, :json) else -- cgit v1.2.1 From 1c73d302e2ce5a27aba7171af741b3590d48aba9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 12:00:18 -0300 Subject: Avoid an extra a query per label when setting label priority --- app/controllers/projects/labels_controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 33c3b7f79c2..919c6f239cb 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -97,8 +97,9 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do params[:label_ids].each_with_index do |label_id, index| - label = labels.find_by_id(label_id) - label.update_attribute(:priority, index) if label + next unless labels.where(id: label_id).any? + + Label.where(id: label_id).update_all(priority: index) end end -- cgit v1.2.1 From 2910896b53f107558904e228340009bb9fccca4e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 13:54:29 -0300 Subject: Remove duplication between global and the regular label partials --- app/helpers/labels_helper.rb | 52 ++++++++++----------- app/views/groups/labels/_label.html.haml | 53 ---------------------- app/views/groups/labels/_label_row.html.haml | 6 --- app/views/groups/labels/index.html.haml | 2 +- app/views/projects/issues/_issue.html.haml | 2 +- app/views/projects/labels/_label.html.haml | 53 ---------------------- app/views/projects/labels/index.html.haml | 6 +-- .../merge_requests/_merge_request.html.haml | 2 +- app/views/shared/_label.html.haml | 53 ++++++++++++++++++++++ app/views/shared/_label_row.html.haml | 3 +- 10 files changed, 84 insertions(+), 148 deletions(-) delete mode 100644 app/views/groups/labels/_label.html.haml delete mode 100644 app/views/groups/labels/_label_row.html.haml delete mode 100644 app/views/projects/labels/_label.html.haml create mode 100644 app/views/shared/_label.html.haml diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 8e5321c05fa..65fc460c670 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -4,9 +4,8 @@ module LabelsHelper # Link to a Label # # label - Label object to link to - # project - Project object which will be used as the context for the label's - # link. If omitted, defaults to `@project`, or the label's own - # project. + # subject - Project/Group object which will be used as the context for the + # label's link. If omitted, defaults to the label's own group/project. # type - The type of item the link will point to (:issue or # :merge_request). If omitted, defaults to :issue. # block - An optional block that will be passed to `link_to`, forming the @@ -18,12 +17,11 @@ module LabelsHelper # # Allow the generated link to use the label's own project # link_to_label(label) # - # # Force the generated link to use @project - # @project = Project.first - # link_to_label(label) + # # Force the generated link to use a provided group + # link_to_label(label, subject: Group.last) # # # Force the generated link to use a provided project - # link_to_label(label, project: Project.last) + # link_to_label(label, subject: Project.last) # # # Force the generated link to point to merge requests instead of issues # link_to_label(label, type: :merge_request) @@ -32,9 +30,8 @@ module LabelsHelper # link_to_label(label) { "My Custom Label Text" } # # Returns a String - def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block) - project ||= @project || label.project - link = label_filter_path(project, label, type: type) + def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block) + link = label_filter_path(label, type: type) if block_given? link_to link, class: css_class, &block @@ -43,27 +40,16 @@ module LabelsHelper end end - def link_to_group_label(label, group: nil, type: :issue, tooltip: true, css_class: nil, &block) - group ||= @group || label.group - link = label_filter_path(group, label, type: type) - - if block_given? - link_to link, class: css_class, &block + def label_filter_path(label, type: issue) + case label + when GroupLabel + send("#{type.to_s.pluralize}_group_path", + label.group, + label_name: [label.name]) else - link_to render_colored_label(label, tooltip: tooltip), link, class: css_class - end - end - - def label_filter_path(subject, label, type: issue) - case subject - when Project send("namespace_project_#{type.to_s.pluralize}_path", - subject.namespace, - subject, - label_name: [label.name]) - when Group - send("#{type.to_s.pluralize}_group_path", - subject, + label.project.namespace, + label.project, label_name: [label.name]) end end @@ -92,6 +78,13 @@ module LabelsHelper end end + def toggle_subscription_label_path(label) + case label + when GroupLabel then toggle_subscription_group_label_path(label.group, label) + else toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) + end + end + def label_type_icon(label, options = {}) title, icon = case label @@ -101,6 +94,7 @@ module LabelsHelper options[:class] ||= '' options[:class] << ' has-tooltip js-label-type' + options[:class] << ' hidden' if options.fetch(:hidden, false) content_tag :span, class: options[:class], diff --git a/app/views/groups/labels/_label.html.haml b/app/views/groups/labels/_label.html.haml deleted file mode 100644 index 9faf90c303e..00000000000 --- a/app/views/groups/labels/_label.html.haml +++ /dev/null @@ -1,53 +0,0 @@ -- label_css_id = dom_id(label) -- open_issues_count = label.open_issues_count(current_user) -- open_merge_requests_count = label.open_merge_requests_count(current_user) - -%li{id: label_css_id, data: { id: label.id } } - = render 'label_row', label: label - - .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown - %button.btn.btn-default.label-options-toggle{ data: { toggle: 'dropdown' } } - Options - %span.caret - .dropdown-menu.dropdown-menu-align-right - %ul - %li - = link_to_group_label(label, type: :merge_request) do - = pluralize open_merge_requests_count, 'merge request' - %li - = link_to_group_label(label) do - = pluralize open_issues_count, 'open issue' - - if current_user - %li.label-subscription{ data: { url: toggle_subscription_group_label_path(@group, label) } } - %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span= label_subscription_toggle_button_text(label) - - if can? current_user, :admin_label, @group - %li - = link_to 'Edit', edit_group_label_path(@group, label) - %li - = link_to 'Delete', group_label_path(@group, label), title: 'Delete', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} - - .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_group_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do - = pluralize open_merge_requests_count, 'merge request' - = link_to_group_label(label, css_class: 'btn btn-transparent btn-action') do - = pluralize open_issues_count, 'open issue' - - - if current_user - .label-subscription.inline{ data: { url: toggle_subscription_group_label_path(@group, label) } } - %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span.sr-only= label_subscription_toggle_button_text(label) - = icon('eye', class: 'label-subscribe-button-icon') - = icon('spinner spin', class: 'label-subscribe-button-loading') - - - if can? current_user, :admin_label, @group - = link_to edit_group_label_path(@group, label), title: 'Edit', class: 'btn btn-transparent btn-action', data: {toggle: 'tooltip'} do - %span.sr-only Edit - = icon('pencil-square-o') - = link_to group_label_path(@group, label), title: 'Delete', class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?', toggle: 'tooltip'} do - %span.sr-only Delete - = icon('trash-o') - - - if current_user - :javascript - new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/groups/labels/_label_row.html.haml b/app/views/groups/labels/_label_row.html.haml deleted file mode 100644 index e21fac25b01..00000000000 --- a/app/views/groups/labels/_label_row.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -%span.label-row - %span.label-name - = link_to_group_label(label, tooltip: false) - - if label.description - %span.label-description - = markdown(label.description, pipeline: :single_line) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index d9f1d350cb3..8e93ea4f625 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -17,7 +17,7 @@ Group Labels - if @labels.present? %ul.content-list.manage-labels-list.js-group-labels - = render partial: 'label', collection: @labels, as: :label + = render partial: 'shared/label', collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - else .nothing-here-block diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 8b1a8a8a2d9..c80210d6ff4 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -50,7 +50,7 @@ - if issue.labels.any?   - issue.labels.each do |label| - = link_to_label(label, project: issue.project) + = link_to_label(label, subject: issue.project) - if issue.tasks?   %span.task-status diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml deleted file mode 100644 index 2b7b79390f7..00000000000 --- a/app/views/projects/labels/_label.html.haml +++ /dev/null @@ -1,53 +0,0 @@ -- label_css_id = dom_id(label) -- open_issues_count = label.open_issues_count(current_user, @project) -- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) - -%li{id: label_css_id, data: { id: label.id } } - = render "shared/label_row", label: label - - .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown - %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } } - Options - = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right - %ul - %li - = link_to_label(label, type: :merge_request) do - = pluralize open_merge_requests_count, 'merge request' - %li - = link_to_label(label) do - = pluralize open_issues_count, 'open issue' - - if current_user - %li.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } - %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span= label_subscription_toggle_button_text(label) - - if can_admin_label(label) - %li - = link_to 'Edit', edit_label_path(label) - %li - = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} - - .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do - = pluralize open_merge_requests_count, 'merge request' - = link_to_label(label, css_class: 'btn btn-transparent btn-action') do - = pluralize open_issues_count, 'open issue' - - - if current_user - .label-subscription.inline{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } - %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span.sr-only= label_subscription_toggle_button_text(label) - = icon('eye', class: 'label-subscribe-button-icon') - = icon('spinner spin', class: 'label-subscribe-button-loading') - - - if can_admin_label(label) - = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do - %span.sr-only Edit - = icon('pencil-square-o') - = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do - %span.sr-only Delete - = icon('trash-o') - - - if current_user - :javascript - new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 05282338493..8e6f84fc430 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -23,7 +23,7 @@ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - if @prioritized_labels.present? - = render partial: 'label', collection: @prioritized_labels, as: :label + = render partial: 'shared/label', collection: @prioritized_labels, as: :label .group-labels{ class: ('hide' if hide || @project.group.blank?) } %h5 @@ -32,7 +32,7 @@ %ul.content-list.manage-labels-list.js-group-labels %p.empty-message{ class: ('hidden' unless @group_labels.empty?) } No group labels - if @group_labels.present? - = render partial: 'label', collection: @group_labels, as: :label + = render partial: 'shared/label', collection: @group_labels, as: :label .project-labels %h5{ class: ('hide' if hide) } @@ -41,7 +41,7 @@ %ul.content-list.manage-labels-list.js-project-labels %p.empty-message{ class: ('hidden' unless @project_labels.empty?) } No project labels - if @project_labels.present? - = render @project_labels + = render partial: 'shared/label', collection: @project_labels, as: :label = paginate @project_labels, theme: 'gitlab' - else .nothing-here-block diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 68fb7d5a414..ad62bf50b57 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -62,7 +62,7 @@ - if merge_request.labels.any?   - merge_request.labels.each do |label| - = link_to_label(label, project: merge_request.project, type: 'merge_request') + = link_to_label(label, subject: merge_request.project, type: 'merge_request') - if merge_request.tasks?   %span.task-status diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml new file mode 100644 index 00000000000..13795807ab8 --- /dev/null +++ b/app/views/shared/_label.html.haml @@ -0,0 +1,53 @@ +- label_css_id = dom_id(label) +- open_issues_count = label.open_issues_count(current_user, @project) +- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) + +%li{id: label_css_id, data: { id: label.id } } + = render "shared/label_row", label: label + + .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown + %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } } + Options + = icon('caret-down') + .dropdown-menu.dropdown-menu-align-right + %ul + %li + = link_to_label(label, type: :merge_request) do + = pluralize open_merge_requests_count, 'merge request' + %li + = link_to_label(label) do + = pluralize open_issues_count, 'open issue' + - if current_user + %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } + %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span= label_subscription_toggle_button_text(label) + - if can_admin_label(label) + %li + = link_to 'Edit', edit_label_path(label) + %li + = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} + + .pull-right.hidden-xs.hidden-sm.hidden-md + = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = pluralize open_merge_requests_count, 'merge request' + = link_to_label(label, css_class: 'btn btn-transparent btn-action') do + = pluralize open_issues_count, 'open issue' + + - if current_user + .label-subscription.inline{ data: { url: toggle_subscription_label_path(label) } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span.sr-only= label_subscription_toggle_button_text(label) + = icon('eye', class: 'label-subscribe-button-icon') + = icon('spinner spin', class: 'label-subscribe-button-loading') + + - if can_admin_label(label) + = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do + %span.sr-only Edit + = icon('pencil-square-o') + = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do + %span.sr-only Delete + = icon('trash-o') + + - if current_user + :javascript + new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 751b2d1c158..8a1ebdd7fb6 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,7 +10,8 @@ = icon('star') %span.label-name = link_to_label(label, tooltip: false) - = label_type_icon(label, class: "#{'hidden' if label.priority.blank?}" ) + - if can?(current_user, :admin_label, @project) + = label_type_icon(label, hidden: label.priority.blank?) - if label.description %span.label-description = markdown_field(label, :description) -- cgit v1.2.1 From 701544fb48a5add0cc7cbba729e6438d7a040385 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 15:19:57 -0300 Subject: Hides project/group labels section if there are none --- app/views/projects/labels/index.html.haml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 8e6f84fc430..99f8e8095ad 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -18,28 +18,26 @@ -# Only show it in the first page - hide = params[:page].present? && params[:page] != '1' - if can?(current_user, :admin_label, @project) - .prioritized-labels{ class: ('hide' if hide) } + .prioritized-labels{ class: ('hidden' if hide) } %h5 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - if @prioritized_labels.present? = render partial: 'shared/label', collection: @prioritized_labels, as: :label - .group-labels{ class: ('hide' if hide || @project.group.blank?) } + .group-labels{ class: ('hidden' if hide || @project.group.blank? || @group_labels.empty?) } %h5 = icon('folder-open') Group Labels %ul.content-list.manage-labels-list.js-group-labels - %p.empty-message{ class: ('hidden' unless @group_labels.empty?) } No group labels - if @group_labels.present? = render partial: 'shared/label', collection: @group_labels, as: :label - .project-labels - %h5{ class: ('hide' if hide) } + .project-labels{ class: ('hidden' if @project_labels.empty?) } + %h5{ class: ('hidden' if hide) } = icon('bookmark') Project Labels %ul.content-list.manage-labels-list.js-project-labels - %p.empty-message{ class: ('hidden' unless @project_labels.empty?) } No project labels - if @project_labels.present? = render partial: 'shared/label', collection: @project_labels, as: :label = paginate @project_labels, theme: 'gitlab' -- cgit v1.2.1 From 32c663ff248f6ad2f2fa10fd2e81d6535fb88fd6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 15:50:11 -0300 Subject: Use policies to handle with global/project label permissions --- app/helpers/labels_helper.rb | 10 ---------- app/policies/group_label_policy.rb | 5 +++++ app/policies/label_policy.rb | 7 +++++++ app/views/shared/_label.html.haml | 4 ++-- 4 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 app/policies/group_label_policy.rb create mode 100644 app/policies/label_policy.rb diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 65fc460c670..c14caa5e387 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -54,16 +54,6 @@ module LabelsHelper end end - def can_admin_label(label) - subject = - case label - when GroupLabel then label.group - else label.project - end - - can?(current_user, :admin_label, subject) - end - def edit_label_path(label) case label when GroupLabel then edit_group_label_path(label.group, label) diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb new file mode 100644 index 00000000000..4d4052c5800 --- /dev/null +++ b/app/policies/group_label_policy.rb @@ -0,0 +1,5 @@ +class GroupLabelPolicy < BasePolicy + def rules + can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.group) + end +end diff --git a/app/policies/label_policy.rb b/app/policies/label_policy.rb new file mode 100644 index 00000000000..1677ad7f1bb --- /dev/null +++ b/app/policies/label_policy.rb @@ -0,0 +1,7 @@ +class LabelPolicy < BasePolicy + def rules + return unless @user + + can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) + end +end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 13795807ab8..c0b912b0584 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -21,7 +21,7 @@ %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } %span= label_subscription_toggle_button_text(label) - - if can_admin_label(label) + - if can?(current_user, :admin_label, label) %li = link_to 'Edit', edit_label_path(label) %li @@ -40,7 +40,7 @@ = icon('eye', class: 'label-subscribe-button-icon') = icon('spinner spin', class: 'label-subscribe-button-loading') - - if can_admin_label(label) + - if can?(current_user, :admin_label, label) = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') -- cgit v1.2.1 From e2dd75c0a2d863c8e530e54f3a0a015bdec1e84f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 16:09:31 -0300 Subject: Makes the query to retrieve group labels more simpler --- app/finders/labels_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 85ef9bea08d..b8828bcdd32 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -16,7 +16,7 @@ class LabelsFinder < UnionFinder def label_ids label_ids = [] - label_ids << Label.where(group_id: projects.where.not(group: nil).select(:namespace_id)).select(:id) + label_ids << Label.where(group_id: projects.joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)).select(:id) label_ids << Label.where(project_id: projects.select(:id)).select(:id) end -- cgit v1.2.1 From cfedd42badc6b84457d1de35cb31988777462d5a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 17:07:56 -0300 Subject: Add ProjectLabel model --- app/finders/issuable_finder.rb | 3 +- app/helpers/labels_helper.rb | 8 ++-- app/models/label.rb | 7 --- app/models/project.rb | 6 ++- app/models/project_label.rb | 5 +++ app/policies/label_policy.rb | 7 --- app/policies/project_label_policy.rb | 5 +++ app/views/projects/labels/_form.html.haml | 2 +- app/views/projects/labels/edit.html.haml | 2 +- app/views/projects/labels/new.html.haml | 2 +- ...60920191518_set_project_label_type_on_labels.rb | 17 +++++++ lib/banzai/filter/label_reference_filter.rb | 2 +- lib/gitlab/google_code_import/importer.rb | 2 +- spec/factories/labels.rb | 2 +- spec/models/label_spec.rb | 52 ++++++++++------------ spec/models/project_label_spec.rb | 11 +++++ spec/models/project_spec.rb | 2 +- 17 files changed, 79 insertions(+), 56 deletions(-) create mode 100644 app/models/project_label.rb delete mode 100644 app/policies/label_policy.rb create mode 100644 app/policies/project_label_policy.rb create mode 100644 db/migrate/20160920191518_set_project_label_type_on_labels.rb create mode 100644 spec/models/project_label_spec.rb diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 37151f8d134..6f2adf47c3a 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -128,7 +128,8 @@ class IssuableFinder @labels = Label.where(title: label_names) if projects - @labels = @labels.where(project: projects) + label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) + @labels = @labels.where(labels: { id: label_ids }) end else @labels = Label.none diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index c14caa5e387..844bd3fd183 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -57,21 +57,21 @@ module LabelsHelper def edit_label_path(label) case label when GroupLabel then edit_group_label_path(label.group, label) - else edit_namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label) end end def destroy_label_path(label) case label when GroupLabel then group_label_path(label.group, label) - else namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label) end end def toggle_subscription_label_path(label) case label when GroupLabel then toggle_subscription_group_label_path(label.group, label) - else toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) end end @@ -79,7 +79,7 @@ module LabelsHelper title, icon = case label when GroupLabel then ['Group', 'folder-open'] - else ['Project', 'bookmark'] + when ProjectLabel then ['Project', 'bookmark'] end options[:class] ||= '' diff --git a/app/models/label.rb b/app/models/label.rb index be0c20479d5..0a68be7a30f 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -15,15 +15,12 @@ class Label < ActiveRecord::Base default_value_for :color, DEFAULT_COLOR - belongs_to :project - has_many :lists, dependent: :destroy has_many :label_links, dependent: :destroy has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' validates :color, color: true, allow_blank: false - validates :project, presence: true, if: :project_label? # Don't allow ',' for label titles validates :title, presence: true, format: { with: /\A[^,]+\z/ } @@ -137,10 +134,6 @@ class Label < ActiveRecord::Base .count end - def project_label? - type.blank? && !template? - end - def label_format_reference(format = :id) raise StandardError, 'Unknown format' unless [:id, :name].include?(format) diff --git a/app/models/project.rb b/app/models/project.rb index db7301219e5..41125223044 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -107,7 +107,7 @@ class Project < ActiveRecord::Base # Merge requests from source project should be kept when source project was removed has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest has_many :issues, dependent: :destroy - has_many :labels, dependent: :destroy + has_many :labels, dependent: :destroy, class_name: 'ProjectLabel' has_many :services, dependent: :destroy has_many :events, dependent: :destroy has_many :milestones, dependent: :destroy @@ -730,8 +730,10 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| label = label.dup - label.template = nil + label.template = false label.project_id = self.id + label.type = 'ProjectLabel' + label.save end end diff --git a/app/models/project_label.rb b/app/models/project_label.rb new file mode 100644 index 00000000000..3e41113e340 --- /dev/null +++ b/app/models/project_label.rb @@ -0,0 +1,5 @@ +class ProjectLabel < Label + belongs_to :project + + validates :project, presence: true +end diff --git a/app/policies/label_policy.rb b/app/policies/label_policy.rb deleted file mode 100644 index 1677ad7f1bb..00000000000 --- a/app/policies/label_policy.rb +++ /dev/null @@ -1,7 +0,0 @@ -class LabelPolicy < BasePolicy - def rules - return unless @user - - can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) - end -end diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb new file mode 100644 index 00000000000..e7bd58372a6 --- /dev/null +++ b/app/policies/project_label_policy.rb @@ -0,0 +1,5 @@ +class ProjectLabelPolicy < BasePolicy + def rules + can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) + end +end diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 5f7be074f25..28a062c7eb5 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| += form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| = form_errors(@label) .form-group diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index c9ec371c3e1..372abcb8773 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -7,4 +7,4 @@ = icon('bookmark') Edit Project Label %hr - = render 'form' + = render 'form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label) diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index a1e2df6c55d..f170c41bfc4 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -7,4 +7,4 @@ = icon('bookmark') New Project Label %hr - = render 'form' + = render 'form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project) diff --git a/db/migrate/20160920191518_set_project_label_type_on_labels.rb b/db/migrate/20160920191518_set_project_label_type_on_labels.rb new file mode 100644 index 00000000000..af47d0320e2 --- /dev/null +++ b/db/migrate/20160920191518_set_project_label_type_on_labels.rb @@ -0,0 +1,17 @@ +class SetProjectLabelTypeOnLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + update_column_in_batches(:labels, :type, 'ProjectLabel') do |table, query| + query.where(table[:project_id].not_eq(nil)) + end + end + + def down + update_column_in_batches(:labels, :type, nil) do |table, query| + query.where(table[:project_id].not_eq(nil)) + end + end +end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 3a09912f1be..4c4784b0052 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -70,7 +70,7 @@ module Banzai end def object_link_text(object, matches) - if object.project.nil? || object.project == context[:project] + if object.is_a?(GroupLabel) || object.project == context[:project] LabelsHelper.render_colored_label(object) else LabelsHelper.render_colored_cross_project_label(object) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 62da327931f..ef8c3e35619 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -237,7 +237,7 @@ module Gitlab def create_label(name) color = nice_label_color(name) - Label.create!(project_id: project.id, name: name, color: color) + project.labels.create!(name: name, color: color) end def format_content(raw_content) diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index eb489099854..ec4c56457ea 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :label do + factory :label, class: ProjectLabel do sequence(:title) { |n| "label#{n}" } color "#990000" project diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 894021dc8e6..1f1fe45d5a7 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -1,47 +1,41 @@ require 'spec_helper' describe Label, models: true do - let(:label) { create(:label) } + describe 'modules' do + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(Subscribable) } + end describe 'associations' do - it { is_expected.to belong_to(:project) } - - it { is_expected.to have_many(:label_links).dependent(:destroy) } it { is_expected.to have_many(:issues).through(:label_links).source(:target) } + it { is_expected.to have_many(:label_links).dependent(:destroy) } it { is_expected.to have_many(:lists).dependent(:destroy) } end - describe 'modules' do - subject { described_class } - - it { is_expected.to include_module(Referable) } - end - describe 'validation' do - it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_uniqueness_of(:title) } it 'validates color code' do - expect(label).not_to allow_value('G-ITLAB').for(:color) - expect(label).not_to allow_value('AABBCC').for(:color) - expect(label).not_to allow_value('#AABBCCEE').for(:color) - expect(label).not_to allow_value('GGHHII').for(:color) - expect(label).not_to allow_value('#').for(:color) - expect(label).not_to allow_value('').for(:color) - - expect(label).to allow_value('#AABBCC').for(:color) - expect(label).to allow_value('#abcdef').for(:color) + is_expected.not_to allow_value('G-ITLAB').for(:color) + is_expected.not_to allow_value('AABBCC').for(:color) + is_expected.not_to allow_value('#AABBCCEE').for(:color) + is_expected.not_to allow_value('GGHHII').for(:color) + is_expected.not_to allow_value('#').for(:color) + is_expected.not_to allow_value('').for(:color) + + is_expected.to allow_value('#AABBCC').for(:color) + is_expected.to allow_value('#abcdef').for(:color) end it 'validates title' do - expect(label).not_to allow_value('G,ITLAB').for(:title) - expect(label).not_to allow_value('').for(:title) - - expect(label).to allow_value('GITLAB').for(:title) - expect(label).to allow_value('gitlab').for(:title) - expect(label).to allow_value('G?ITLAB').for(:title) - expect(label).to allow_value('G&ITLAB').for(:title) - expect(label).to allow_value("customer's request").for(:title) + is_expected.not_to allow_value('G,ITLAB').for(:title) + is_expected.not_to allow_value('').for(:title) + + is_expected.to allow_value('GITLAB').for(:title) + is_expected.to allow_value('gitlab').for(:title) + is_expected.to allow_value('G?ITLAB').for(:title) + is_expected.to allow_value('G&ITLAB').for(:title) + is_expected.to allow_value("customer's request").for(:title) end end @@ -53,6 +47,8 @@ describe Label, models: true do end describe '#to_reference' do + let(:label) { create(:label) } + context 'using id' do it 'returns a String reference to the object' do expect(label.to_reference).to eq "~#{label.id}" diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb new file mode 100644 index 00000000000..93062b9d402 --- /dev/null +++ b/spec/models/project_label_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe ProjectLabel, models: true do + describe 'relationships' do + it { is_expected.to belong_to(:project) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 67dbcc362f6..e6d98e25d0b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -56,7 +56,7 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } - it { is_expected.to have_many(:labels).dependent(:destroy) } + it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) } it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } it { is_expected.to have_many(:environments).dependent(:destroy) } it { is_expected.to have_many(:deployments).dependent(:destroy) } -- cgit v1.2.1 From e28058c4107ce454a84b3e3b5750f936dace7db1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 21 Sep 2016 17:47:58 -0300 Subject: Validate if project label title does not exist at group level --- app/models/label.rb | 5 +++-- app/models/project_label.rb | 14 ++++++++++++++ config/locales/en.yml | 1 + spec/factories/labels.rb | 6 ++++++ spec/models/label_spec.rb | 2 +- spec/models/project_label_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 3 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index 0a68be7a30f..f844a3d3378 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -24,13 +24,14 @@ class Label < ActiveRecord::Base # Don't allow ',' for label titles validates :title, presence: true, format: { with: /\A[^,]+\z/ } - validates :title, uniqueness: true, unless: :template? + validates :title, uniqueness: { scope: [:group_id, :project_id] } before_save :nullify_priority default_scope { order(title: :asc) } - scope :templates, -> { where(template: true) } + scope :templates, -> { where(template: true) } + scope :with_title, ->(title) { where(title: title) } def self.prioritized where.not(priority: nil).reorder(:priority, :title) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 3e41113e340..1171aa2dbb3 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -2,4 +2,18 @@ class ProjectLabel < Label belongs_to :project validates :project, presence: true + + validate :title_must_not_exist_at_group_level + + delegate :group, to: :project, allow_nil: true + + private + + def title_must_not_exist_at_group_level + return unless group.present? + + if group.labels.with_title(self.title).exists? + errors.add(:title, :label_already_exists_at_group_level, group: group.name) + end + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index cedb5e207bd..12a59be79f0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5,6 +5,7 @@ en: hello: "Hello world" errors: messages: + label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one." wrong_size: "is the wrong size (should be %{file_size})" size_too_small: "is too small (should be at least %{file_size})" size_too_big: "is too big (should be at most %{file_size})" diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index ec4c56457ea..5c789d72bac 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -4,4 +4,10 @@ FactoryGirl.define do color "#990000" project end + + factory :group_label, class: GroupLabel do + sequence(:title) { |n| "label#{n}" } + color "#990000" + group + end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 1f1fe45d5a7..ab640e216cf 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -13,7 +13,7 @@ describe Label, models: true do end describe 'validation' do - it { is_expected.to validate_uniqueness_of(:title) } + it { is_expected.to validate_uniqueness_of(:title).scoped_to([:group_id, :project_id]) } it 'validates color code' do is_expected.not_to allow_value('G-ITLAB').for(:color) diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 93062b9d402..355bb2a938c 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -7,5 +7,39 @@ describe ProjectLabel, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:project) } + + context 'validates if title must not exist at group level' do + let(:group) { create(:group, name: 'gitlab-org') } + let(:project) { create(:empty_project, group: group) } + + before do + create(:group_label, group: group, title: 'Bug') + end + + it 'returns error if title already exists at group level' do + label = described_class.new(project: project, title: 'Bug') + + label.valid? + + expect(label.errors[:title]).to include 'already exists at group level for gitlab-org. Please choose another one.' + end + + it 'does not returns error if title does not exist at group level' do + label = described_class.new(project: project, title: 'Security') + + label.valid? + + expect(label.errors[:title]).to be_empty + end + + it 'does not returns error if project does not belong to group' do + another_project = create(:empty_project) + label = described_class.new(project: another_project, title: 'Bug') + + label.valid? + + expect(label.errors[:title]).to be_empty + end + end end end -- cgit v1.2.1 From 8522ef44bf4298a750d352ff17832b3f4fc6756d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 17:10:54 -0300 Subject: Recreates missing group labels when moving project to another group --- app/services/labels/transfer_service.rb | 52 +++++++++++++++++++++++++ app/services/projects/transfer_service.rb | 4 ++ spec/factories/merge_requests.rb | 10 +++++ spec/services/labels/transfer_service_spec.rb | 41 +++++++++++++++++++ spec/services/projects/transfer_service_spec.rb | 10 +++++ 5 files changed, 117 insertions(+) create mode 100644 app/services/labels/transfer_service.rb create mode 100644 spec/services/labels/transfer_service_spec.rb diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb new file mode 100644 index 00000000000..81897c62c0f --- /dev/null +++ b/app/services/labels/transfer_service.rb @@ -0,0 +1,52 @@ +# Labels::TransferService class +# +# User for recreate the missing group labels at project level +# +module Labels + class TransferService + def initialize(current_user, group, project) + @current_user = current_user + @group = group + @project = project + end + + def execute + return unless group.present? + + Label.transaction do + labels_to_transfer = Label.where(id: label_links.select(:label_id).uniq) + + labels_to_transfer.find_each do |label| + new_label_id = find_or_create_label!(label) + + LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) + end + end + end + + private + + attr_reader :current_user, :group, :project + + def label_links + label_link_ids = [] + label_link_ids << LabelLink.where(target: project.issues, label: group.labels).select(:id) + label_link_ids << LabelLink.where(target: project.merge_requests, label: group.labels).select(:id) + + union = Gitlab::SQL::Union.new(label_link_ids) + + LabelLink.where("label_links.id IN (#{union.to_sql})") + end + + def labels + @labels ||= LabelsFinder.new(current_user, project_id: project.id).execute + end + + def find_or_create_label!(label) + new_label = labels.find_by(title: label.title) + new_label ||= project.labels.create!(label.attributes.slice("title", "description", "color")) + + new_label.id + end + end +end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index bc7f8bf433b..28470f59807 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -28,6 +28,7 @@ module Projects Project.transaction do old_path = project.path_with_namespace old_namespace = project.namespace + old_group = project.group new_path = File.join(new_namespace.try(:path) || '', project.path) if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present? @@ -57,6 +58,9 @@ module Projects # Move wiki repo also if present gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki") + # Move missing group labels to project + Labels::TransferService.new(current_user, old_group, project).execute + # clear project cached events project.reset_events_cache diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index c6a08d78b78..f780e01253c 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -68,5 +68,15 @@ FactoryGirl.define do factory :closed_merge_request, traits: [:closed] factory :reopened_merge_request, traits: [:reopened] factory :merge_request_with_diffs, traits: [:with_diffs] + + factory :labeled_merge_request do + transient do + labels [] + end + + after(:create) do |merge_request, evaluator| + merge_request.update_attributes(labels: evaluator.labels) + end + end end end diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb new file mode 100644 index 00000000000..a72a05f6c99 --- /dev/null +++ b/spec/services/labels/transfer_service_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Labels::TransferService, services: true do + describe '#execute' do + let(:user) { create(:user) } + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } + let(:project) { create(:project, namespace: group_2) } + + let(:group_label_1) { create(:group_label, group: group_1, name: 'Group Label 1') } + let(:group_label_2) { create(:group_label, group: group_1, name: 'Group Label 2') } + let(:group_label_3) { create(:group_label, group: group_1, name: 'Group Label 3') } + let(:group_label_4) { create(:group_label, group: group_2, name: 'Group Label 4') } + let(:project_label_1) { create(:label, project: project, name: 'Project Label 1') } + + subject(:service) { described_class.new(user, group_1, project) } + + before do + create(:labeled_issue, project: project, labels: [group_label_1]) + create(:labeled_issue, project: project, labels: [group_label_4]) + create(:labeled_issue, project: project, labels: [project_label_1]) + create(:labeled_merge_request, source_project: project, labels: [group_label_1, group_label_2]) + end + + it 'recreates the missing group labels at project level' do + expect { service.execute }.to change(project.labels, :count).by(2) + end + + it 'does not recreate missing group labels that are not applied to issues or merge requests' do + service.execute + + expect(project.labels.where(title: group_label_3.title)).to be_empty + end + + it 'does not recreate missing group labels that already exist in the project group' do + service.execute + + expect(project.labels.where(title: group_label_4.title)).to be_empty + end + end +end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 57c71544dff..1540b90163a 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -71,4 +71,14 @@ describe Projects::TransferService, services: true do it { expect(private_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) } end end + + context 'missing group labels applied to issues or merge requests' do + it 'delegates tranfer to Labels::TransferService' do + group.add_owner(user) + + expect_any_instance_of(Labels::TransferService).to receive(:execute).once.and_call_original + + transfer_project(project, user, group) + end + end end -- cgit v1.2.1 From ae88126d13d05ea040af495d77dcd1a84253d282 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 18:06:46 -0300 Subject: Show labels widget on issuable sidebar if project has only group labels --- app/controllers/concerns/issuable_actions.rb | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 27f1e91d865..be86fa106f8 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -2,7 +2,7 @@ module IssuableActions extend ActiveSupport::Concern included do - before_action :labels, only: [:new, :edit] + before_action :labels, only: [:show, :new, :edit] before_action :authorize_destroy_issuable!, only: :destroy before_action :authorize_admin_issuable!, only: :bulk_update end diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index ba9f0c27661..7363ead09ff 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -107,7 +107,7 @@ = dropdown_content do .js-due-date-calendar - - if issuable.project.labels.any? + - if @labels && @labels.any? - selected_labels = issuable.labels .block.labels .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } -- cgit v1.2.1 From b654229dcd3e4460ad7305ee7714395f044a72aa Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 19:58:36 -0300 Subject: Fix LabelsHelper#link_to_label to use the subject argument --- app/helpers/labels_helper.rb | 22 ++++++++++++++-------- app/views/shared/_label.html.haml | 8 ++++---- app/views/shared/_label_row.html.haml | 2 +- app/views/shared/_labels_row.html.haml | 2 +- spec/helpers/labels_helper_spec.rb | 27 +++++++++++++-------------- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 844bd3fd183..e26e82c6448 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -31,7 +31,13 @@ module LabelsHelper # # Returns a String def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block) - link = label_filter_path(label, type: type) + subject ||= + case label + when GroupLabel then label.group + when ProjectLabel then label.project + end + + link = label_filter_path(subject, label, type: type) if block_given? link_to link, class: css_class, &block @@ -40,16 +46,16 @@ module LabelsHelper end end - def label_filter_path(label, type: issue) - case label - when GroupLabel + def label_filter_path(subject, label, type: issue) + case subject + when Group send("#{type.to_s.pluralize}_group_path", - label.group, + subject, label_name: [label.name]) - else + when Project send("namespace_project_#{type.to_s.pluralize}_path", - label.project.namespace, - label.project, + subject.namespace, + subject, label_name: [label.name]) end end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index c0b912b0584..ba8a3efccda 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -12,10 +12,10 @@ .dropdown-menu.dropdown-menu-align-right %ul %li - = link_to_label(label, type: :merge_request) do + = link_to_label(label, subject: @project, type: :merge_request) do = pluralize open_merge_requests_count, 'merge request' %li - = link_to_label(label) do + = link_to_label(label, subject: @project) do = pluralize open_issues_count, 'open issue' - if current_user %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } @@ -28,9 +28,9 @@ = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: @project, type: :merge_request, css_class: 'btn btn-transparent btn-action') do = pluralize open_merge_requests_count, 'merge request' - = link_to_label(label, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: @project, css_class: 'btn btn-transparent btn-action') do = pluralize open_issues_count, 'open issue' - if current_user diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 8a1ebdd7fb6..a623bbc6b11 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -9,7 +9,7 @@ %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' } = icon('star') %span.label-name - = link_to_label(label, tooltip: false) + = link_to_label(label, subject: @project, tooltip: false) - if can?(current_user, :admin_label, @project) = label_type_icon(label, hidden: label.priority.blank?) - if label.description diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml index e324d0e5203..21b37a7c9ae 100644 --- a/app/views/shared/_labels_row.html.haml +++ b/app/views/shared/_labels_row.html.haml @@ -1,5 +1,5 @@ - labels.each do |label| %span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" } - = link_to_label(label, css_class: 'btn btn-transparent') + = link_to_label(label, subject: @project, css_class: 'btn btn-transparent') %button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } } = icon("times") diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 501f150cfda..d30daf47543 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -5,27 +5,26 @@ describe LabelsHelper do let(:project) { create(:empty_project) } let(:label) { create(:label, project: project) } - context 'with @project set' do - before do - @project = project - end - - it 'uses the instance variable' do - expect(link_to_label(label)).to match %r{} + context 'without subject' do + it "uses the label's project" do + expect(link_to_label(label)).to match %r{.*} end end - context 'without @project set' do - it "uses the label's project" do - expect(link_to_label(label)).to match %r{.*} + context 'with a project as subject' do + let(:namespace) { build(:namespace, name: 'foo3') } + let(:another_project) { build(:empty_project, namespace: namespace, name: 'bar3') } + + it 'links to project issues page' do + expect(link_to_label(label, subject: another_project)).to match %r{.*} end end - context 'with a project argument' do - let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') } + context 'with a group as subject' do + let(:group) { build(:group, name: 'bar') } - it 'links to merge requests page' do - expect(link_to_label(label, project: another_project)).to match %r{.*} + it 'links to group issues page' do + expect(link_to_label(label, subject: group)).to match %r{.*} end end -- cgit v1.2.1 From 20b6974a5c7866b24969937caadd5cf483c8f8a4 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 23:23:58 -0300 Subject: Fix Issuable#add_labels_by_names to validate if label exists on group --- app/models/concerns/issuable.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index c4b42ad82c7..1647d693a9d 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -235,9 +235,19 @@ module Issuable end def add_labels_by_names(label_names) + label_ids = [] + label_ids << project.group.labels.select(:id) if project.group.present? + label_ids << project.labels.select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + available_labels = Label.where("labels.id IN (#{union.to_sql})") + label_names.each do |label_name| - label = project.labels.create_with(color: Label::DEFAULT_COLOR). - find_or_create_by(title: label_name.strip) + title = label_name.strip + label = available_labels.find_by(title: title) + label = project.labels.build(title: title, color: Label::DEFAULT_COLOR) if label.nil? + self.labels << label end end -- cgit v1.2.1 From e00c739f975672eaba474824436ec70d979e1fcc Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 23:36:31 -0300 Subject: Add Label attributes: type, and group_id to safe model attributes --- spec/lib/gitlab/import_export/safe_model_attributes.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 8c8be66df9f..26049914bac 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -64,7 +64,9 @@ Label: - id - title - color +- group_id - project_id +- type - created_at - updated_at - template -- cgit v1.2.1 From 1644276bac361c43a56936cbbadef2a15fe646a6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 27 Sep 2016 23:57:41 -0300 Subject: Add tests to LabelsFinder --- spec/finders/labels_finder_spec.rb | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 spec/finders/labels_finder_spec.rb diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb new file mode 100644 index 00000000000..27acc464ea2 --- /dev/null +++ b/spec/finders/labels_finder_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe LabelsFinder do + describe '#execute' do + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } + let(:group_3) { create(:group) } + + let(:project_1) { create(:empty_project, namespace: group_1) } + let(:project_2) { create(:empty_project, namespace: group_2) } + let(:project_3) { create(:empty_project) } + let(:project_4) { create(:empty_project, :public) } + let(:project_5) { create(:empty_project, namespace: group_1) } + + let!(:project_label_1) { create(:label, project: project_1, title: 'Label 1') } + let!(:project_label_2) { create(:label, project: project_2, title: 'Label 2') } + let!(:project_label_4) { create(:label, project: project_4, title: 'Label 4') } + let!(:project_label_5) { create(:label, project: project_5, title: 'Label 5') } + + let!(:group_label_1) { create(:group_label, group: group_1, title: 'Label 1') } + let!(:group_label_2) { create(:group_label, group: group_1, title: 'Group Label 2') } + let!(:group_label_3) { create(:group_label, group: group_2, title: 'Group Label 3') } + + let(:user) { create(:user) } + + before do + create(:label, project: project_3, title: 'Label 3') + create(:group_label, group: group_3, title: 'Group Label 4') + + project_1.team << [user, :developer] + end + + context 'with no filter' do + it 'returns labels from projects the user have access' do + group_2.add_developer(user) + + finder = described_class.new(user) + + expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4] + end + end + + context 'filtering by group_id' do + it 'returns labels available for any project within the group' do + group_1.add_developer(user) + + finder = described_class.new(user, group_id: group_1.id) + + expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1, project_label_5] + end + end + + context 'filtering by project_id' do + it 'returns labels available for the project' do + finder = described_class.new(user, project_id: project_1.id) + + expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1] + end + end + + context 'filtering by title' do + it 'returns label with that title' do + finder = described_class.new(user, title: 'Group Label 2') + + expect(finder.execute).to eq [group_label_2] + end + end + end +end -- cgit v1.2.1 From e036a72dca49766df3ee455d6ac955c30846f3fb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Sep 2016 11:12:44 -0300 Subject: Add Lavel#type to methods in lib/gitlab/import_export/import_export.yml --- lib/gitlab/import_export/import_export.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index bb9d1080330..4204a13dd63 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -71,6 +71,8 @@ excluded_attributes: - :awardable_id methods: + labels: + - :type statuses: - :type services: -- cgit v1.2.1 From fd0ba37276f6246c4095c4879bf9e1186f7c5ad8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Sep 2016 11:23:42 -0300 Subject: Update project test file for project import integration test --- .../import_export/test_project_export.tar.gz | Bin 1363770 -> 680875 bytes 1 file changed, 0 insertions(+), 0 deletions(-) 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 d04bdea0fe4..50b42bcec13 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 -- cgit v1.2.1 From 484f19ed1c5c07cbf8ea26fab8b6759961fcf9ca Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Sep 2016 16:17:41 -0300 Subject: Include global labels when moving an issue to another project --- app/services/issues/move_service.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index ab667456db7..a2a5f57d069 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -52,8 +52,12 @@ module Issues end def cloneable_label_ids - @new_project.labels - .where(title: @old_issue.labels.pluck(:title)).pluck(:id) + params = { + project_id: @new_project.id, + title: @old_issue.labels.pluck(:title) + } + + LabelsFinder.new(current_user, params).execute.pluck(:id) end def cloneable_milestone_id -- cgit v1.2.1 From 07709c5576a06179c5365b0d7fe154c5f67ca7e5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 29 Sep 2016 00:21:47 -0300 Subject: Unfold references for group labels when moving issue to another project --- app/helpers/labels_helper.rb | 4 +- app/models/group_label.rb | 17 +++++ app/models/label.rb | 24 ------- app/models/project_label.rb | 24 +++++++ lib/banzai/filter/label_reference_filter.rb | 39 +++++++++- lib/gitlab/gfm/reference_rewriter.rb | 18 ++++- .../banzai/filter/label_reference_filter_spec.rb | 82 ++++++++++++++++++++++ spec/lib/gitlab/gfm/reference_rewriter_spec.rb | 26 ++++++- spec/models/group_label_spec.rb | 28 ++++++++ spec/models/label_spec.rb | 46 ------------ spec/models/project_label_spec.rb | 46 ++++++++++++ 11 files changed, 275 insertions(+), 79 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index e26e82c6448..6d0d33b84fb 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -115,8 +115,8 @@ module LabelsHelper span.html_safe end - def render_colored_cross_project_label(label, tooltip: true) - label_suffix = label.project.name_with_namespace + def render_colored_cross_project_label(label, source_project = nil, tooltip: true) + label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace label_suffix = " in #{escape_once(label_suffix)}" render_colored_label(label, label_suffix, tooltip: tooltip) end diff --git a/app/models/group_label.rb b/app/models/group_label.rb index a854d075820..bfcaf3df27e 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -2,4 +2,21 @@ class GroupLabel < Label belongs_to :group validates :group, presence: true + + ## + # Returns the String necessary to reference this GroupLabel in Markdown + # + # format - Symbol format to use (default: :id, optional: :name) + # + # Examples: + # + # GroupLabel.first.to_reference # => "~1" + # GroupLabel.first.to_reference(format: :name) # => "~\"bug\"" + # + # Returns a String + # + def to_reference(from_project = nil, format: :id) + format_reference = label_format_reference(format) + "#{self.class.reference_prefix}#{format_reference}" + end end diff --git a/app/models/label.rb b/app/models/label.rb index f844a3d3378..7dd2d8790b0 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -73,30 +73,6 @@ class Label < ActiveRecord::Base nil end - ## - # Returns the String necessary to reference this Label in Markdown - # - # format - Symbol format to use (default: :id, optional: :name) - # - # Examples: - # - # Label.first.to_reference # => "~1" - # Label.first.to_reference(format: :name) # => "~\"bug\"" - # Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1" - # - # Returns a String - # - def to_reference(from_project = nil, format: :id) - format_reference = label_format_reference(format) - reference = "#{self.class.reference_prefix}#{format_reference}" - - if cross_project_reference?(from_project) - project.to_reference + reference - else - reference - end - end - def open_issues_count(user = nil, project = nil) issues_count(user, project_id: project.try(:id) || project_id, state: 'opened') end diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 1171aa2dbb3..2fc074dc401 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -7,6 +7,30 @@ class ProjectLabel < Label delegate :group, to: :project, allow_nil: true + ## + # Returns the String necessary to reference this ProjectLabel in Markdown + # + # format - Symbol format to use (default: :id, optional: :name) + # + # Examples: + # + # ProjectLabel.first.to_reference # => "~1" + # ProjectLabel.first.to_reference(format: :name) # => "~\"bug\"" + # ProjectLabel.first.to_reference(project) # => "gitlab-org/gitlab-ce~1" + # + # Returns a String + # + def to_reference(from_project = nil, format: :id) + format_reference = label_format_reference(format) + reference = "#{self.class.reference_prefix}#{format_reference}" + + if cross_project_reference?(from_project) + project.to_reference + reference + else + reference + end + end + private def title_must_not_exist_at_group_level diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 4c4784b0052..649c697b415 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -70,13 +70,46 @@ module Banzai end def object_link_text(object, matches) - if object.is_a?(GroupLabel) || object.project == context[:project] - LabelsHelper.render_colored_label(object) + if same_group?(object) && namespace_match?(matches) + render_same_project_label(object) + elsif same_project?(object) + render_same_project_label(object) else - LabelsHelper.render_colored_cross_project_label(object) + render_cross_project_label(object, matches) end end + def same_group?(object) + object.is_a?(GroupLabel) && object.group == project.group + end + + def namespace_match?(matches) + matches[:project].blank? || matches[:project] == project.path_with_namespace + end + + def same_project?(object) + object.is_a?(ProjectLabel) && object.project == project + end + + def project + context[:project] + end + + def render_same_project_label(object) + LabelsHelper.render_colored_label(object) + end + + def render_cross_project_label(object, matches) + source_project = + if matches[:project] + Project.find_with_namespace(matches[:project]) + else + object.project + end + + LabelsHelper.render_colored_cross_project_label(object, source_project) + end + def unescape_html_entities(text) CGI.unescapeHTML(text.to_s) end diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb index 78d7a4f27cf..d0b8cd90e0e 100644 --- a/lib/gitlab/gfm/reference_rewriter.rb +++ b/lib/gitlab/gfm/reference_rewriter.rb @@ -58,7 +58,7 @@ module Gitlab referable = find_referable(reference) return reference unless referable - cross_reference = referable.to_reference(target_project) + cross_reference = build_cross_reference(referable, target_project) return reference if reference == cross_reference new_text = before + cross_reference + after @@ -72,6 +72,22 @@ module Gitlab extractor.all.first end + def build_cross_reference(referable, target_project) + if referable.respond_to?(:project) + referable.to_reference(target_project) + else + to_reference(referable, target_project) + end + end + + def to_reference(referable, target_project) + if @source_project != target_project + @source_project.to_reference + referable.to_reference + else + referable.to_reference + end + end + def substitution_valid?(substituted) @original_html == markdown(substituted) end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 908ccebbf87..9c09f00ae8a 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -305,6 +305,58 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end + describe 'group label references' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, name: 'gfm references', group: group) } + + context 'without project reference' do + let(:reference) { group_label.to_reference(format: :name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}", project: project) + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: group_label.name) + expect(doc.text).to eq 'See gfm references' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{group_label.name}\.\))) + end + + it 'ignores invalid label names' do + exp = act = %(Label #{Label.reference_prefix}"#{group_label.name.reverse}") + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'with project reference' do + let(:reference) { project.to_reference + group_label.to_reference(format: :name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}", project: project) + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: group_label.name) + expect(doc.text).to eq 'See gfm references' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{group_label.name}\.\))) + end + + it 'ignores invalid label names' do + exp = act = %(Label #{project.to_reference}#{Label.reference_prefix}"#{group_label.name.reverse}") + + expect(reference_filter(act).to_html).to eq exp + end + end + end + describe 'cross project label references' do context 'valid project referenced' do let(:another_project) { create(:empty_project, :public) } @@ -339,4 +391,34 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end end + + describe 'cross group label references' do + context 'valid project referenced' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:another_group) { create(:group) } + let(:another_project) { create(:empty_project, :public, namespace: another_group) } + let(:project_name) { another_project.name_with_namespace } + let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') } + let(:reference) { another_project.to_reference + group_label.to_reference } + + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')) + .to match /background-color: #00ff00/ + end + + it 'contains cross project content' do + expect(result.css('a').first.text).to eq "#{group_label.name} in #{project_name}" + end + end + end end diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 0af249d8690..f045463c1cb 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'some text' } - let(:old_project) { create(:project) } - let(:new_project) { create(:project) } + let(:old_project) { create(:project, name: 'old') } + let(:new_project) { create(:project, name: 'new') } let(:user) { create(:user) } before { old_project.team << [user, :guest] } @@ -62,7 +62,7 @@ describe Gitlab::Gfm::ReferenceRewriter do it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" } end - context 'description with labels' do + context 'description with project labels' do let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } let(:project_ref) { old_project.to_reference } @@ -76,6 +76,26 @@ describe Gitlab::Gfm::ReferenceRewriter do it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} } end end + + context 'description with group labels' do + let(:old_group) { create(:group) } + let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) } + let(:project_ref) { old_project.to_reference } + + before do + old_project.update(namespace: old_group) + end + + context 'label referenced by id' do + let(:text) { '#1 and ~321' } + it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} } + end + + context 'label referenced by text' do + let(:text) { '#1 and ~"group label"' } + it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} } + end + end end context 'reference contains milestone' do diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index a82d23bcc0b..92b07a3cd44 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -8,4 +8,32 @@ describe GroupLabel, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:group) } end + + describe '#to_reference' do + let(:label) { create(:group_label) } + + context 'using id' do + it 'returns a String reference to the object' do + expect(label.to_reference).to eq "~#{label.id}" + end + end + + context 'using name' do + it 'returns a String reference to the object' do + expect(label.to_reference(format: :name)).to eq %(~"#{label.name}") + end + + it 'uses id when name contains double quote' do + label = create(:label, name: %q{"irony"}) + expect(label.to_reference(format: :name)).to eq "~#{label.id}" + end + end + + context 'using invalid format' do + it 'raises error' do + expect { label.to_reference(format: :invalid) } + .to raise_error StandardError, /Unknown format/ + end + end + end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index ab640e216cf..c6e1ea19987 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -45,50 +45,4 @@ describe Label, models: true do expect(label.title).to eq('foo & bar?') end end - - describe '#to_reference' do - let(:label) { create(:label) } - - context 'using id' do - it 'returns a String reference to the object' do - expect(label.to_reference).to eq "~#{label.id}" - end - end - - context 'using name' do - it 'returns a String reference to the object' do - expect(label.to_reference(format: :name)).to eq %(~"#{label.name}") - end - - it 'uses id when name contains double quote' do - label = create(:label, name: %q{"irony"}) - expect(label.to_reference(format: :name)).to eq "~#{label.id}" - end - end - - context 'using invalid format' do - it 'raises error' do - expect { label.to_reference(format: :invalid) } - .to raise_error StandardError, /Unknown format/ - end - end - - context 'cross project reference' do - let(:project) { create(:project) } - - context 'using name' do - it 'returns cross reference with label name' do - expect(label.to_reference(project, format: :name)) - .to eq %Q(#{label.project.to_reference}~"#{label.name}") - end - end - - context 'using id' do - it 'returns cross reference with label id' do - expect(label.to_reference(project, format: :id)) - .to eq %Q(#{label.project.to_reference}~#{label.id}) - end - end - end - end end diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 355bb2a938c..7966c52c38d 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -42,4 +42,50 @@ describe ProjectLabel, models: true do end end end + + describe '#to_reference' do + let(:label) { create(:label) } + + context 'using id' do + it 'returns a String reference to the object' do + expect(label.to_reference).to eq "~#{label.id}" + end + end + + context 'using name' do + it 'returns a String reference to the object' do + expect(label.to_reference(format: :name)).to eq %(~"#{label.name}") + end + + it 'uses id when name contains double quote' do + label = create(:label, name: %q{"irony"}) + expect(label.to_reference(format: :name)).to eq "~#{label.id}" + end + end + + context 'using invalid format' do + it 'raises error' do + expect { label.to_reference(format: :invalid) } + .to raise_error StandardError, /Unknown format/ + end + end + + context 'cross project reference' do + let(:project) { create(:project) } + + context 'using name' do + it 'returns cross reference with label name' do + expect(label.to_reference(project, format: :name)) + .to eq %Q(#{label.project.to_reference}~"#{label.name}") + end + end + + context 'using id' do + it 'returns cross reference with label id' do + expect(label.to_reference(project, format: :id)) + .to eq %Q(#{label.project.to_reference}~#{label.id}) + end + end + end + end end -- cgit v1.2.1 From 0bfa39d5bdb9f53bfc319b9351230b3eb405b619 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 29 Sep 2016 01:03:28 -0300 Subject: Remove scopes/types for labels --- app/assets/stylesheets/pages/labels.scss | 10 +---- app/controllers/projects/labels_controller.rb | 19 ++++---- app/helpers/labels_helper.rb | 20 --------- app/views/groups/labels/edit.html.haml | 3 +- app/views/groups/labels/index.html.haml | 8 +--- app/views/groups/labels/new.html.haml | 5 +-- app/views/projects/labels/edit.html.haml | 3 +- app/views/projects/labels/index.html.haml | 52 +++++++++------------- app/views/projects/labels/new.html.haml | 3 +- app/views/shared/_label_row.html.haml | 2 - .../controllers/projects/labels_controller_spec.rb | 38 +++------------- .../projects/labels/update_prioritization_spec.rb | 10 ++--- 12 files changed, 50 insertions(+), 123 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index cbd009ccd07..9bac6d46355 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -140,10 +140,6 @@ color: $gray-light; } - .label-type { - opacity: 0.3; - } - li:hover { .draggable-handler { display: inline-block; @@ -152,11 +148,7 @@ } } -.group-labels + .project-labels { - margin-top: 30px; -} - -.group-labels, .project-labels { +.other-labels { .remove-priority { display: none; } diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 919c6f239cb..3154a4435f6 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -3,7 +3,7 @@ class Projects::LabelsController < Projects::ApplicationController before_action :module_enabled before_action :label, only: [:edit, :update, :destroy] - before_action :labels, only: [:index] + before_action :find_labels, only: [:index, :set_priorities, :remove_priority] before_action :authorize_read_label! before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :generate, :destroy, :remove_priority, @@ -12,9 +12,8 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index - @prioritized_labels = @labels.prioritized - @group_labels = @project.group.labels.unprioritized if @project.group.present? - @project_labels = @project.labels.unprioritized.page(params[:page]) + @prioritized_labels = @available_labels.prioritized + @labels = @available_labels.unprioritized.page(params[:page]) respond_to do |format| format.html @@ -70,7 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController def destroy @label.destroy - @labels = labels + @labels = find_labels respond_to do |format| format.html do @@ -83,7 +82,7 @@ class Projects::LabelsController < Projects::ApplicationController def remove_priority respond_to do |format| - label = labels.find(params[:id]) + label = @available_labels.find(params[:id]) if label.update_attribute(:priority, nil) format.json { render json: label } @@ -96,8 +95,10 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do + label_ids = @available_labels.where(id: params[:label_ids]).pluck(:id) + params[:label_ids].each_with_index do |label_id, index| - next unless labels.where(id: label_id).any? + next unless label_ids.include?(label_id.to_i) Label.where(id: label_id).update_all(priority: index) end @@ -125,8 +126,8 @@ class Projects::LabelsController < Projects::ApplicationController end alias_method :subscribable_resource, :label - def labels - @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + def find_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute end def authorize_admin_labels! diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 6d0d33b84fb..e8992c114b0 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -81,26 +81,6 @@ module LabelsHelper end end - def label_type_icon(label, options = {}) - title, icon = - case label - when GroupLabel then ['Group', 'folder-open'] - when ProjectLabel then ['Project', 'bookmark'] - end - - options[:class] ||= '' - options[:class] << ' has-tooltip js-label-type' - options[:class] << ' hidden' if options.fetch(:hidden, false) - - content_tag :span, - class: options[:class], - data: { 'placement' => 'top' }, - title: title, - aria: { label: title } do - icon(icon, base: true) - end - end - def render_colored_label(label, label_suffix = '', tooltip: true) label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label_color) diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml index e247393abd5..28471f407ad 100644 --- a/app/views/groups/labels/edit.html.haml +++ b/app/views/groups/labels/edit.html.haml @@ -1,8 +1,7 @@ - page_title 'Edit', @label.name, 'Labels' %h3.page-title - = icon('folder-open') - Edit Group Label + Edit Label %hr = render 'form', url: group_label_path(@group, @label) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 8e93ea4f625..6c69e3465f4 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -10,13 +10,9 @@ New label .labels - - hide = @group.labels.empty? || (params[:page].present? && params[:page] != '1') - .group-labels - %h5{ class: ('hide' if hide) } - = icon('folder-open') - Group Labels + .other-labels - if @labels.present? - %ul.content-list.manage-labels-list.js-group-labels + %ul.content-list.manage-labels-list.js-other-labels = render partial: 'shared/label', collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - else diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml index 01a50607db4..257ae97de03 100644 --- a/app/views/groups/labels/new.html.haml +++ b/app/views/groups/labels/new.html.haml @@ -1,9 +1,8 @@ -- page_title 'New Group Label' +- page_title 'New Label' - header_title group_title(@group, 'Labels', group_labels_path(@group)) %h3.page-title - = icon('folder-open') - New Group Label + New Label %hr = render 'form', url: group_labels_path diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 372abcb8773..49adb593559 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -4,7 +4,6 @@ %div{ class: container_class } %h3.page-title - = icon('bookmark') - Edit Project Label + Edit Label %hr = render 'form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 99f8e8095ad..c1ec9cabc40 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -14,36 +14,26 @@ New label .labels - - unless @labels.empty? + - if can?(current_user, :admin_label, @project) -# Only show it in the first page - - hide = params[:page].present? && params[:page] != '1' - - if can?(current_user, :admin_label, @project) - .prioritized-labels{ class: ('hidden' if hide) } - %h5 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } - %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - - if @prioritized_labels.present? - = render partial: 'shared/label', collection: @prioritized_labels, as: :label - - .group-labels{ class: ('hidden' if hide || @project.group.blank? || @group_labels.empty?) } - %h5 - = icon('folder-open') - Group Labels - %ul.content-list.manage-labels-list.js-group-labels - - if @group_labels.present? - = render partial: 'shared/label', collection: @group_labels, as: :label + - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1') + .prioritized-labels{ class: ('hide' if hide) } + %h5 Prioritized Labels + %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } + %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet + - if @prioritized_labels.present? + = render partial: 'shared/label', collection: @prioritized_labels, as: :label - .project-labels{ class: ('hidden' if @project_labels.empty?) } - %h5{ class: ('hidden' if hide) } - = icon('bookmark') - Project Labels - %ul.content-list.manage-labels-list.js-project-labels - - if @project_labels.present? - = render partial: 'shared/label', collection: @project_labels, as: :label - = paginate @project_labels, theme: 'gitlab' - - else - .nothing-here-block - - if can?(current_user, :admin_label, @project) - Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. - - else - No labels created yet. + .other-labels + - if can?(current_user, :admin_label, @project) + %h5{ class: ('hide' if hide) } Other Labels + %ul.content-list.manage-labels-list.js-other-labels + - if @labels.present? + = render partial: 'shared/label', collection: @labels, as: :label + = paginate @labels, theme: 'gitlab' + - if @labels.blank? + .nothing-here-block + - if can?(current_user, :admin_label, @project) + Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. + - else + No labels created diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index f170c41bfc4..0c177feb43c 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -4,7 +4,6 @@ %div{ class: container_class } %h3.page-title - = icon('bookmark') - New Project Label + New Label %hr = render 'form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index a623bbc6b11..813ce4f1405 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,8 +10,6 @@ = icon('star') %span.label-name = link_to_label(label, subject: @project, tooltip: false) - - if can?(current_user, :admin_label, @project) - = label_type_icon(label, hidden: label.priority.blank?) - if label.description %span.label-description = markdown_field(label, :description) diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 2b39f9cf0d1..29251f49810 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -33,55 +33,29 @@ describe Projects::LabelsController do end it 'is sorted by priority, then label title' do - expect(assigns(:prioritized_labels)).to match_array [group_label_2, label_1, label_3, group_label_1, label_2] + expect(assigns(:prioritized_labels)).to eq [group_label_2, label_1, label_3, group_label_1, label_2] end end - context '@group_labels' do - it 'contains only group labels' do - list_labels - - expect(assigns(:group_labels)).to all(have_attributes(group_id: a_value > 0)) - end - + context '@labels' do it 'contains only unprioritized labels' do list_labels - expect(assigns(:group_labels)).to all(have_attributes(priority: nil)) + expect(assigns(:labels)).to all(have_attributes(priority: nil)) end it 'is sorted by label title' do list_labels - expect(assigns(:group_labels)).to match_array [group_label_3, group_label_4] + expect(assigns(:labels)).to eq [group_label_3, group_label_4, label_4, label_5] end - it 'is nil when project does not belong to a group' do + it 'does not include group labels when project does not belong to a group' do project.update(namespace: create(:namespace)) list_labels - expect(assigns(:group_labels)).to be_nil - end - end - - context '@project_labels' do - before do - list_labels - end - - it 'contains only project labels' do - list_labels - - expect(assigns(:project_labels)).to all(have_attributes(project_id: a_value > 0)) - end - - it 'contains only unprioritized labels' do - expect(assigns(:project_labels)).to all(have_attributes(priority: nil)) - end - - it 'is sorted by label title' do - expect(assigns(:project_labels)).to match_array [label_4, label_5] + expect(assigns(:labels)).not_to include(group_label_3, group_label_4) end end diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 21896f0a787..84a12a38c26 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -22,8 +22,8 @@ feature 'Prioritize labels', feature: true do expect(page).to have_content('No prioritized labels yet') - page.within('.group-labels') do - first('.js-toggle-priority').click + page.within('.other-labels') do + all('.js-toggle-priority')[1].click wait_for_ajax expect(page).not_to have_content('feature') end @@ -47,7 +47,7 @@ feature 'Prioritize labels', feature: true do expect(page).not_to have_content('bug') end - page.within('.group-labels') do + page.within('.other-labels') do expect(page).to have_content('feature') end end @@ -57,7 +57,7 @@ feature 'Prioritize labels', feature: true do expect(page).to have_content('No prioritized labels yet') - page.within('.project-labels') do + page.within('.other-labels') do first('.js-toggle-priority').click wait_for_ajax expect(page).not_to have_content('bug') @@ -82,7 +82,7 @@ feature 'Prioritize labels', feature: true do expect(page).not_to have_content('bug') end - page.within('.project-labels') do + page.within('.other-labels') do expect(page).to have_content('bug') expect(page).to have_content('wontfix') end -- cgit v1.2.1 From 848a146fc3bd34ec94a206f2ed6ef33d539bfce5 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Sep 2016 09:51:12 +0200 Subject: Fix import test --- lib/gitlab/import_export/import_export.yml | 4 +++- lib/gitlab/import_export/relation_factory.rb | 5 +++-- .../import_export/test_project_export.tar.gz | Bin 680875 -> 680856 bytes 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 4204a13dd63..59abca04b35 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -71,7 +71,9 @@ excluded_attributes: - :awardable_id methods: - labels: + project_labels: + - :type + label: - :type statuses: - :type diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 9300f789e1b..5a84bc97226 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -9,7 +9,8 @@ module Gitlab builds: 'Ci::Build', hooks: 'ProjectHook', merge_access_levels: 'ProtectedBranch::MergeAccessLevel', - push_access_levels: 'ProtectedBranch::PushAccessLevel' }.freeze + push_access_levels: 'ProtectedBranch::PushAccessLevel', + labels: :project_labels }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -19,7 +20,7 @@ module Gitlab IMPORTED_OBJECT_MAX_RETRIES = 5.freeze - EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze + EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels].freeze FINDER_ATTRIBUTES = %w[title project_id].freeze 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 50b42bcec13..8f683cf89aa 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 -- cgit v1.2.1 From 77b7bfd463bf57d38cb6aa30f277cd19ffbb6504 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Sep 2016 13:15:18 +0200 Subject: Fix import/export labels to cope with project and group labels. Added relevant specs. --- doc/user/project/settings/import_export.md | 3 ++- lib/gitlab/import_export.rb | 2 +- lib/gitlab/import_export/import_export.yml | 2 +- lib/gitlab/import_export/project_tree_restorer.rb | 7 +++++- lib/gitlab/import_export/relation_factory.rb | 20 +++++++++++++-- spec/lib/gitlab/import_export/project.json | 27 ++++++++++++++++++-- .../import_export/project_tree_restorer_spec.rb | 29 +++++++++++++++++++++- .../import_export/project_tree_saver_spec.rb | 16 +++++++++--- 8 files changed, 94 insertions(+), 12 deletions(-) diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 65ed9fae4ec..dfc762fe1d3 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -22,7 +22,8 @@ with all their related data and be moved into a new GitLab instance. | GitLab version | Import/Export version | | -------- | -------- | -| 8.12.0 to current | 0.1.4 | +| 8.13.0 to current | 0.1.5 | +| 8.12.0 | 0.1.4 | | 8.10.3 | 0.1.3 | | 8.10.0 | 0.1.2 | | 8.9.5 | 0.1.1 | diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 181e288a014..eb667a85b78 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -3,7 +3,7 @@ module Gitlab extend self # For every version update, the version history in import_export.md has to be kept up to date. - VERSION = '0.1.4' + VERSION = '0.1.5' FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 59abca04b35..8882f146632 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -71,7 +71,7 @@ excluded_attributes: - :awardable_id methods: - project_labels: + labels: - :type label: - :type diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 5a109f24f9f..accac5325f8 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -110,7 +110,7 @@ module Gitlab def create_relation(relation, relation_hash_list) relation_array = [relation_hash_list].flatten.map do |relation_hash| Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym, - relation_hash: relation_hash, + relation_hash: parsed_relation_hash(relation_hash), members_mapper: members_mapper, user: @user, project_id: restored_project.id) @@ -118,6 +118,11 @@ module Gitlab relation_hash_list.is_a?(Array) ? relation_array : relation_array.first end + + def parsed_relation_hash(relation_hash) + group_id = restored_project.group ? restored_project.group.id : nil + relation_hash.merge!('group_id' => group_id, 'project_id' => restored_project.id) + end end end end diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 5a84bc97226..8bc4ab85c18 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -10,7 +10,8 @@ module Gitlab hooks: 'ProjectHook', merge_access_levels: 'ProtectedBranch::MergeAccessLevel', push_access_levels: 'ProtectedBranch::PushAccessLevel', - labels: :project_labels }.freeze + labels: :project_labels, + label: :project_label }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -20,7 +21,7 @@ module Gitlab IMPORTED_OBJECT_MAX_RETRIES = 5.freeze - EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels].freeze + EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels project_label group_label].freeze FINDER_ATTRIBUTES = %w[title project_id].freeze @@ -57,6 +58,8 @@ module Gitlab update_user_references update_project_references + + handle_group_label if group_label? reset_ci_tokens if @relation_name == 'Ci::Trigger' @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] set_st_diffs if @relation_name == :merge_request_diff @@ -124,6 +127,19 @@ module Gitlab @relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id'] end + def group_label? + @relation_hash['type'] == 'GroupLabel' + end + + def handle_group_label + # If there's no group, move the label to a project label + if @relation_hash['group_id'] + @relation_name = :group_label + else + @relation_hash['type'] = 'ProjectLabel' + end + end + def reset_ci_tokens return unless Gitlab::ImportExport.reset_tokens? diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 98323fe6be4..bf9dc279f7d 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -64,7 +64,29 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null + "priority": null, + "type": "ProjectLabel" + } + }, + { + "id": 3, + "label_id": 3, + "target_id": 40, + "target_type": "Issue", + "created_at": "2016-07-22T08:57:02.841Z", + "updated_at": "2016-07-22T08:57:02.841Z", + "label": { + "id": 3, + "title": "test3", + "color": "#428bca", + "group_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "priority": null, + "project_id": null, + "type": "GroupLabel" } } ], @@ -536,7 +558,8 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null + "priority": null, + "type": "ProjectLabel" } } ], 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 7582a732cdf..365d08940ba 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do it 'has the same label associated to two issues' do restored_project_json - expect(Label.first.issues.count).to eq(2) + expect(ProjectLabel.find_by_title('test2').issues.count).to eq(2) end it 'has milestones associated to two separate issues' do @@ -107,6 +107,33 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Label.first.label_links.first.target).not_to be_nil end + it 'has project labels' do + restored_project_json + + expect(ProjectLabel.count).to eq(2) + end + + it 'has no group labels' do + restored_project_json + + expect(GroupLabel.count).to eq(0) + end + + context 'with group' do + let!(:project) { create(:empty_project, + name: 'project', + path: 'project', + builds_access_level: ProjectFeature::DISABLED, + issues_access_level: ProjectFeature::DISABLED, + group: create(:group)) } + + it 'has group labels' do + restored_project_json + + expect(GroupLabel.count).to eq(1) + end + end + it 'has a project feature' 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 cf8f2200c57..9a8ba61559b 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,12 @@ 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 and group labels' do + label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type']} + + expect(label_types).to match(['ProjectLabel', 'GroupLabel']) + end + it 'saves the correct service type' do expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService') end @@ -135,15 +141,19 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do issue = create(:issue, assignee: user) snippet = create(:project_snippet) release = create(:release) + group = create(:group) project = create(:project, :public, issues: [issue], snippets: [snippet], - releases: [release] + releases: [release], + group: group ) - label = create(:label, project: project) - create(:label_link, label: label, target: issue) + project_label = create(:label, project: project) + group_label = create(:group_label, group: group) + create(:label_link, label: project_label, target: issue) + create(:label_link, label: group_label, target: issue) milestone = create(:milestone, project: project) merge_request = create(:merge_request, source_project: project, milestone: milestone) commit_status = create(:commit_status, project: project) -- cgit v1.2.1 From 723e576782aefa339a4db8916908c7ebe5a92f48 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Sep 2016 15:06:10 +0200 Subject: fix rubocop warning --- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 365d08940ba..6312b990a66 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -120,12 +120,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do end context 'with group' do - let!(:project) { create(:empty_project, + let!(:project) do + create(:empty_project, name: 'project', path: 'project', builds_access_level: ProjectFeature::DISABLED, issues_access_level: ProjectFeature::DISABLED, - group: create(:group)) } + group: create(:group)) + end it 'has group labels' do restored_project_json -- cgit v1.2.1 From 3db2261005c438faad8bf4a339d46eb7798f05b5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 29 Sep 2016 15:50:14 -0300 Subject: Reuse LabelsFinder on Banzai::Filter::LabelReferenceFilter --- app/finders/issuable_finder.rb | 5 ++-- app/finders/labels_finder.rb | 38 +++++++++++++++++++++++++---- app/models/project.rb | 4 +++ lib/banzai/filter/label_reference_filter.rb | 12 ++++----- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 6f2adf47c3a..41ea8f801c1 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -128,8 +128,7 @@ class IssuableFinder @labels = Label.where(title: label_names) if projects - label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) - @labels = @labels.where(labels: { id: label_ids }) + @labels = LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute end else @labels = Label.none @@ -277,7 +276,7 @@ class IssuableFinder items = items.with_label(label_names, params[:sort]) if projects - label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) + label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id) items = items.where(labels: { id: label_ids }) end end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index b8828bcdd32..28110be7097 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -16,8 +16,16 @@ class LabelsFinder < UnionFinder def label_ids label_ids = [] - label_ids << Label.where(group_id: projects.joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)).select(:id) - label_ids << Label.where(project_id: projects.select(:id)).select(:id) + + if project + label_ids << project.group.labels if project.group.present? + label_ids << project.labels + else + label_ids << Label.where(group_id: projects.group_ids) + label_ids << Label.where(project_id: projects.select(:id)) + end + + label_ids end def sort(items) @@ -37,18 +45,38 @@ class LabelsFinder < UnionFinder params[:project_id].presence end + def project_ids + params[:project_ids].presence + end + def title params[:title].presence end + def project + return @project if defined?(@project) + + if project_id + @project = available_projects.find(project_id) rescue nil + else + @project = nil + end + + @project + end + def projects return @projects if defined?(@projects) - @projects = ProjectsFinder.new.execute(current_user) - @projects = @projects.joins(:namespace).where(namespaces: { id: group_id, type: 'Group' }) if group_id - @projects = @projects.where(id: project_id) if project_id + @projects = available_projects + @projects = @projects.in_namespace(group_id) if group_id + @projects = @projects.where(id: project_ids) if project_ids @projects = @projects.reorder(nil) @projects end + + def available_projects + @available_projects ||= ProjectsFinder.new.execute(current_user) + end end diff --git a/app/models/project.rb b/app/models/project.rb index 41125223044..a04817987b4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -388,6 +388,10 @@ class Project < ActiveRecord::Base Project.count end end + + def group_ids + joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id) + end end def lfs_enabled? diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 649c697b415..2ce33aa1d15 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -39,13 +39,7 @@ module Banzai end def find_labels(project) - label_ids = [] - label_ids << project.group.labels.select(:id) if project.group.present? - label_ids << project.labels.select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - object_class.where("labels.id IN (#{union.to_sql})") + LabelsFinder.new(user, project_id: project.id).execute end # Parameters to pass to `Label.find_by` based on the given arguments @@ -91,6 +85,10 @@ module Banzai object.is_a?(ProjectLabel) && object.project == project end + def user + context[:current_user] || context[:author] + end + def project context[:project] end -- cgit v1.2.1 From 7e11ca86fdb23c967c25b19735770f99f936b32c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 3 Oct 2016 18:41:46 -0300 Subject: Reuse LabelsFinder on Issueable#add_labels_by_names --- app/models/concerns/issuable.rb | 12 +++--------- lib/api/merge_requests.rb | 4 ++-- lib/gitlab/fogbugz_import/importer.rb | 2 +- lib/gitlab/google_code_import/importer.rb | 2 +- spec/lib/gitlab/google_code_import/importer_spec.rb | 7 ++++--- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 1647d693a9d..fee68d9cc8f 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -234,19 +234,13 @@ module Issuable labels.delete_all end - def add_labels_by_names(label_names) - label_ids = [] - label_ids << project.group.labels.select(:id) if project.group.present? - label_ids << project.labels.select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - available_labels = Label.where("labels.id IN (#{union.to_sql})") + def add_labels_by_names(label_names, current_user) + available_labels = LabelsFinder.new(current_user, project_id: project.id).execute label_names.each do |label_name| title = label_name.strip label = available_labels.find_by(title: title) - label = project.labels.build(title: title, color: Label::DEFAULT_COLOR) if label.nil? + label ||= project.labels.build(title: title, color: Label::DEFAULT_COLOR) self.labels << label end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 2b685621da9..67fdd0be927 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -91,7 +91,7 @@ module API if merge_request.valid? # Find or create labels and attach to issue if params[:labels].present? - merge_request.add_labels_by_names(params[:labels].split(",")) + merge_request.add_labels_by_names(params[:labels].split(","), current_user) end present merge_request, with: Entities::MergeRequest, current_user: current_user @@ -201,7 +201,7 @@ module API # Find or create labels and attach to issue unless params[:labels].nil? merge_request.remove_labels - merge_request.add_labels_by_names(params[:labels].split(",")) + merge_request.add_labels_by_names(params[:labels].split(","), current_user) end present merge_request, with: Entities::MergeRequest, current_user: current_user diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 501d5a95547..1d6f97b99c7 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -129,7 +129,7 @@ module Gitlab assignee_id: assignee_id, state: bug['fOpen'] == 'true' ? 'opened' : 'closed' ) - issue.add_labels_by_names(labels) + issue.add_labels_by_names(labels, project.creator) if issue.iid != bug['ixBug'] issue.update_attribute(:iid, bug['ixBug']) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index ef8c3e35619..8d757da2264 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -100,7 +100,7 @@ module Gitlab state: raw_issue["state"] == "closed" ? "closed" : "opened" ) - issue.add_labels_by_names(labels) + issue.add_labels_by_names(labels, project.creator) if issue.iid != raw_issue["id"] issue.update_attribute(:iid, raw_issue["id"]) diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 54f85f8cffc..097861fd34d 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -15,6 +15,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do subject { described_class.new(project) } before do + project.team << [project.creator, :master] project.create_import_data(data: import_data) end @@ -31,9 +32,9 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do subject.execute %w( - Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical - Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security - Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery + Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical + Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security + Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery Component-Systray Component-Clock Component-Launcher Component-Tint2conf Component-Docs Component-New ).each do |label| label.sub!("-", ": ") -- cgit v1.2.1 From 9629bb962cd3666ac58cfbaba9d79df011221f41 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 3 Oct 2016 18:43:39 -0300 Subject: Add column type to labels and do the batch update in the same migration --- db/migrate/20160919144305_add_type_to_labels.rb | 7 ++++++- .../20160920191518_set_project_label_type_on_labels.rb | 17 ----------------- 2 files changed, 6 insertions(+), 18 deletions(-) delete mode 100644 db/migrate/20160920191518_set_project_label_type_on_labels.rb diff --git a/db/migrate/20160919144305_add_type_to_labels.rb b/db/migrate/20160919144305_add_type_to_labels.rb index 43aac7846d3..66172bda6ff 100644 --- a/db/migrate/20160919144305_add_type_to_labels.rb +++ b/db/migrate/20160919144305_add_type_to_labels.rb @@ -1,9 +1,14 @@ class AddTypeToLabels < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - DOWNTIME = false + DOWNTIME = true + DOWNTIME_REASON = 'Labels will not work as expected until this migration is complete.' def change add_column :labels, :type, :string + + update_column_in_batches(:labels, :type, 'ProjectLabel') do |table, query| + query.where(table[:project_id].not_eq(nil)) + end end end diff --git a/db/migrate/20160920191518_set_project_label_type_on_labels.rb b/db/migrate/20160920191518_set_project_label_type_on_labels.rb deleted file mode 100644 index af47d0320e2..00000000000 --- a/db/migrate/20160920191518_set_project_label_type_on_labels.rb +++ /dev/null @@ -1,17 +0,0 @@ -class SetProjectLabelTypeOnLabels < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - def up - update_column_in_batches(:labels, :type, 'ProjectLabel') do |table, query| - query.where(table[:project_id].not_eq(nil)) - end - end - - def down - update_column_in_batches(:labels, :type, nil) do |table, query| - query.where(table[:project_id].not_eq(nil)) - end - end -end -- cgit v1.2.1 From 11d786e7da9f767a877bbdd4fd482c0d0c4cd747 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 3 Oct 2016 18:59:04 -0300 Subject: Use try instead of ternary operator on Gitlab::ImportExport::ProjectTreeRestorer --- lib/gitlab/import_export/project_tree_restorer.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index accac5325f8..7cdba880a93 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -120,8 +120,7 @@ module Gitlab end def parsed_relation_hash(relation_hash) - group_id = restored_project.group ? restored_project.group.id : nil - relation_hash.merge!('group_id' => group_id, 'project_id' => restored_project.id) + relation_hash.merge!('group_id' => restored_project.group.try(:id), 'project_id' => restored_project.id) end end end -- cgit v1.2.1 From 1fd84700874ed29a7852b0f090dbaffd86cb8d00 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 22:30:18 -0300 Subject: Hide prioritized labels only when no labels are available to project --- app/views/projects/labels/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index c1ec9cabc40..f135bf6f6b4 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -16,7 +16,7 @@ .labels - if can?(current_user, :admin_label, @project) -# Only show it in the first page - - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1') + - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') .prioritized-labels{ class: ('hide' if hide) } %h5 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } -- cgit v1.2.1 From 00e3c2e00f1c81aa2f7a76e4d93c8a1fb2074d6e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 23:37:07 -0300 Subject: Fix replace label references with links for group labels --- lib/banzai/filter/label_reference_filter.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 2ce33aa1d15..21085ae6d49 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -39,7 +39,13 @@ module Banzai end def find_labels(project) - LabelsFinder.new(user, project_id: project.id).execute + label_ids = [] + label_ids << project.group.labels.select(:id) if project.group.present? + label_ids << project.labels.select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + Label.where("labels.id IN (#{union.to_sql})") end # Parameters to pass to `Label.find_by` based on the given arguments -- cgit v1.2.1 From f98e97fe78dce11a1f88c3961be402d179e63927 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 00:04:15 -0300 Subject: Reduce duplication between the project and group label forms --- app/controllers/groups/labels_controller.rb | 1 + app/views/groups/labels/_form.html.haml | 33 ----------------------------- app/views/groups/labels/edit.html.haml | 2 +- app/views/groups/labels/new.html.haml | 2 +- app/views/projects/labels/_form.html.haml | 33 ----------------------------- app/views/projects/labels/edit.html.haml | 2 +- app/views/projects/labels/new.html.haml | 2 +- app/views/shared/labels/_form.html.haml | 33 +++++++++++++++++++++++++++++ 8 files changed, 38 insertions(+), 70 deletions(-) delete mode 100644 app/views/groups/labels/_form.html.haml delete mode 100644 app/views/projects/labels/_form.html.haml create mode 100644 app/views/shared/labels/_form.html.haml diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 0ebdee55c79..483a5aedf12 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -21,6 +21,7 @@ class Groups::LabelsController < Groups::ApplicationController def new @label = @group.labels.new + @previous_labels_path = previous_labels_path end def create diff --git a/app/views/groups/labels/_form.html.haml b/app/views/groups/labels/_form.html.haml deleted file mode 100644 index a0b44b0dcfb..00000000000 --- a/app/views/groups/labels/_form.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -= form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| - = form_errors(@label) - - .form-group - = f.label :title, class: 'control-label' - .col-sm-10 - = f.text_field :title, class: "form-control", required: true, autofocus: true - .form-group - = f.label :description, class: 'control-label' - .col-sm-10 - = f.text_field :description, class: "form-control js-quick-submit" - .form-group - = f.label :color, "Background color", class: 'control-label' - .col-sm-10 - .input-group - .input-group-addon.label-color-preview   - = f.color_field :color, class: "form-control" - .help-block - Choose any color. - %br - Or you can choose one of suggested colors below - - .suggest-colors - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - - .form-actions - - if @label.persisted? - = f.submit 'Save changes', class: 'btn btn-save js-save-button' - - else - = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to 'Cancel', @previous_labels_path, class: 'btn btn-cancel' diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml index 28471f407ad..836981fc6fd 100644 --- a/app/views/groups/labels/edit.html.haml +++ b/app/views/groups/labels/edit.html.haml @@ -4,4 +4,4 @@ Edit Label %hr -= render 'form', url: group_label_path(@group, @label) += render 'shared/labels/form', url: group_label_path(@group, @label), back_path: @previous_labels_path diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml index 257ae97de03..2be87460b1d 100644 --- a/app/views/groups/labels/new.html.haml +++ b/app/views/groups/labels/new.html.haml @@ -5,4 +5,4 @@ New Label %hr -= render 'form', url: group_labels_path += render 'shared/labels/form', url: group_labels_path, back_path: @previous_labels_path diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml deleted file mode 100644 index 28a062c7eb5..00000000000 --- a/app/views/projects/labels/_form.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -= form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| - = form_errors(@label) - - .form-group - = f.label :title, class: 'control-label' - .col-sm-10 - = f.text_field :title, class: "form-control", required: true, autofocus: true - .form-group - = f.label :description, class: 'control-label' - .col-sm-10 - = f.text_field :description, class: "form-control js-quick-submit" - .form-group - = f.label :color, "Background color", class: 'control-label' - .col-sm-10 - .input-group - .input-group-addon.label-color-preview   - = f.text_field :color, class: "form-control" - .help-block - Choose any color. - %br - Or you can choose one of suggested colors below - - .suggest-colors - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - - .form-actions - - if @label.persisted? - = f.submit 'Save changes', class: 'btn btn-save js-save-button' - - else - = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to 'Cancel', namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 49adb593559..a80a07b52e6 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -6,4 +6,4 @@ %h3.page-title Edit Label %hr - = render 'form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label) + = render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project) diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 0c177feb43c..f0d9be744d1 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -6,4 +6,4 @@ %h3.page-title New Label %hr - = render 'form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project) + = render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project) diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml new file mode 100644 index 00000000000..647e05e5ff7 --- /dev/null +++ b/app/views/shared/labels/_form.html.haml @@ -0,0 +1,33 @@ += form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| + = form_errors(@label) + + .form-group + = f.label :title, class: 'control-label' + .col-sm-10 + = f.text_field :title, class: "form-control", required: true, autofocus: true + .form-group + = f.label :description, class: 'control-label' + .col-sm-10 + = f.text_field :description, class: "form-control js-quick-submit" + .form-group + = f.label :color, "Background color", class: 'control-label' + .col-sm-10 + .input-group + .input-group-addon.label-color-preview   + = f.text_field :color, class: "form-control" + .help-block + Choose any color. + %br + Or you can choose one of suggested colors below + + .suggest-colors + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + + .form-actions + - if @label.persisted? + = f.submit 'Save changes', class: 'btn btn-save js-save-button' + - else + = f.submit 'Create Label', class: 'btn btn-create js-save-button' + = link_to 'Cancel', back_path, class: 'btn btn-cancel' -- cgit v1.2.1 From bdf365e64f35e76ba6d9a372111ce502db11827e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 13:57:52 -0300 Subject: Use delegate! on group and project labels policies --- app/policies/group_label_policy.rb | 2 +- app/policies/project_label_policy.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb index 4d4052c5800..7b34aa182eb 100644 --- a/app/policies/group_label_policy.rb +++ b/app/policies/group_label_policy.rb @@ -1,5 +1,5 @@ class GroupLabelPolicy < BasePolicy def rules - can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.group) + delegate! @subject.group end end diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb index e7bd58372a6..b12b4c5166b 100644 --- a/app/policies/project_label_policy.rb +++ b/app/policies/project_label_policy.rb @@ -1,5 +1,5 @@ class ProjectLabelPolicy < BasePolicy def rules - can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) + delegate! @subject.project end end -- cgit v1.2.1 From 2deef25eb0c87db2fddf8b25c95891650d9c43a7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:13:26 -0300 Subject: Use Label.attributes instead of Label.dup when creating label templates --- app/models/project.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index a04817987b4..7ab624eafdf 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -733,12 +733,7 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| - label = label.dup - label.template = false - label.project_id = self.id - label.type = 'ProjectLabel' - - label.save + self.labels.create!(label.attributes.symbolize_keys.except(:id, :template)) end end -- cgit v1.2.1 From 73bfc15cd4fc681015e84f7db8507e38e4ca8b59 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:18:21 -0300 Subject: Always use symbols for type on LabelsHelper#link_to_label --- app/helpers/labels_helper.rb | 2 +- app/views/projects/merge_requests/_merge_request.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index e8992c114b0..af8741f5e06 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -46,7 +46,7 @@ module LabelsHelper end end - def label_filter_path(subject, label, type: issue) + def label_filter_path(subject, label, type: :issue) case subject when Group send("#{type.to_s.pluralize}_group_path", diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index ad62bf50b57..12408068834 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -62,7 +62,7 @@ - if merge_request.labels.any?   - merge_request.labels.each do |label| - = link_to_label(label, subject: merge_request.project, type: 'merge_request') + = link_to_label(label, subject: merge_request.project, type: :merge_request) - if merge_request.tasks?   %span.task-status -- cgit v1.2.1 From 933ebb8f9b289cc077e4d16fd62e1e7b04bc10de Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:35:50 -0300 Subject: Use present? instead of presence on Projects::MergeRequestsController --- app/controllers/projects/merge_requests_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9171b47cda1..0f593d1a936 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -40,7 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.preload(:target_project) - @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence + if params[:label_name].present? + labels_params = { project_id: @project.id, title: params[:label_name] } + @labels = LabelsFinder.new(current_user, labels_params).execute + end respond_to do |format| format.html -- cgit v1.2.1 From 504682db9e2dd99fe827940ac18d5ea8030ae49c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:39:32 -0300 Subject: Limit what label fields we expose on Dashboard::LabelsController#index --- app/controllers/dashboard/labels_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 05f7bc37952..d5031da867a 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,7 +1,9 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index + labels = LabelsFinder.new(current_user).execute + respond_to do |format| - format.json { render json: LabelsFinder.new(current_user).execute } + format.json { render json: labels.as_json(only: [:id, :title, :color]) } end end end -- cgit v1.2.1 From 36fee24c80afa1e1c5f55cd5f5e5f45a60531d8e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:46:07 -0300 Subject: Limit what label fields we expose on Groups::LabelsController#index --- app/controllers/groups/labels_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 483a5aedf12..0a3dee5ce39 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -14,7 +14,8 @@ class Groups::LabelsController < Groups::ApplicationController end format.json do - render json: LabelsFinder.new(current_user, group_id: @group.id).execute + available_labels = LabelsFinder.new(current_user, group_id: @group.id).execute + render json: available_labels.as_json(only: [:id, :title, :color]) end end end -- cgit v1.2.1 From bde992a83a9e0655dd9c2f59d7314c14b714bd31 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:47:02 -0300 Subject: Limit what label fields we expose on Projects::LabelsController#index --- app/controllers/projects/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 3154a4435f6..87c9101551b 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -18,7 +18,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @labels + render json: @labels.as_json(only: [:id, :title, :color]) end end end -- cgit v1.2.1 From f74d5f2750c638f30020ba727e2947b7207bf0e2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 15:50:17 -0300 Subject: Fix shared labels filter --- app/views/shared/issuable/_filter.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 8c2036a1cde..f8018fc3de0 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -27,7 +27,7 @@ = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } + = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title, :type).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } .filter-item.inline.reset-filters %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters -- cgit v1.2.1 From 1e6d136af31b0b91a03881a0c20b9bfa448201ee Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 16:30:40 -0300 Subject: Keep cross project reference logic in GroupLabel#to_reference --- app/models/group_label.rb | 16 ++++++++++++++-- lib/gitlab/gfm/reference_rewriter.rb | 10 +--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/models/group_label.rb b/app/models/group_label.rb index bfcaf3df27e..c7efa29a5f6 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -15,8 +15,20 @@ class GroupLabel < Label # # Returns a String # - def to_reference(from_project = nil, format: :id) + def to_reference(source_project = nil, target_project = nil, format: :id) format_reference = label_format_reference(format) - "#{self.class.reference_prefix}#{format_reference}" + reference = "#{self.class.reference_prefix}#{format_reference}" + + if cross_project_reference?(source_project, target_project) + source_project.to_reference + reference + else + reference + end + end + + private + + def cross_project_reference?(source_project, target_project) + source_project && target_project && source_project != target_project end end diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb index d0b8cd90e0e..a7c596dced0 100644 --- a/lib/gitlab/gfm/reference_rewriter.rb +++ b/lib/gitlab/gfm/reference_rewriter.rb @@ -76,15 +76,7 @@ module Gitlab if referable.respond_to?(:project) referable.to_reference(target_project) else - to_reference(referable, target_project) - end - end - - def to_reference(referable, target_project) - if @source_project != target_project - @source_project.to_reference + referable.to_reference - else - referable.to_reference + referable.to_reference(@source_project, target_project) end end -- cgit v1.2.1 From 247859c82915a0ee88944c1fcda3f6faf49e54c0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 16:33:55 -0300 Subject: Render all available labels to project on project labels dropdown --- app/controllers/projects/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 87c9101551b..f4ad503c9a6 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -18,7 +18,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @labels.as_json(only: [:id, :title, :color]) + render json: @available_labels.as_json(only: [:id, :title, :color]) end end end -- cgit v1.2.1 From 0e8dd599134f17e58cf533ab21cf3c4a5b50c353 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 16:51:11 -0300 Subject: Move common logic to reference group/project label to Label#to_reference --- app/models/group_label.rb | 27 +-------------------------- app/models/label.rb | 28 ++++++++++++++++++++++++++++ app/models/project_label.rb | 24 ++---------------------- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/app/models/group_label.rb b/app/models/group_label.rb index c7efa29a5f6..a1d8d087726 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -3,32 +3,7 @@ class GroupLabel < Label validates :group, presence: true - ## - # Returns the String necessary to reference this GroupLabel in Markdown - # - # format - Symbol format to use (default: :id, optional: :name) - # - # Examples: - # - # GroupLabel.first.to_reference # => "~1" - # GroupLabel.first.to_reference(format: :name) # => "~\"bug\"" - # - # Returns a String - # def to_reference(source_project = nil, target_project = nil, format: :id) - format_reference = label_format_reference(format) - reference = "#{self.class.reference_prefix}#{format_reference}" - - if cross_project_reference?(source_project, target_project) - source_project.to_reference + reference - else - reference - end - end - - private - - def cross_project_reference?(source_project, target_project) - source_project && target_project && source_project != target_project + super(source_project, target_project, format: format) end end diff --git a/app/models/label.rb b/app/models/label.rb index 7dd2d8790b0..444f45fa09e 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -97,8 +97,36 @@ class Label < ActiveRecord::Base write_attribute(:title, sanitize_title(value)) if value.present? end + ## + # Returns the String necessary to reference this Label in Markdown + # + # format - Symbol format to use (default: :id, optional: :name) + # + # Examples: + # + # Label.first.to_reference # => "~1" + # Label.first.to_reference(format: :name) # => "~\"bug\"" + # Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1" + # + # Returns a String + # + def to_reference(source_project = nil, target_project = nil, format: :id) + format_reference = label_format_reference(format) + reference = "#{self.class.reference_prefix}#{format_reference}" + + if cross_project_reference?(source_project, target_project) + source_project.to_reference + reference + else + reference + end + end + private + def cross_project_reference?(source_project, target_project) + source_project && target_project && source_project != target_project + end + def issues_count(user, params = {}) IssuesFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) .execute diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 2fc074dc401..a246a90435d 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -7,28 +7,8 @@ class ProjectLabel < Label delegate :group, to: :project, allow_nil: true - ## - # Returns the String necessary to reference this ProjectLabel in Markdown - # - # format - Symbol format to use (default: :id, optional: :name) - # - # Examples: - # - # ProjectLabel.first.to_reference # => "~1" - # ProjectLabel.first.to_reference(format: :name) # => "~\"bug\"" - # ProjectLabel.first.to_reference(project) # => "gitlab-org/gitlab-ce~1" - # - # Returns a String - # - def to_reference(from_project = nil, format: :id) - format_reference = label_format_reference(format) - reference = "#{self.class.reference_prefix}#{format_reference}" - - if cross_project_reference?(from_project) - project.to_reference + reference - else - reference - end + def to_reference(target_project = nil, format: :id) + super(project, target_project, format: format) end private -- cgit v1.2.1 From 6792644ae77476af88343d4a5b9e370326d331ea Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:14:05 -0300 Subject: Use present? instead of presence on Projects::IssuesController#index --- app/controllers/projects/issues_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 9f18c8c03df..cb649264146 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -26,7 +26,7 @@ class Projects::IssuesController < Projects::ApplicationController @issues = issues_collection @issues = @issues.page(params[:page]) - if params[:label_name].presence + if params[:label_name].present? @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute end -- cgit v1.2.1 From da9407927be1dd4a6b0b00be360e61d6bb507da8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:16:51 -0300 Subject: Remove unnecessary `title.present?` on LabelsFinder --- app/finders/labels_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 28110be7097..5ee2e1ee6e8 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -33,7 +33,7 @@ class LabelsFinder < UnionFinder end def with_title(items) - items = items.where(title: title) if title.present? + items = items.where(title: title) if title items end -- cgit v1.2.1 From 5912251e614421b4798ac235c7f408a1b45a2b4c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:22:11 -0300 Subject: Use reverse_merge on Label#issues_count and Label#merge_requests_count --- app/models/label.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index 444f45fa09e..112d9f3fbe5 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -128,13 +128,13 @@ class Label < ActiveRecord::Base end def issues_count(user, params = {}) - IssuesFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) .execute .count end def merge_requests_count(user, params = {}) - MergeRequestsFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) .execute .count end -- cgit v1.2.1 From fc59d357201a907e6079f6f0a7fc9a31f2957f88 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:25:25 -0300 Subject: Skip update query if label have the same id on Labels::TransferService --- app/services/labels/transfer_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 81897c62c0f..04312c114ef 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -19,6 +19,8 @@ module Labels labels_to_transfer.find_each do |label| new_label_id = find_or_create_label!(label) + next if new_label_id == label.id + LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) end end -- cgit v1.2.1 From cece77f273407da4a9ed66acda53e9ac4117dbaf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:45:39 -0300 Subject: Fix validation to allow updates to description/color of project label --- app/models/project_label.rb | 2 +- spec/models/project_label_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index a246a90435d..5b739bcdadf 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -14,7 +14,7 @@ class ProjectLabel < Label private def title_must_not_exist_at_group_level - return unless group.present? + return unless group.present? && title_changed? if group.labels.with_title(self.title).exists? errors.add(:title, :label_already_exists_at_group_level, group: group.name) diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 7966c52c38d..c861d4b73bb 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -40,6 +40,16 @@ describe ProjectLabel, models: true do expect(label.errors[:title]).to be_empty end + + it 'does not returns error when title does not change' do + project_label = create(:label, project: project, name: 'Security') + create(:group_label, group: group, name: 'Security') + project_label.description = 'Security related stuff.' + + project_label.valid? + + expect(project_label .errors[:title]).to be_empty + end end end -- cgit v1.2.1 From 0dbb47f00ee7333bbe76ddda853511c9120e2ad6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:03:52 -0300 Subject: Fix IssuableBaseService#find_or_create_label_ids --- app/services/issuable_base_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index b3e4f8dcf27..deadf1fe283 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -88,7 +88,7 @@ class IssuableBaseService < BaseService return unless labels params[:label_ids] = labels.split(',').map do |label_name| - label = available_labels.find_by(title: title).select(:id) + label = available_labels.find_by(title: label_name) label ||= project.labels.create(title: label_name.strip, color: Label::DEFAULT_COLOR) label.id -- cgit v1.2.1 From 033ea9d1e80544df5236ca045c88f649e41afbc7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:04:55 -0300 Subject: Move label management to services on merge requests API --- lib/api/merge_requests.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 67fdd0be927..bf8504e1101 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -86,14 +86,11 @@ module API render_api_error!({ labels: errors }, 400) end + attrs[:labels] = params[:labels] if params[:labels] + merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute if merge_request.valid? - # Find or create labels and attach to issue - if params[:labels].present? - merge_request.add_labels_by_names(params[:labels].split(","), current_user) - end - present merge_request, with: Entities::MergeRequest, current_user: current_user else handle_merge_request_errors! merge_request.errors @@ -195,15 +192,11 @@ module API render_api_error!({ labels: errors }, 400) end + attrs[:labels] = params[:labels] if params[:labels] + merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request) if merge_request.valid? - # Find or create labels and attach to issue - unless params[:labels].nil? - merge_request.remove_labels - merge_request.add_labels_by_names(params[:labels].split(","), current_user) - end - present merge_request, with: Entities::MergeRequest, current_user: current_user else handle_merge_request_errors! merge_request.errors -- cgit v1.2.1 From bf710b5119dce329a1ffd43b01b3a4dbb3e94b09 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:34:44 -0300 Subject: Validate label params against all labels available to project on the API --- lib/api/helpers.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 67473f300c9..45120898b76 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -71,6 +71,10 @@ module API @project ||= find_project(params[:id]) end + def available_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute + end + def find_project(id) project = Project.find_with_namespace(id) || Project.find_by(id: id) @@ -118,7 +122,7 @@ module API end def find_project_label(id) - label = user_project.labels.find_by_id(id) || user_project.labels.find_by_title(id) + label = available_labels.find_by_id(id) || available_labels.find_by_title(id) label || not_found!('Label') end @@ -197,16 +201,11 @@ module API def validate_label_params(params) errors = {} - if params[:labels].present? - params[:labels].split(',').each do |label_name| - label = user_project.labels.create_with( - color: Label::DEFAULT_COLOR).find_or_initialize_by( - title: label_name.strip) + params[:labels].to_s.split(',').each do |label_name| + label = available_labels.find_or_initialize_by(title: label_name.strip) + next if label.valid? - if label.invalid? - errors[label.title] = label.errors - end - end + errors[label.title] = label.errors end errors -- cgit v1.2.1 From 68f30b2fff362805568588f416709e7000d75ce3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:48:59 -0300 Subject: Add support to group labels on issues board API --- lib/api/boards.rb | 4 ++-- spec/requests/api/boards_spec.rb | 25 +++++++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index b14dd4f6e83..4ac491edc1b 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -65,8 +65,8 @@ module API requires :label_id, type: Integer, desc: 'The ID of an existing label' end post '/lists' do - unless user_project.labels.exists?(params[:label_id]) - render_api_error!({ error: "Label not found!" }, 400) + unless available_labels.exists?(params[:label_id]) + render_api_error!({ error: 'Label not found!' }, 400) end authorize!(:admin_list, user_project) diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index f4b04445c6c..4f5c09a3029 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -106,9 +106,20 @@ describe API::API, api: true do describe "POST /projects/:id/board/lists" do let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - it 'creates a new issue board list' do - post api(base_url, user), - label_id: ux_label.id + it 'creates a new issue board list for group labels' do + group = create(:group) + group_label = create(:group_label, group: group) + project.update(group: group) + + post api(base_url, user), label_id: group_label.id + + expect(response).to have_http_status(201) + expect(json_response['label']['name']).to eq(group_label.title) + expect(json_response['position']).to eq(3) + end + + it 'creates a new issue board list for project labels' do + post api(base_url, user), label_id: ux_label.id expect(response).to have_http_status(201) expect(json_response['label']['name']).to eq(ux_label.title) @@ -116,15 +127,13 @@ describe API::API, api: true do end it 'returns 400 when creating a new list if label_id is invalid' do - post api(base_url, user), - label_id: 23423 + post api(base_url, user), label_id: 23423 expect(response).to have_http_status(400) end - it "returns 403 for project members with guest role" do - put api("#{base_url}/#{test_list.id}", guest), - position: 1 + it 'returns 403 for project members with guest role' do + put api("#{base_url}/#{test_list.id}", guest), position: 1 expect(response).to have_http_status(403) end -- cgit v1.2.1 From 9b288238549dac5b59fd467f6ee1fdc53b6c783e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:53:06 -0300 Subject: List all available labels to the project on the labels API --- lib/api/labels.rb | 2 +- spec/requests/api/labels_spec.rb | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/api/labels.rb b/lib/api/labels.rb index c806829d69e..642e6345b9e 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -11,7 +11,7 @@ module API # Example Request: # GET /projects/:id/labels get ':id/labels' do - present user_project.labels, with: Entities::Label, current_user: current_user + present available_labels, with: Entities::Label, current_user: current_user end # Creates a new label diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 83789223019..1da9988978b 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -12,12 +12,18 @@ describe API::API, api: true do end describe 'GET /projects/:id/labels' do - it 'returns project labels' do + it 'returns all available labels to the project' do + group = create(:group) + group_label = create(:group_label, group: group) + project.update(group: group) + get api("/projects/#{project.id}/labels", user) + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.first['name']).to eq(label1.name) + expect(json_response.size).to eq(2) + expect(json_response.first['name']).to eq(group_label.name) + expect(json_response.second['name']).to eq(label1.name) end end -- cgit v1.2.1 From ed7591f7b8c55498e3e38be41cc049a92b9fe4a6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 19:05:07 -0300 Subject: Remove unused method Issuable#remove_labels --- app/models/concerns/issuable.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index fee68d9cc8f..0b57e318662 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -230,10 +230,6 @@ module Issuable labels.order('title ASC').pluck(:title) end - def remove_labels - labels.delete_all - end - def add_labels_by_names(label_names, current_user) available_labels = LabelsFinder.new(current_user, project_id: project.id).execute -- cgit v1.2.1 From f008cdf218952556a0dc93962f55c0ff50733594 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 19:30:02 -0300 Subject: Remove Issuable#add_labels_by_names --- app/models/concerns/issuable.rb | 12 ------------ lib/gitlab/fogbugz_import/importer.rb | 25 ++++++++++--------------- lib/gitlab/google_code_import/importer.rb | 19 ++++++++----------- 3 files changed, 18 insertions(+), 38 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 0b57e318662..76de927ceab 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -230,18 +230,6 @@ module Issuable labels.order('title ASC').pluck(:title) end - def add_labels_by_names(label_names, current_user) - available_labels = LabelsFinder.new(current_user, project_id: project.id).execute - - label_names.each do |label_name| - title = label_name.strip - label = available_labels.find_by(title: title) - label ||= project.labels.build(title: title, color: Label::DEFAULT_COLOR) - - self.labels << label - end - end - # Convert this Issuable class name to a format usable by Ability definitions # # Examples: diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 1d6f97b99c7..9e926e2681a 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -122,25 +122,20 @@ module Gitlab author_id = user_info(bug['ixPersonOpenedBy'])[:gitlab_id] || project.creator_id issue = Issue.create!( - project_id: project.id, - title: bug['sTitle'], - description: body, - author_id: author_id, - assignee_id: assignee_id, - state: bug['fOpen'] == 'true' ? 'opened' : 'closed' + iid: bug['ixBug'], + project_id: project.id, + title: bug['sTitle'], + description: body, + author_id: author_id, + assignee_id: assignee_id, + state: bug['fOpen'] == 'true' ? 'opened' : 'closed', + created_at: date, + updated_at: DateTime.parse(bug['dtLastUpdated']) ) - issue.add_labels_by_names(labels, project.creator) - if issue.iid != bug['ixBug'] - issue.update_attribute(:iid, bug['ixBug']) - end + issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) import_issue_comments(issue, comments) - - issue.update_attribute(:created_at, date) - - last_update = DateTime.parse(bug['dtLastUpdated']) - issue.update_attribute(:updated_at, last_update) end end diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 8d757da2264..79a0eaba1e8 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -92,19 +92,16 @@ module Gitlab end issue = Issue.create!( - project_id: project.id, - title: raw_issue["title"], - description: body, - author_id: project.creator_id, - assignee_id: assignee_id, - state: raw_issue["state"] == "closed" ? "closed" : "opened" + iid: raw_issue['id'], + project_id: project.id, + title: raw_issue['title'], + description: body, + author_id: project.creator_id, + assignee_id: assignee_id, + state: raw_issue['state'] == 'closed' ? 'closed' : 'opened' ) - issue.add_labels_by_names(labels, project.creator) - - if issue.iid != raw_issue["id"] - issue.update_attribute(:iid, raw_issue["id"]) - end + issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) import_issue_comments(issue, comments) end -- cgit v1.2.1 From de9d3915d249a59e65226f6dd2ebe1f50a47306e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 19:38:22 -0300 Subject: Remove `::` for method call on Label#text_color --- app/models/label.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/label.rb b/app/models/label.rb index 112d9f3fbe5..8b775e81db7 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -90,7 +90,7 @@ class Label < ActiveRecord::Base end def text_color - LabelsHelper::text_color_for_bg(self.color) + LabelsHelper.text_color_for_bg(self.color) end def title=(value) -- cgit v1.2.1 From 297892011330ecdd2fa7cbe47fbc6fd4f3b62171 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 16:12:07 -0300 Subject: Add LabelPriority model --- app/models/label.rb | 7 +-- app/models/label_priority.rb | 8 ++++ .../20161014173530_create_label_priorities.rb | 55 ++++++++++++++++++++++ db/schema.rb | 15 +++++- spec/factories/label_priorities.rb | 7 +++ spec/models/label_priority_spec.rb | 21 +++++++++ spec/models/label_spec.rb | 1 + 7 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 app/models/label_priority.rb create mode 100644 db/migrate/20161014173530_create_label_priorities.rb create mode 100644 spec/factories/label_priorities.rb create mode 100644 spec/models/label_priority_spec.rb diff --git a/app/models/label.rb b/app/models/label.rb index 8b775e81db7..3ce4e253035 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -16,6 +16,7 @@ class Label < ActiveRecord::Base default_value_for :color, DEFAULT_COLOR has_many :lists, dependent: :destroy + has_many :priorities, class_name: 'LabelPriority' has_many :label_links, dependent: :destroy has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' @@ -26,8 +27,6 @@ class Label < ActiveRecord::Base validates :title, presence: true, format: { with: /\A[^,]+\z/ } validates :title, uniqueness: { scope: [:group_id, :project_id] } - before_save :nullify_priority - default_scope { order(title: :asc) } scope :templates, -> { where(template: true) } @@ -149,10 +148,6 @@ class Label < ActiveRecord::Base end end - def nullify_priority - self.priority = nil if priority.blank? - end - def sanitize_title(value) CGI.unescapeHTML(Sanitize.clean(value.to_s)) end diff --git a/app/models/label_priority.rb b/app/models/label_priority.rb new file mode 100644 index 00000000000..5b85e0b6533 --- /dev/null +++ b/app/models/label_priority.rb @@ -0,0 +1,8 @@ +class LabelPriority < ActiveRecord::Base + belongs_to :project + belongs_to :label + + validates :project, :label, :priority, presence: true + validates :label_id, uniqueness: { scope: :project_id } + validates :priority, numericality: { only_integer: true, greater_than_or_equal_to: 0 } +end diff --git a/db/migrate/20161014173530_create_label_priorities.rb b/db/migrate/20161014173530_create_label_priorities.rb new file mode 100644 index 00000000000..f9d94ebdc70 --- /dev/null +++ b/db/migrate/20161014173530_create_label_priorities.rb @@ -0,0 +1,55 @@ +class CreateLabelPriorities < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'Prioritezed labels will not work as expected until this migration is complete.' + + disable_ddl_transaction! + + def up + create_table :label_priorities do |t| + t.references :project, foreign_key: { on_delete: :cascade }, null: false + t.references :label, foreign_key: { on_delete: :cascade }, null: false + t.integer :priority, null: false + + t.timestamps null: false + end + + execute <<-EOF.strip_heredoc + INSERT INTO label_priorities (project_id, label_id, priority, created_at, updated_at) + SELECT labels.project_id, labels.id, labels.priority, NOW(), NOW() + FROM labels + WHERE labels.project_id IS NOT NULL + AND labels.priority IS NOT NULL; + EOF + + add_concurrent_index :label_priorities, [:project_id, :label_id], unique: true + add_concurrent_index :label_priorities, :priority + + remove_column :labels, :priority + end + + def down + add_column :labels, :priority, :integer + + if Gitlab::Database.mysql? + execute <<-EOF.strip_heredoc + UPDATE labels + INNER JOIN label_priorities ON labels.id = label_priorities.label_id AND labels.project_id = label_priorities.project_id + SET labels.priority = label_priorities.priority; + EOF + else + execute <<-EOF.strip_heredoc + UPDATE labels + SET priority = label_priorities.priority + FROM label_priorities + WHERE labels.id = label_priorities.label_id + AND labels.project_id = label_priorities.project_id; + EOF + end + + add_concurrent_index :labels, :priority + + drop_table :label_priorities + end +end diff --git a/db/schema.rb b/db/schema.rb index 37f0be0e834..e17fdf75f88 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -519,6 +519,17 @@ ActiveRecord::Schema.define(version: 20161017095000) do add_index "label_links", ["label_id"], name: "index_label_links_on_label_id", using: :btree add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree + create_table "label_priorities", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "label_id", null: false + t.integer "priority", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "label_priorities", ["priority"], name: "index_label_priorities_on_priority", using: :btree + add_index "label_priorities", ["project_id", "label_id"], name: "index_label_priorities_on_project_id_and_label_id", unique: true, using: :btree + create_table "labels", force: :cascade do |t| t.string "title" t.string "color" @@ -527,14 +538,12 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.datetime "updated_at" t.boolean "template", default: false t.string "description" - t.integer "priority" t.text "description_html" t.string "type" t.integer "group_id" end add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree - add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree add_index "labels", ["title"], name: "index_labels_on_title", using: :btree @@ -1216,6 +1225,8 @@ ActiveRecord::Schema.define(version: 20161017095000) do add_foreign_key "boards", "projects" add_foreign_key "issue_metrics", "issues", on_delete: :cascade + add_foreign_key "label_priorities", "labels", on_delete: :cascade + add_foreign_key "label_priorities", "projects", on_delete: :cascade add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "lists", "boards" add_foreign_key "lists", "labels" diff --git a/spec/factories/label_priorities.rb b/spec/factories/label_priorities.rb new file mode 100644 index 00000000000..f25939d2d3e --- /dev/null +++ b/spec/factories/label_priorities.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :label_priority do + project factory: :empty_project + label + sequence(:priority) + end +end diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb new file mode 100644 index 00000000000..5f7fa3aa047 --- /dev/null +++ b/spec/models/label_priority_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe LabelPriority, models: true do + describe 'relationships' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:label) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:label) } + it { is_expected.to validate_presence_of(:priority) } + it { is_expected.to validate_numericality_of(:priority).only_integer.is_greater_than_or_equal_to(0) } + + it 'validates uniqueness of label_id scoped to project_id' do + create(:label_priority) + + expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:project_id) + end + end +end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index c6e1ea19987..4af0fb6afa9 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -10,6 +10,7 @@ describe Label, models: true do it { is_expected.to have_many(:issues).through(:label_links).source(:target) } it { is_expected.to have_many(:label_links).dependent(:destroy) } it { is_expected.to have_many(:lists).dependent(:destroy) } + it { is_expected.to have_many(:priorities).class_name('LabelPriority') } end describe 'validation' do -- cgit v1.2.1 From 67314e95ae836365fa1989439a6379aac781a0b4 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 18:32:44 -0300 Subject: Add support to group labels prioritization on project level --- app/controllers/projects/labels_controller.rb | 13 +++++++------ app/models/label.rb | 10 ++++++---- spec/features/projects/labels/update_prioritization_spec.rb | 8 ++++---- spec/models/label_priority_spec.rb | 1 - 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index f4ad503c9a6..f453b70fb5d 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -12,8 +12,8 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index - @prioritized_labels = @available_labels.prioritized - @labels = @available_labels.unprioritized.page(params[:page]) + @prioritized_labels = @available_labels.prioritized(@project) + @labels = @available_labels.unprioritized(@project).page(params[:page]) respond_to do |format| format.html @@ -84,11 +84,10 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| label = @available_labels.find(params[:id]) - if label.update_attribute(:priority, nil) + if label.priorities.where(project_id: project).delete_all format.json { render json: label } else - message = label.errors.full_messages.uniq.join('. ') - format.json { render json: { message: message }, status: :unprocessable_entity } + format.json { head :unprocessable_entity } end end end @@ -100,7 +99,9 @@ class Projects::LabelsController < Projects::ApplicationController params[:label_ids].each_with_index do |label_id, index| next unless label_ids.include?(label_id.to_i) - Label.where(id: label_id).update_all(priority: index) + label_priority = LabelPriority.find_or_initialize_by(project_id: @project.id, label_id: label_id) + label_priority.priority = index + label_priority.save! end end diff --git a/app/models/label.rb b/app/models/label.rb index 3ce4e253035..ea11d9d7864 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -32,12 +32,14 @@ class Label < ActiveRecord::Base scope :templates, -> { where(template: true) } scope :with_title, ->(title) { where(title: title) } - def self.prioritized - where.not(priority: nil).reorder(:priority, :title) + def self.prioritized(project) + joins(:priorities) + .where(label_priorities: { project_id: project }) + .reorder('label_priorities.priority ASC, labels.title ASC') end - def self.unprioritized - where(priority: nil) + def self.unprioritized(project) + where.not(id: prioritized(project).select(:id)) end alias_attribute :name, :title diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 84a12a38c26..c9fa8315e79 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -35,7 +35,7 @@ feature 'Prioritize labels', feature: true do end scenario 'user can unprioritize a group label', js: true do - feature.update(priority: 1) + create(:label_priority, project: project, label: feature, priority: 1) visit namespace_project_labels_path(project.namespace, project) @@ -70,7 +70,7 @@ feature 'Prioritize labels', feature: true do end scenario 'user can unprioritize a project label', js: true do - bug.update(priority: 1) + create(:label_priority, project: project, label: bug, priority: 1) visit namespace_project_labels_path(project.namespace, project) @@ -89,8 +89,8 @@ feature 'Prioritize labels', feature: true do end scenario 'user can sort prioritized labels and persist across reloads', js: true do - bug.update(priority: 1) - feature.update(priority: 2) + create(:label_priority, project: project, label: bug, priority: 1) + create(:label_priority, project: project, label: feature, priority: 2) visit namespace_project_labels_path(project.namespace, project) diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb index 5f7fa3aa047..d18c2f7949a 100644 --- a/spec/models/label_priority_spec.rb +++ b/spec/models/label_priority_spec.rb @@ -9,7 +9,6 @@ describe LabelPriority, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:label) } - it { is_expected.to validate_presence_of(:priority) } it { is_expected.to validate_numericality_of(:priority).only_integer.is_greater_than_or_equal_to(0) } it 'validates uniqueness of label_id scoped to project_id' do -- cgit v1.2.1 From 99e928f103182b58156edb107b55344eaafc6772 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 18:51:34 -0300 Subject: Add restriction to number of permitted priorities per project label --- app/models/project_label.rb | 9 +++++++++ spec/models/project_label_spec.rb | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 5b739bcdadf..f863d442c21 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -1,8 +1,11 @@ class ProjectLabel < Label + NUMBER_OF_PRIORITIES = 1 + belongs_to :project validates :project, presence: true + validate :permitted_numbers_of_priorities validate :title_must_not_exist_at_group_level delegate :group, to: :project, allow_nil: true @@ -20,4 +23,10 @@ class ProjectLabel < Label errors.add(:title, :label_already_exists_at_group_level, group: group.name) end end + + def permitted_numbers_of_priorities + if priorities && priorities.size >= NUMBER_OF_PRIORITIES + errors.add(:priorities, 'Number of permitted priorities exceeded') + end + end end diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index c861d4b73bb..cd4732fb737 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -48,7 +48,18 @@ describe ProjectLabel, models: true do project_label.valid? - expect(project_label .errors[:title]).to be_empty + expect(project_label.errors[:title]).to be_empty + end + end + + context 'when attempting to add more than one priority to the project label' do + it 'returns error' do + subject.priorities.build + subject.priorities.build + + subject.valid? + + expect(subject.errors[:priorities]).to include 'Number of permitted priorities exceeded' end end end -- cgit v1.2.1 From 3c2aaec1f2624ad4817e7ac52120985682afa448 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 20:06:26 -0300 Subject: Fix sorting by label priorities --- app/models/concerns/issuable.rb | 3 ++- app/models/concerns/sortable.rb | 6 +++-- app/models/label.rb | 11 ++++++++ app/models/todo.rb | 2 +- .../controllers/projects/labels_controller_spec.rb | 31 +++++++++++++--------- spec/factories/labels.rb | 10 +++++++ 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 76de927ceab..d726cb6b7aa 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -146,7 +146,8 @@ module Issuable def order_labels_priority(excluded_labels: []) condition_field = "#{table_name}.id" - highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql + project_field = "#{table_name}.project_id" + highest_priority = highest_label_priority(name, project_field, condition_field, excluded_labels: excluded_labels).to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). group(arel_table[:id]). diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 1ebecd86af9..83e551fd152 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -38,10 +38,12 @@ module Sortable private - def highest_label_priority(object_types, condition_field, excluded_labels: []) - query = Label.select(Label.arel_table[:priority].minimum). + def highest_label_priority(object_types, project_field, condition_field, excluded_labels: []) + query = Label.select(LabelPriority.arel_table[:priority].minimum). + left_join_priorities. joins(:label_links). where(label_links: { target_type: object_types }). + where("label_priorities.project_id = #{project_field}"). where("label_links.target_id = #{condition_field}"). reorder(nil) diff --git a/app/models/label.rb b/app/models/label.rb index ea11d9d7864..1d775a83f96 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -42,6 +42,17 @@ class Label < ActiveRecord::Base where.not(id: prioritized(project).select(:id)) end + def self.left_join_priorities + labels = Label.arel_table + priorities = LabelPriority.arel_table + + label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin). + on(labels[:id].eq(priorities[:label_id])). + join_sources + + joins(label_priorities) + end + alias_attribute :name, :title def self.reference_prefix diff --git a/app/models/todo.rb b/app/models/todo.rb index 6ae9956ade5..fd90a893d2e 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -52,7 +52,7 @@ class Todo < ActiveRecord::Base # 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 + highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.project_id", "todos.target_id").to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')). diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 29251f49810..622ab154493 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -15,21 +15,28 @@ describe Projects::LabelsController do let!(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') } let!(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') } let!(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') } - let!(:label_4) { create(:label, project: project, priority: nil, title: 'Label 4') } - let!(:label_5) { create(:label, project: project, priority: nil, title: 'Label 5') } + let!(:label_4) { create(:label, project: project, title: 'Label 4') } + let!(:label_5) { create(:label, project: project, title: 'Label 5') } - let!(:group_label_1) { create(:group_label, group: group, priority: 3, title: 'Group Label 1') } - let!(:group_label_2) { create(:group_label, group: group, priority: 1, title: 'Group Label 2') } - let!(:group_label_3) { create(:group_label, group: group, priority: nil, title: 'Group Label 3') } - let!(:group_label_4) { create(:group_label, group: group, priority: nil, title: 'Group Label 4') } + let!(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') } + let!(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') } + let!(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') } + let!(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') } + + before do + create(:label_priority, project: project, label: group_label_1, priority: 3) + create(:label_priority, project: project, label: group_label_2, priority: 1) + end context '@prioritized_labels' do before do list_labels end - it 'contains only prioritized labels' do - expect(assigns(:prioritized_labels)).to all(have_attributes(priority: a_value > 0)) + it 'does not include labels without priority' do + list_labels + + expect(assigns(:prioritized_labels)).not_to include(group_label_3, group_label_4, label_4, label_5) end it 'is sorted by priority, then label title' do @@ -38,16 +45,16 @@ describe Projects::LabelsController do end context '@labels' do - it 'contains only unprioritized labels' do + it 'is sorted by label title' do list_labels - expect(assigns(:labels)).to all(have_attributes(priority: nil)) + expect(assigns(:labels)).to eq [group_label_3, group_label_4, label_4, label_5] end - it 'is sorted by label title' do + it 'does not include labels with priority' do list_labels - expect(assigns(:labels)).to eq [group_label_3, group_label_4, label_4, label_5] + expect(assigns(:labels)).not_to include(group_label_2, label_1, label_3, group_label_1, label_2) end it 'does not include group labels when project does not belong to a group' do diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 5c789d72bac..3e8822faf97 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -3,6 +3,16 @@ FactoryGirl.define do sequence(:title) { |n| "label#{n}" } color "#990000" project + + transient do + priority nil + end + + after(:create) do |label, evaluator| + if evaluator.priority + label.priorities.create(project: label.project, priority: evaluator.priority) + end + end end factory :group_label, class: GroupLabel do -- cgit v1.2.1 From 86e0b5d643df21503281115774da550e06a4e878 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 20:51:41 -0300 Subject: Fix issue board related controllers to expose label priority per project --- app/controllers/projects/boards/issues_controller.rb | 4 ++-- app/controllers/projects/boards/lists_controller.rb | 5 ++--- app/models/issue.rb | 12 ++++++++++++ app/models/label.rb | 6 ++++++ app/models/list.rb | 11 +++++++++++ spec/fixtures/api/schemas/list.json | 2 +- 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 71eb56aed0b..a2b01ff43dc 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -72,10 +72,10 @@ module Projects def serialize_as_json(resource) resource.as_json( + labels: true, only: [:iid, :title, :confidential], include: { - assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, - labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } + assignee: { only: [:id, :name, :username], methods: [:avatar_url] } }) end end diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb index 76ae41319c4..67e3c9add81 100644 --- a/app/controllers/projects/boards/lists_controller.rb +++ b/app/controllers/projects/boards/lists_controller.rb @@ -76,9 +76,8 @@ module Projects resource.as_json( only: [:id, :list_type, :position], methods: [:title], - include: { - label: { only: [:id, :title, :description, :color, :priority] } - }) + label: true + ) end end end diff --git a/app/models/issue.rb b/app/models/issue.rb index abd58e0454a..f7ccce2924a 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -274,4 +274,16 @@ class Issue < ActiveRecord::Base def check_for_spam? project.public? end + + def as_json(options = {}) + super(options).tap do |json| + if options.has_key?(:labels) + json[:labels] = labels.as_json( + project: project, + only: [:id, :title, :description, :color], + methods: [:text_color] + ) + end + end + end end diff --git a/app/models/label.rb b/app/models/label.rb index 1d775a83f96..6fd45d251a8 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -133,6 +133,12 @@ class Label < ActiveRecord::Base end end + def as_json(options = {}) + super(options).tap do |json| + json[:priority] = priorities.find_by(project: options[:project]).try(:priority) if options.has_key?(:project) + end + end + private def cross_project_reference?(source_project, target_project) diff --git a/app/models/list.rb b/app/models/list.rb index eb87decdbc8..065d75bd1dc 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -26,6 +26,17 @@ class List < ActiveRecord::Base label? ? label.name : list_type.humanize end + def as_json(options = {}) + super(options).tap do |json| + if options.has_key?(:label) + json[:label] = label.as_json( + project: board.project, + only: [:id, :title, :description, :color] + ) + end + end + end + private def can_be_destroyed diff --git a/spec/fixtures/api/schemas/list.json b/spec/fixtures/api/schemas/list.json index f070fa3b254..8d94cf26ecb 100644 --- a/spec/fixtures/api/schemas/list.json +++ b/spec/fixtures/api/schemas/list.json @@ -13,7 +13,7 @@ "enum": ["backlog", "label", "done"] }, "label": { - "type": ["object"], + "type": ["object", "null"], "required": [ "id", "color", -- cgit v1.2.1 From 9cb123843474d60c452c02bf5701f11769661829 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Sat, 15 Oct 2016 12:23:16 -0300 Subject: Fix project issues labels feature spec --- features/steps/project/issues/labels.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index 2937d5d7ca8..f74a9b5df47 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps end step 'I remove label \'bug\'' do - page.within "#label_#{bug_label.id}" do + page.within "#project_label_#{bug_label.id}" do first(:link, 'Delete').click end end -- cgit v1.2.1 From 074c964913218e99c42f0d8b5855c4ad2ad93267 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Sat, 15 Oct 2016 12:46:20 -0300 Subject: Add label type to group and project labels lists --- app/assets/stylesheets/pages/labels.scss | 16 ++++++++++- app/views/groups/labels/index.html.haml | 2 +- app/views/shared/_label_row.html.haml | 2 ++ .../20161017125927_add_unique_index_to_labels.rb | 32 ++++++++++++++++++++++ db/schema.rb | 5 ++-- 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20161017125927_add_unique_index_to_labels.rb diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 9bac6d46355..2f7f7325877 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -66,7 +66,21 @@ text-overflow: ellipsis; vertical-align: middle; max-width: 100%; - } + } + } + + .label-type { + display: block; + margin-bottom: 10px; + margin-left: 50px; + + @media (min-width: $screen-sm-min) { + display: inline-block; + width: 100px; + margin-left: 10px; + margin-bottom: 0; + vertical-align: middle; + } } .label-description { diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 6c69e3465f4..70783a63409 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -2,7 +2,7 @@ .top-area.adjust .nav-text - Labels can be applied to issues and merge requests. + Labels can be applied to issues and merge requests. Group labels are available for any project within the group. .nav-controls - if can?(current_user, :admin_label, @group) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 813ce4f1405..76c3327fefc 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,6 +10,8 @@ = icon('star') %span.label-name = link_to_label(label, subject: @project, tooltip: false) + %span.label-type + = label.model_name.human.titleize - if label.description %span.label-description = markdown_field(label, :description) diff --git a/db/migrate/20161017125927_add_unique_index_to_labels.rb b/db/migrate/20161017125927_add_unique_index_to_labels.rb new file mode 100644 index 00000000000..16ae38612de --- /dev/null +++ b/db/migrate/20161017125927_add_unique_index_to_labels.rb @@ -0,0 +1,32 @@ +class AddUniqueIndexToLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'This migration removes duplicated labels.' + + disable_ddl_transaction! + + def up + select_all('SELECT title, COUNT(id) as cnt FROM labels GROUP BY project_id, title HAVING COUNT(id) > 1').each do |label| + label_title = quote_string(label['title']) + duplicated_ids = select_all("SELECT id FROM labels WHERE title = '#{label_title}' ORDER BY id ASC").map{ |label| label['id'] } + label_id = duplicated_ids.first + duplicated_ids.delete(label_id) + + execute("UPDATE label_links SET label_id = #{label_id} WHERE label_id IN(#{duplicated_ids.join(",")})") + execute("DELETE FROM labels WHERE id IN(#{duplicated_ids.join(",")})") + end + + remove_index :labels, column: :project_id if index_exists?(:labels, :project_id) + remove_index :labels, column: :title if index_exists?(:labels, :title) + + add_concurrent_index :labels, [:group_id, :project_id, :title], unique: true + end + + def down + remove_index :labels, column: [:group_id, :project_id, :title] if index_exists?(:labels, [:group_id, :project_id, :title], unique: true) + + add_concurrent_index :labels, :project_id + add_concurrent_index :labels, :title + end +end diff --git a/db/schema.rb b/db/schema.rb index e17fdf75f88..f9353290abf 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: 20161017095000) do +ActiveRecord::Schema.define(version: 20161017125927) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -543,9 +543,8 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.integer "group_id" end + add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree - add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree - add_index "labels", ["title"], name: "index_labels_on_title", using: :btree create_table "lfs_objects", force: :cascade do |t| t.string "oid", null: false -- cgit v1.2.1 From 530aae9080942646b130510e970d9d82c009d8e5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 16:34:22 -0200 Subject: Abstract LabelPriority away into methods on Label model --- app/controllers/projects/labels_controller.rb | 14 +++---- app/models/label.rb | 16 +++++++- spec/models/label_spec.rb | 58 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index f453b70fb5d..42fd09e9b7e 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -84,7 +84,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| label = @available_labels.find(params[:id]) - if label.priorities.where(project_id: project).delete_all + if label.unprioritize!(project) format.json { render json: label } else format.json { head :unprocessable_entity } @@ -94,14 +94,12 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do - label_ids = @available_labels.where(id: params[:label_ids]).pluck(:id) + available_labels_ids = @available_labels.where(id: params[:label_ids]).pluck(:id) + label_ids = params[:label_ids].select { |id| available_labels_ids.include?(id.to_i) } - params[:label_ids].each_with_index do |label_id, index| - next unless label_ids.include?(label_id.to_i) - - label_priority = LabelPriority.find_or_initialize_by(project_id: @project.id, label_id: label_id) - label_priority.priority = index - label_priority.save! + label_ids.each_with_index do |label_id, index| + label = @available_labels.find(label_id) + label.prioritize!(project, index) end end diff --git a/app/models/label.rb b/app/models/label.rb index 6fd45d251a8..ae07e8f60e1 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -97,6 +97,20 @@ class Label < ActiveRecord::Base merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened') end + def prioritize!(project, value) + label_priority = priorities.find_or_initialize_by(project_id: project.id) + label_priority.priority = value + label_priority.save! + end + + def unprioritize!(project) + priorities.where(project: project).delete_all + end + + def priority(project) + priorities.find_by(project: project).try(:priority) + end + def template? template end @@ -135,7 +149,7 @@ class Label < ActiveRecord::Base def as_json(options = {}) super(options).tap do |json| - json[:priority] = priorities.find_by(project: options[:project]).try(:priority) if options.has_key?(:project) + json[:priority] = priority(options[:project]) if options.has_key?(:project) end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 4af0fb6afa9..0c163659a71 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -46,4 +46,62 @@ describe Label, models: true do expect(label.title).to eq('foo & bar?') end end + + describe 'priorization' do + subject(:label) { create(:label) } + + let(:project) { label.project } + + describe '#prioritize!' do + context 'when label is not prioritized' do + it 'creates a label priority' do + expect { label.prioritize!(project, 1) }.to change(label.priorities, :count).by(1) + end + + it 'sets label priority' do + label.prioritize!(project, 1) + + expect(label.priorities.first.priority).to eq 1 + end + end + + context 'when label is prioritized' do + let!(:priority) { create(:label_priority, project: project, label: label, priority: 0) } + + it 'does not create a label priority' do + expect { label.prioritize!(project, 1) }.not_to change(label.priorities, :count) + end + + it 'updates label priority' do + label.prioritize!(project, 1) + + expect(priority.reload.priority).to eq 1 + end + end + end + + describe '#unprioritize!' do + it 'removes label priority' do + create(:label_priority, project: project, label: label, priority: 0) + + expect { label.unprioritize!(project) }.to change(label.priorities, :count).by(-1) + end + end + + describe '#priority' do + context 'when label is not prioritized' do + it 'returns nil' do + expect(label.priority(project)).to be_nil + end + end + + context 'when label is prioritized' do + it 'returns label priority' do + create(:label_priority, project: project, label: label, priority: 1) + + expect(label.priority(project)).to eq 1 + end + end + end + end end -- cgit v1.2.1 From 1e5ea6e7e05cd45fc56d0341ac8b5c32e57779b5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 16:55:46 -0200 Subject: Return only labels that user have access on IssuableFinder#labels --- app/finders/issuable_finder.rb | 13 +++++-------- app/finders/labels_finder.rb | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 41ea8f801c1..e27986ef95b 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -124,15 +124,12 @@ class IssuableFinder def labels return @labels if defined?(@labels) - if labels? && !filter_by_no_label? - @labels = Label.where(title: label_names) - - if projects - @labels = LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute + @labels = + if labels? && !filter_by_no_label? + LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute + else + Label.none end - else - @labels = Label.none - end end def assignee? diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 5ee2e1ee6e8..48fd1280ed2 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -45,7 +45,7 @@ class LabelsFinder < UnionFinder params[:project_id].presence end - def project_ids + def projects_ids params[:project_ids].presence end @@ -70,7 +70,7 @@ class LabelsFinder < UnionFinder @projects = available_projects @projects = @projects.in_namespace(group_id) if group_id - @projects = @projects.where(id: project_ids) if project_ids + @projects = @projects.where(id: projects_ids) if projects_ids @projects = @projects.reorder(nil) @projects -- cgit v1.2.1 From d3b76e832f0afc38e2d0ffdff540c708a74ac26c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 17:30:49 -0200 Subject: Reuse LabelsFinder on Banzai::Filter::LabelReferenceFilter --- app/finders/labels_finder.rb | 18 ++++++++++++++---- lib/banzai/filter/label_reference_filter.rb | 8 +------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 48fd1280ed2..65db4184ecf 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -4,7 +4,9 @@ class LabelsFinder < UnionFinder @params = params end - def execute + def execute(authorized_only: true) + @authorized_only = authorized_only + items = find_union(label_ids, Label) items = with_title(items) sort(items) @@ -12,7 +14,7 @@ class LabelsFinder < UnionFinder private - attr_reader :current_user, :params + attr_reader :current_user, :params, :authorized_only def label_ids label_ids = [] @@ -57,7 +59,7 @@ class LabelsFinder < UnionFinder return @project if defined?(@project) if project_id - @project = available_projects.find(project_id) rescue nil + @project = find_project else @project = nil end @@ -65,10 +67,18 @@ class LabelsFinder < UnionFinder @project end + def find_project + if authorized_only + available_projects.find_by(id: project_id) + else + Project.find_by(id: project_id) + end + end + def projects return @projects if defined?(@projects) - @projects = available_projects + @projects = authorized_only ? available_projects : Project.all @projects = @projects.in_namespace(group_id) if group_id @projects = @projects.where(id: projects_ids) if projects_ids @projects = @projects.reorder(nil) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 21085ae6d49..c24831e68ee 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -39,13 +39,7 @@ module Banzai end def find_labels(project) - label_ids = [] - label_ids << project.group.labels.select(:id) if project.group.present? - label_ids << project.labels.select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - Label.where("labels.id IN (#{union.to_sql})") + LabelsFinder.new(nil, project_id: project.id).execute(authorized_only: false) end # Parameters to pass to `Label.find_by` based on the given arguments -- cgit v1.2.1 From 8379fbcd47930320bf4dd6a3ac41c6efd427a91a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 17:48:46 -0200 Subject: Add subject to group and projects labels which return group/project --- app/helpers/labels_helper.rb | 10 ++-------- app/models/group_label.rb | 2 ++ app/models/project_label.rb | 2 ++ spec/models/group_label_spec.rb | 8 ++++++++ spec/models/project_label_spec.rb | 8 ++++++++ 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index af8741f5e06..a1d7713b45f 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -14,7 +14,7 @@ module LabelsHelper # # Examples: # - # # Allow the generated link to use the label's own project + # # Allow the generated link to use the label's own subject # link_to_label(label) # # # Force the generated link to use a provided group @@ -31,13 +31,7 @@ module LabelsHelper # # Returns a String def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block) - subject ||= - case label - when GroupLabel then label.group - when ProjectLabel then label.project - end - - link = label_filter_path(subject, label, type: type) + link = label_filter_path(subject || label.subject, label, type: type) if block_given? link_to link, class: css_class, &block diff --git a/app/models/group_label.rb b/app/models/group_label.rb index a1d8d087726..a698b532d19 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -3,6 +3,8 @@ class GroupLabel < Label validates :group, presence: true + alias_attribute :subject, :group + def to_reference(source_project = nil, target_project = nil, format: :id) super(source_project, target_project, format: format) end diff --git a/app/models/project_label.rb b/app/models/project_label.rb index f863d442c21..0a79521ed09 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -10,6 +10,8 @@ class ProjectLabel < Label delegate :group, to: :project, allow_nil: true + alias_attribute :subject, :project + def to_reference(target_project = nil, format: :id) super(project, target_project, format: format) end diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index 92b07a3cd44..85eb889225b 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -9,6 +9,14 @@ describe GroupLabel, models: true do it { is_expected.to validate_presence_of(:group) } end + describe '#subject' do + it 'aliases group to subject' do + subject = described_class.new(group: build(:group)) + + expect(subject.subject).to be(subject.group) + end + end + describe '#to_reference' do let(:label) { create(:group_label) } diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index cd4732fb737..18c9d449ee5 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -64,6 +64,14 @@ describe ProjectLabel, models: true do end end + describe '#subject' do + it 'aliases project to subject' do + subject = described_class.new(project: build(:empty_project)) + + expect(subject.subject).to be(subject.project) + end + end + describe '#to_reference' do let(:label) { create(:label) } -- cgit v1.2.1 From 928acba4c0ae31626dac621f0f240f18cbad548a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 18:03:06 -0200 Subject: Use keyword arguments on Sortable#highest_label_priority --- app/models/concerns/issuable.rb | 11 ++++++++--- app/models/concerns/sortable.rb | 8 ++++---- app/models/todo.rb | 8 +++++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index d726cb6b7aa..245a865bcba 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -145,9 +145,14 @@ module Issuable end def order_labels_priority(excluded_labels: []) - condition_field = "#{table_name}.id" - project_field = "#{table_name}.project_id" - highest_priority = highest_label_priority(name, project_field, condition_field, excluded_labels: excluded_labels).to_sql + params = { + target_type: name, + target_column: "#{table_name}.id", + project_column: "#{table_name}.project_id", + excluded_labels: excluded_labels + } + + highest_priority = highest_label_priority(params).to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). group(arel_table[:id]). diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 83e551fd152..12b23f00769 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -38,13 +38,13 @@ module Sortable private - def highest_label_priority(object_types, project_field, condition_field, excluded_labels: []) + def highest_label_priority(target_type:, target_column:, project_column:, excluded_labels: []) query = Label.select(LabelPriority.arel_table[:priority].minimum). left_join_priorities. joins(:label_links). - where(label_links: { target_type: object_types }). - where("label_priorities.project_id = #{project_field}"). - where("label_links.target_id = #{condition_field}"). + where("label_priorities.project_id = #{project_column}"). + where(label_links: { target_type: target_type }). + where("label_links.target_id = #{target_column}"). reorder(nil) query.where.not(title: excluded_labels) if excluded_labels.present? diff --git a/app/models/todo.rb b/app/models/todo.rb index fd90a893d2e..11c072dd000 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -52,7 +52,13 @@ class Todo < ActiveRecord::Base # 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.project_id", "todos.target_id").to_sql + params = { + target_type: ['Issue', 'MergeRequest'], + target_column: "todos.target_id", + project_column: "todos.project_id" + } + + highest_priority = highest_label_priority(params).to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')). -- cgit v1.2.1 From 301264beada6aaf7dd0e4244c3f41131f9b4f359 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 18:50:49 -0200 Subject: Fix sorting merge requests by priority --- app/models/concerns/issuable.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 245a865bcba..9a9b562af02 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -145,10 +145,16 @@ module Issuable end def order_labels_priority(excluded_labels: []) + project_column = + case table_name + when Issue.table_name then "#{table_name}.project_id" + when MergeRequest.table_name then "#{table_name}.target_project_id" + end + params = { target_type: name, target_column: "#{table_name}.id", - project_column: "#{table_name}.project_id", + project_column: project_column, excluded_labels: excluded_labels } -- cgit v1.2.1 From 49ec98d1b2ca6f57f3f9434a0be0018fa5a53681 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 22:39:21 -0200 Subject: Recreates the label priorities when moving project to another group --- app/services/labels/transfer_service.rb | 3 ++- spec/services/labels/transfer_service_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 04312c114ef..559d2860d97 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -14,7 +14,7 @@ module Labels return unless group.present? Label.transaction do - labels_to_transfer = Label.where(id: label_links.select(:label_id).uniq) + labels_to_transfer = Label.where(id: label_links.select(:label_id)) labels_to_transfer.find_each do |label| new_label_id = find_or_create_label!(label) @@ -22,6 +22,7 @@ module Labels next if new_label_id == label.id LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) + LabelPriority.where(project_id: project.id, label_id: label.id).update_all(label_id: new_label_id) end end end diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index a72a05f6c99..cb09c16698a 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -26,6 +26,16 @@ describe Labels::TransferService, services: true do expect { service.execute }.to change(project.labels, :count).by(2) end + it 'recreates label priorities related to the missing group labels' do + create(:label_priority, project: project, label: group_label_1, priority: 1) + + service.execute + + new_project_label = project.labels.find_by(title: group_label_1.title) + expect(new_project_label.id).not_to eq group_label_1.id + expect(new_project_label.priorities).not_to be_empty + end + it 'does not recreate missing group labels that are not applied to issues or merge requests' do service.execute -- cgit v1.2.1 From 6c189dcc8e76d5ddb348832500b003bf0d1b49a6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 00:11:33 -0200 Subject: Add service to create project labels --- app/services/labels/create_service.rb | 33 +++++++++++++++++++ spec/services/labels/create_service_spec.rb | 51 +++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 app/services/labels/create_service.rb create mode 100644 spec/services/labels/create_service_spec.rb diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb new file mode 100644 index 00000000000..bb475ce741d --- /dev/null +++ b/app/services/labels/create_service.rb @@ -0,0 +1,33 @@ +module Labels + class CreateService + def initialize(current_user, project, params = {}) + @current_user = current_user + @group = project.group + @project = project + @params = params.dup + end + + def execute + find_or_create_label + end + + private + + attr_reader :current_user, :group, :project, :params + + def available_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute + end + + def find_or_create_label + new_label = available_labels.find_by(title: title) + new_label ||= project.labels.create(params) + + new_label + end + + def title + params[:title] || params[:name] + end + end +end diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb new file mode 100644 index 00000000000..1e4bc294b46 --- /dev/null +++ b/spec/services/labels/create_service_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Labels::CreateService, services: true do + describe '#execute' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + + let(:params) do + { + title: 'Security', + description: 'Security related stuff.', + color: '#FF0000' + } + end + + subject(:service) { described_class.new(user, project, params) } + + before do + project.team << [user, :developer] + end + + context 'when label does not exist at group level' do + it 'creates a new label at project level' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at group level' do + it 'returns the group label' do + group_label = create(:group_label, group: group, title: 'Security') + + expect(service.execute).to eq group_label + end + end + + context 'when label does not exist at group level' do + it 'creates a new label at project leve' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at project level' do + it 'returns the project label' do + project_label = create(:label, project: project, title: 'Security') + + expect(service.execute).to eq project_label + end + end + end +end -- cgit v1.2.1 From d009d38ed6ab5459f596194ce808c304e6379161 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 00:27:10 -0200 Subject: User Labes::CreateService to create labels --- app/models/project.rb | 3 ++- app/services/boards/lists/generate_service.rb | 3 +-- app/services/issuable_base_service.rb | 4 ++-- app/services/labels/transfer_service.rb | 8 ++------ lib/gitlab/fogbugz_import/importer.rb | 4 ++-- lib/gitlab/github_import/label_formatter.rb | 7 ++++--- lib/gitlab/google_code_import/importer.rb | 4 ++-- lib/gitlab/issues_labels.rb | 4 ++-- 8 files changed, 17 insertions(+), 20 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 7ab624eafdf..bc15ca3fc2e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -733,7 +733,8 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| - self.labels.create!(label.attributes.symbolize_keys.except(:id, :template)) + params = label.attributes.except('id', 'template', 'created_at', 'updated_at') + Labels::CreateService.new(owner, self, params).execute end end diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index d8048f1c67e..1d3c7f2071b 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -19,8 +19,7 @@ module Boards end def find_or_create_label(params) - project.labels.create_with(color: params[:color]) - .find_or_create_by(name: params[:name]) + ::Labels::CreateService.new(current_user, project, params).execute end def label_params diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index deadf1fe283..4554963370f 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -88,8 +88,8 @@ class IssuableBaseService < BaseService return unless labels params[:label_ids] = labels.split(',').map do |label_name| - label = available_labels.find_by(title: label_name) - label ||= project.labels.create(title: label_name.strip, color: Label::DEFAULT_COLOR) + service = Labels::CreateService.new(current_user, project, title: label_name.strip) + label = service.execute label.id end diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 559d2860d97..65b4bdbaff9 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -41,13 +41,9 @@ module Labels LabelLink.where("label_links.id IN (#{union.to_sql})") end - def labels - @labels ||= LabelsFinder.new(current_user, project_id: project.id).execute - end - def find_or_create_label!(label) - new_label = labels.find_by(title: label.title) - new_label ||= project.labels.create!(label.attributes.slice("title", "description", "color")) + params = label.attributes.slice('title', 'description', 'color') + new_label = CreateService.new(current_user, project, params).execute new_label.id end diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 9e926e2681a..f154ee689cf 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -74,8 +74,8 @@ module Gitlab end def create_label(name) - color = nice_label_color(name) - Label.create!(project_id: project.id, title: name, color: color) + params = { title: name, color: nice_label_color(name) } + ::Labels::CreateService.new(project.owner, project, params).execute end def user_info(person_id) diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb index 2cad7fca88e..3101116a614 100644 --- a/lib/gitlab/github_import/label_formatter.rb +++ b/lib/gitlab/github_import/label_formatter.rb @@ -14,9 +14,10 @@ module Gitlab end def create! - project.labels.find_or_create_by!(title: title) do |label| - label.color = color - end + params = attributes.except(:project) + service = ::Labels::CreateService.new(project.owner, project, params) + + service.execute end private diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 79a0eaba1e8..904a228aeef 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -233,8 +233,8 @@ module Gitlab end def create_label(name) - color = nice_label_color(name) - project.labels.create!(name: name, color: color) + params = { name: name, color: nice_label_color(name) } + ::Labels::CreateService.new(project.owner, project, params).execute end def format_content(raw_content) diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 1bec6088292..6788eca7146 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -18,8 +18,8 @@ module Gitlab { title: "enhancement", color: green } ] - labels.each do |label| - project.labels.create(label) + labels.each do |params| + ::Labels::CreateService.new(project.owner, project).execute(params) end end end -- cgit v1.2.1 From 771d3fc3cbaa2b473c87d23fc1c067fb9bad6206 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 00:56:05 -0200 Subject: Split migration to create label priorities --- .../20161014173530_create_label_priorities.rb | 32 +------------------ .../20161018024215_migrate_labels_priority.rb | 36 ++++++++++++++++++++++ .../20161018024550_remove_priority_from_labels.rb | 17 ++++++++++ db/schema.rb | 2 +- 4 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 db/migrate/20161018024215_migrate_labels_priority.rb create mode 100644 db/migrate/20161018024550_remove_priority_from_labels.rb diff --git a/db/migrate/20161014173530_create_label_priorities.rb b/db/migrate/20161014173530_create_label_priorities.rb index f9d94ebdc70..2c22841c28a 100644 --- a/db/migrate/20161014173530_create_label_priorities.rb +++ b/db/migrate/20161014173530_create_label_priorities.rb @@ -2,7 +2,7 @@ class CreateLabelPriorities < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = true - DOWNTIME_REASON = 'Prioritezed labels will not work as expected until this migration is complete.' + DOWNTIME_REASON = 'This migration adds foreign keys' disable_ddl_transaction! @@ -15,41 +15,11 @@ class CreateLabelPriorities < ActiveRecord::Migration t.timestamps null: false end - execute <<-EOF.strip_heredoc - INSERT INTO label_priorities (project_id, label_id, priority, created_at, updated_at) - SELECT labels.project_id, labels.id, labels.priority, NOW(), NOW() - FROM labels - WHERE labels.project_id IS NOT NULL - AND labels.priority IS NOT NULL; - EOF - add_concurrent_index :label_priorities, [:project_id, :label_id], unique: true add_concurrent_index :label_priorities, :priority - - remove_column :labels, :priority end def down - add_column :labels, :priority, :integer - - if Gitlab::Database.mysql? - execute <<-EOF.strip_heredoc - UPDATE labels - INNER JOIN label_priorities ON labels.id = label_priorities.label_id AND labels.project_id = label_priorities.project_id - SET labels.priority = label_priorities.priority; - EOF - else - execute <<-EOF.strip_heredoc - UPDATE labels - SET priority = label_priorities.priority - FROM label_priorities - WHERE labels.id = label_priorities.label_id - AND labels.project_id = label_priorities.project_id; - EOF - end - - add_concurrent_index :labels, :priority - drop_table :label_priorities end end diff --git a/db/migrate/20161018024215_migrate_labels_priority.rb b/db/migrate/20161018024215_migrate_labels_priority.rb new file mode 100644 index 00000000000..22bec2382f4 --- /dev/null +++ b/db/migrate/20161018024215_migrate_labels_priority.rb @@ -0,0 +1,36 @@ +class MigrateLabelsPriority < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'Prioritized labels will not work as expected until this migration is complete.' + + disable_ddl_transaction! + + def up + execute <<-EOF.strip_heredoc + INSERT INTO label_priorities (project_id, label_id, priority, created_at, updated_at) + SELECT labels.project_id, labels.id, labels.priority, NOW(), NOW() + FROM labels + WHERE labels.project_id IS NOT NULL + AND labels.priority IS NOT NULL; + EOF + end + + def down + if Gitlab::Database.mysql? + execute <<-EOF.strip_heredoc + UPDATE labels + INNER JOIN label_priorities ON labels.id = label_priorities.label_id AND labels.project_id = label_priorities.project_id + SET labels.priority = label_priorities.priority; + EOF + else + execute <<-EOF.strip_heredoc + UPDATE labels + SET priority = label_priorities.priority + FROM label_priorities + WHERE labels.id = label_priorities.label_id + AND labels.project_id = label_priorities.project_id; + EOF + end + end +end diff --git a/db/migrate/20161018024550_remove_priority_from_labels.rb b/db/migrate/20161018024550_remove_priority_from_labels.rb new file mode 100644 index 00000000000..b7416cca664 --- /dev/null +++ b/db/migrate/20161018024550_remove_priority_from_labels.rb @@ -0,0 +1,17 @@ +class RemovePriorityFromLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'This migration removes an existing column' + + disable_ddl_transaction! + + def up + remove_column :labels, :priority, :integer, index: true + end + + def down + add_column :labels, :priority, :integer + add_concurrent_index :labels, :priority + end +end diff --git a/db/schema.rb b/db/schema.rb index f9353290abf..65f55aa109b 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: 20161017125927) do +ActiveRecord::Schema.define(version: 20161018024550) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 007267ef8b927646bab0932b5704ff0f3720253f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 01:00:45 -0200 Subject: Use `includes(:priorities)` on Projects::LabelsController --- app/controllers/projects/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 42fd09e9b7e..4f855134368 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -126,7 +126,7 @@ class Projects::LabelsController < Projects::ApplicationController alias_method :subscribable_resource, :label def find_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities) end def authorize_admin_labels! -- cgit v1.2.1 From 4c9241075dc1b2f1cda5648cf9ad1f553db3d03b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 01:32:00 -0200 Subject: Warn user deleting a group label affect all projects within the group --- app/helpers/labels_helper.rb | 7 +++++++ app/views/shared/_label.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index a1d7713b45f..d7cfd24c918 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -155,6 +155,13 @@ module LabelsHelper label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' end + def label_deletion_confirm_text(label) + case label + when GroupLabel then 'Remove this label? This will affect all projects within the group. Are you sure?' + when ProjectLabel then 'Remove this label? Are you sure?' + end + end + # Required for Banzai::Filter::LabelReferenceFilter module_function :render_colored_label, :render_colored_cross_project_label, :text_color_for_bg, :escape_once diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index ba8a3efccda..5cdd18d24f0 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -44,7 +44,7 @@ = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') - = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do + = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do %span.sr-only Delete = icon('trash-o') -- cgit v1.2.1 From f99744d00de10094d6e483776313c52d86437a9d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 04:00:33 -0200 Subject: Use join instead of subquery on Label.unprioritized scope --- app/models/label.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/label.rb b/app/models/label.rb index ae07e8f60e1..149fd98ecb3 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -39,7 +39,14 @@ class Label < ActiveRecord::Base end def self.unprioritized(project) - where.not(id: prioritized(project).select(:id)) + labels = Label.arel_table + priorities = LabelPriority.arel_table + + label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin). + on(labels[:id].eq(priorities[:label_id]).and(priorities[:project_id].eq(project.id))). + join_sources + + joins(label_priorities).where(priorities[:priority].eq(nil)) end def self.left_join_priorities -- cgit v1.2.1 From a9938e227bfb0ee1908beb9238bb95fece72805e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 04:30:43 -0200 Subject: Add support to group labels to SlashCommands::InterpretService --- app/finders/labels_finder.rb | 2 +- app/services/slash_commands/interpret_service.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 65db4184ecf..a5802171b6c 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -52,7 +52,7 @@ class LabelsFinder < UnionFinder end def title - params[:title].presence + params[:title].presence || params[:name].presence end def project diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index e4ae3dec8aa..5a81194a5f4 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -116,8 +116,10 @@ module SlashCommands desc 'Add label(s)' params '~label1 ~"label 2"' condition do + available_labels = LabelsFinder.new(current_user, project_id: project.id).execute + current_user.can?(:"admin_#{issuable.to_ability_name}", project) && - project.labels.any? + available_labels.any? end command :label do |labels_param| label_ids = find_label_ids(labels_param) @@ -248,7 +250,7 @@ module SlashCommands def find_label_ids(labels_param) label_ids_by_reference = extract_references(labels_param, :label).map(&:id) - labels_ids_by_name = @project.labels.where(name: labels_param.split).select(:id) + labels_ids_by_name = LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute.select(:id) label_ids_by_reference | labels_ids_by_name end -- cgit v1.2.1 From 1a41a89cb383d286e21a125e8a643eb0fbb2442b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 04:18:54 -0200 Subject: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c866696889e..dd37b0a6a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add group level labels. (!6425) - 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 -- cgit v1.2.1 From f0ad0ceff5236f3ee5babee47bfec217a54c3b07 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 06:26:16 -0200 Subject: Fix GitHub importer spec --- lib/gitlab/github_import/label_formatter.rb | 5 ++++- spec/lib/gitlab/github_import/importer_spec.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb index 3101116a614..8ed1574c4fc 100644 --- a/lib/gitlab/github_import/label_formatter.rb +++ b/lib/gitlab/github_import/label_formatter.rb @@ -16,8 +16,11 @@ module Gitlab def create! params = attributes.except(:project) service = ::Labels::CreateService.new(project.owner, project, params) + label = service.execute - service.execute + raise ActiveRecord::RecordInvalid.new(label) unless label.persisted? + + label end private diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index 8854c8431b5..1af553f8f03 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -157,7 +157,7 @@ describe Gitlab::GithubImport::Importer, lib: true do { 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" }, { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" } - ] + ] } described_class.new(project).execute -- cgit v1.2.1 From 2f7260b460f2a893241039115a201c2522fb47ca Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 09:46:11 -0200 Subject: Fix max number of permitted priorities per project label --- app/models/project_label.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 0a79521ed09..33c2b617715 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -1,5 +1,5 @@ class ProjectLabel < Label - NUMBER_OF_PRIORITIES = 1 + MAX_NUMBER_OF_PRIORITIES = 1 belongs_to :project @@ -27,7 +27,7 @@ class ProjectLabel < Label end def permitted_numbers_of_priorities - if priorities && priorities.size >= NUMBER_OF_PRIORITIES + if priorities && priorities.size > MAX_NUMBER_OF_PRIORITIES errors.add(:priorities, 'Number of permitted priorities exceeded') end end -- cgit v1.2.1 From 891e5f4851c2067daba12a1750651170a1583481 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 18 Oct 2016 19:31:10 +0200 Subject: Update specs to cope with new label types and priorities Fixed all related specs and also changed the logic to handle edge cases. This includes exporting and exporting of group labels, which will get associated with the new group (if any) or they will become normal project labels otherwise. Found other issues to do with not being able to import all labels at once in the beginning of the JSON - code was much simpler when we import all labels and milestones associated to a project first, then the associations will find the already created labels instead of creating them from the associations themselves. --- app/models/project.rb | 4 +++ lib/gitlab/import_export/attribute_cleaner.rb | 2 +- lib/gitlab/import_export/import_export.yml | 9 ++++-- lib/gitlab/import_export/json_hash_builder.rb | 6 ++++ lib/gitlab/import_export/relation_factory.rb | 22 +++++++++----- .../import_export/test_project_export.tar.gz | Bin 680856 -> 681774 bytes spec/lib/gitlab/import_export/all_models.yml | 3 ++ spec/lib/gitlab/import_export/project.json | 33 ++++++++++++++++----- .../import_export/project_tree_restorer_spec.rb | 6 ++++ .../import_export/project_tree_saver_spec.rb | 9 +++++- .../gitlab/import_export/safe_model_attributes.yml | 9 +++++- 11 files changed, 82 insertions(+), 21 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index bc15ca3fc2e..94105a8ea79 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -738,6 +738,10 @@ class Project < ActiveRecord::Base end end + def all_labels + Label.find_by_project_id(self.id) + end + def find_service(list, name) list.find { |service| service.to_param == name } end diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb index b9e4042220a..f755a404693 100644 --- a/lib/gitlab/import_export/attribute_cleaner.rb +++ b/lib/gitlab/import_export/attribute_cleaner.rb @@ -1,7 +1,7 @@ module Gitlab module ImportExport class AttributeCleaner - ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id'] def self.clean!(relation_hash:) relation_hash.reject! do |key, _value| diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 8882f146632..e6ecd118609 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -1,6 +1,7 @@ # Model relationships to be included in the project import/export project_tree: - - :labels + - labels: + :priorities - milestones: - :events - issues: @@ -9,7 +10,8 @@ project_tree: - :author - :events - label_links: - - :label + - label: + :priorities - milestone: - :events - snippets: @@ -26,7 +28,8 @@ project_tree: - :merge_request_diff - :events - label_links: - - :label + - label: + :priorities - milestone: - :events - pipelines: diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb index 0cc10f40087..48c09dafcb6 100644 --- a/lib/gitlab/import_export/json_hash_builder.rb +++ b/lib/gitlab/import_export/json_hash_builder.rb @@ -65,11 +65,17 @@ module Gitlab # +value+ existing model to be included in the hash # +parsed_hash+ the original hash def parse_hash(value) + return nil if already_contains_methods?(value) + @attributes_finder.parse(value) do |hash| { include: hash_or_merge(value, hash) } end end + def already_contains_methods?(value) + value.is_a?(Hash) && value.values.detect { |val| val[:methods]} + end + # Adds new model configuration to an existing hash with key +current_key+ # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+ # diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 8bc4ab85c18..dc630e76411 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -11,6 +11,7 @@ module Gitlab merge_access_levels: 'ProtectedBranch::MergeAccessLevel', push_access_levels: 'ProtectedBranch::PushAccessLevel', labels: :project_labels, + priorities: :label_priorities, label: :project_label }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -23,8 +24,6 @@ module Gitlab EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels project_label group_label].freeze - FINDER_ATTRIBUTES = %w[title project_id].freeze - def self.create(*args) new(*args).create end @@ -134,6 +133,7 @@ module Gitlab def handle_group_label # If there's no group, move the label to a project label if @relation_hash['group_id'] + @relation_hash['project_id'] = nil @relation_name = :group_label else @relation_hash['type'] = 'ProjectLabel' @@ -188,11 +188,9 @@ module Gitlab # Otherwise always create the record, skipping the extra SELECT clause. @existing_or_new_object ||= begin if EXISTING_OBJECT_CHECK.include?(@relation_name) - events = parsed_relation_hash.delete('events') + attribute_hash = attribute_hash_for(['events', 'priorities']) - unless events.blank? - existing_object.assign_attributes(events: events) - end + existing_object.assign_attributes(attribute_hash) if attribute_hash.any? existing_object else @@ -201,14 +199,22 @@ module Gitlab end end + def attribute_hash_for(attributes) + attributes.inject({}) do |hash, value| + hash[value] = parsed_relation_hash.delete(value) if parsed_relation_hash[value] + hash + end + end + def existing_object @existing_object ||= begin - finder_hash = parsed_relation_hash.slice(*FINDER_ATTRIBUTES) + finder_attributes = @relation_name == :group_label ? %w[title group_id] : %w[title project_id] + finder_hash = parsed_relation_hash.slice(*finder_attributes) existing_object = relation_class.find_or_create_by(finder_hash) # Done in two steps, as MySQL behaves differently than PostgreSQL using # the +find_or_create_by+ method and does not return the ID the second time. - existing_object.update(parsed_relation_hash) + existing_object.update!(parsed_relation_hash) existing_object 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 8f683cf89aa..bfe59bdb90e 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/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8fcbf12eab8..02b11bd999a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -38,6 +38,7 @@ label: - label_links - issues - merge_requests +- priorities milestone: - project - issues @@ -186,3 +187,5 @@ project: award_emoji: - awardable - user +priorities: +- label \ No newline at end of file diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index bf9dc279f7d..ed9df468ced 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2,6 +2,21 @@ "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", "visibility_level": 10, "archived": false, + "labels": [ + { + "id": 2, + "title": "test2", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "type": "ProjectLabel", + "priorities": [ + ] + } + ], "issues": [ { "id": 40, @@ -64,7 +79,6 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null, "type": "ProjectLabel" } }, @@ -84,9 +98,18 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null, "project_id": null, - "type": "GroupLabel" + "type": "GroupLabel", + "priorities": [ + { + "id": 1, + "project_id": 5, + "label_id": 1, + "priority": 1, + "created_at": "2016-10-18T09:35:43.338Z", + "updated_at": "2016-10-18T09:35:43.338Z" + } + ] } } ], @@ -558,7 +581,6 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null, "type": "ProjectLabel" } } @@ -2249,9 +2271,6 @@ } ] } - ], - "labels": [ - ], "milestones": [ { 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 6312b990a66..069ea960321 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -134,6 +134,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(GroupLabel.count).to eq(1) end + + it 'has label priorities' do + restored_project_json + + expect(GroupLabel.first.priorities).not_to be_empty + end end it 'has a project feature' do 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 9a8ba61559b..c8bba553558 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -114,7 +114,13 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do it 'has project and group labels' do label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type']} - expect(label_types).to match(['ProjectLabel', 'GroupLabel']) + expect(label_types).to match_array(['ProjectLabel', 'GroupLabel']) + end + + it 'has priorities associated to labels' do + priorities = saved_project_json['issues'].first['label_links'].map { |link| link['label']['priorities']} + + expect(priorities.flatten).not_to be_empty end it 'saves the correct service type' do @@ -154,6 +160,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do group_label = create(:group_label, group: group) create(:label_link, label: project_label, target: issue) create(:label_link, label: group_label, target: issue) + create(:label_priority, label: group_label, priority: 1) milestone = create(:milestone, project: project) merge_request = create(:merge_request, source_project: project, milestone: milestone) commit_status = create(:commit_status, project: project) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 26049914bac..feee0f025d8 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -60,7 +60,7 @@ LabelLink: - target_type - created_at - updated_at -Label: +ProjectLabel: - id - title - color @@ -331,3 +331,10 @@ AwardEmoji: - awardable_type - created_at - updated_at +LabelPriority: +- id +- project_id +- label_id +- priority +- created_at +- updated_at \ No newline at end of file -- cgit v1.2.1 From aa78148901cd3877936bc2afcea9c329077bf951 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 17:00:43 -0200 Subject: Remove unused method Project#all_labels --- app/models/project.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 94105a8ea79..bc15ca3fc2e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -738,10 +738,6 @@ class Project < ActiveRecord::Base end end - def all_labels - Label.find_by_project_id(self.id) - end - def find_service(list, name) list.find { |service| service.to_param == name } end -- cgit v1.2.1 From 355389d065216739a2b8e8150a1a569c410f4ff6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:31:08 -0200 Subject: Disable subscribing to group-level labels --- app/assets/stylesheets/pages/labels.scss | 7 +++++++ app/controllers/groups/labels_controller.rb | 5 +---- app/helpers/labels_helper.rb | 21 ++++++++++++++------- app/views/shared/_label.html.haml | 8 ++++---- config/routes/group.rb | 6 +----- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 2f7f7325877..397f89f501a 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -223,6 +223,13 @@ } .label-subscribe-button { + .label-subscribe-button-icon { + &[disabled] { + opacity: 0.5; + pointer-events: none; + } + } + .label-subscribe-button-loading { display: none; } diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 0a3dee5ce39..29528b2cfaa 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -1,7 +1,5 @@ class Groups::LabelsController < Groups::ApplicationController - include ToggleSubscriptionAction - - before_action :label, only: [:edit, :update, :destroy, :toggle_subscription] + before_action :label, only: [:edit, :update, :destroy] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] before_action :save_previous_label_path, only: [:edit] @@ -71,7 +69,6 @@ class Groups::LabelsController < Groups::ApplicationController def label @label ||= @group.labels.find(params[:id]) end - alias_method :subscribable_resource, :label def label_params params.require(:label).permit(:title, :description, :color) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index d7cfd24c918..221a84b042f 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -68,11 +68,12 @@ module LabelsHelper end end - def toggle_subscription_label_path(label) - case label - when GroupLabel then toggle_subscription_group_label_path(label.group, label) - when ProjectLabel then toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) - end + def toggle_subscription_data(label) + return unless label.is_a?(ProjectLabel) + + { + url: toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) + } end def render_colored_label(label, label_suffix = '', tooltip: true) @@ -148,11 +149,17 @@ module LabelsHelper end def label_subscription_status(label) - label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed' + case label + when GroupLabel then 'Subscribing to group labels is currently not supported.' + when ProjectLabel then label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed' + end end def label_subscription_toggle_button_text(label) - label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' + case label + when GroupLabel then 'Subscribing to group labels is currently not supported.' + when ProjectLabel then label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' + end end def label_deletion_confirm_text(label) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 5cdd18d24f0..40c8d2af226 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -18,7 +18,7 @@ = link_to_label(label, subject: @project) do = pluralize open_issues_count, 'open issue' - if current_user - %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } + %li.label-subscription{ data: toggle_subscription_data(label) } %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } %span= label_subscription_toggle_button_text(label) - if can?(current_user, :admin_label, label) @@ -34,10 +34,10 @@ = pluralize open_issues_count, 'open issue' - if current_user - .label-subscription.inline{ data: { url: toggle_subscription_label_path(label) } } + .label-subscription.inline{ data: toggle_subscription_data(label) } %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } %span.sr-only= label_subscription_toggle_button_text(label) - = icon('eye', class: 'label-subscribe-button-icon') + = icon('eye', class: 'label-subscribe-button-icon', disabled: label.is_a?(GroupLabel)) = icon('spinner spin', class: 'label-subscribe-button-loading') - if can?(current_user, :admin_label, label) @@ -48,6 +48,6 @@ %span.sr-only Delete = icon('trash-o') - - if current_user + - if current_user && label.is_a?(ProjectLabel) :javascript new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/config/routes/group.rb b/config/routes/group.rb index 7bb9aa50875..4838c9d91c6 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -29,10 +29,6 @@ resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? Date: Wed, 19 Oct 2016 11:37:10 -0200 Subject: Only show label type for projects that belong to a group --- app/views/shared/_label_row.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 76c3327fefc..d28f9421ecf 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,8 +10,9 @@ = icon('star') %span.label-name = link_to_label(label, subject: @project, tooltip: false) - %span.label-type - = label.model_name.human.titleize + - if defined?(@project) && @project.group.present? + %span.label-type + = label.model_name.human.titleize - if label.description %span.label-description = markdown_field(label, :description) -- cgit v1.2.1 From fc2c64fcdf913a37f987ab5e5626ef9bb9e8b854 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:49:07 -0200 Subject: Add self.project_foreign_key on both Issue and MergeRequest --- app/models/concerns/issuable.rb | 8 +------- app/models/issue.rb | 4 ++++ app/models/merge_request.rb | 4 ++++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 9a9b562af02..17c3b526c97 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -145,16 +145,10 @@ module Issuable end def order_labels_priority(excluded_labels: []) - project_column = - case table_name - when Issue.table_name then "#{table_name}.project_id" - when MergeRequest.table_name then "#{table_name}.target_project_id" - end - params = { target_type: name, target_column: "#{table_name}.id", - project_column: project_column, + project_column: "#{table_name}.#{project_foreign_key}", excluded_labels: excluded_labels } diff --git a/app/models/issue.rb b/app/models/issue.rb index f7ccce2924a..133a5993815 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -138,6 +138,10 @@ class Issue < ActiveRecord::Base reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE end + def self.project_foreign_key + 'project_id' + end + def self.sort(method, excluded_labels: []) case method.to_s when 'due_date_asc' then order_due_date_asc diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index fedc35102ef..0cc0b3c2a0e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -137,6 +137,10 @@ class MergeRequest < ActiveRecord::Base reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE end + def self.project_foreign_key + 'target_project_id' + end + # Returns all the merge requests from an ActiveRecord:Relation. # # This method uses a UNION as it usually operates on the result of -- cgit v1.2.1 From 4f6d1c1d70d744ff599ae9c51e1cbc3a3c23e13e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:53:31 -0200 Subject: Rename Labels::CreateService to Labels::FindOrCreateService --- app/models/project.rb | 2 +- app/services/boards/lists/generate_service.rb | 2 +- app/services/issuable_base_service.rb | 2 +- app/services/labels/create_service.rb | 33 -------------- app/services/labels/find_or_create_service.rb | 33 ++++++++++++++ app/services/labels/transfer_service.rb | 2 +- lib/gitlab/fogbugz_import/importer.rb | 2 +- lib/gitlab/github_import/label_formatter.rb | 2 +- lib/gitlab/google_code_import/importer.rb | 2 +- lib/gitlab/issues_labels.rb | 2 +- spec/services/labels/create_service_spec.rb | 51 ---------------------- .../services/labels/find_or_create_service_spec.rb | 51 ++++++++++++++++++++++ 12 files changed, 92 insertions(+), 92 deletions(-) delete mode 100644 app/services/labels/create_service.rb create mode 100644 app/services/labels/find_or_create_service.rb delete mode 100644 spec/services/labels/create_service_spec.rb create mode 100644 spec/services/labels/find_or_create_service_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index bc15ca3fc2e..a6039bb8cc4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -734,7 +734,7 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| params = label.attributes.except('id', 'template', 'created_at', 'updated_at') - Labels::CreateService.new(owner, self, params).execute + Labels::FindOrCreateService.new(owner, self, params).execute end end diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 1d3c7f2071b..939f9bfd068 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -19,7 +19,7 @@ module Boards end def find_or_create_label(params) - ::Labels::CreateService.new(current_user, project, params).execute + ::Labels::FindOrCreateService.new(current_user, project, params).execute end def label_params diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 4554963370f..bb92cd80cc9 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -88,7 +88,7 @@ class IssuableBaseService < BaseService return unless labels params[:label_ids] = labels.split(',').map do |label_name| - service = Labels::CreateService.new(current_user, project, title: label_name.strip) + service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip) label = service.execute label.id diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb deleted file mode 100644 index bb475ce741d..00000000000 --- a/app/services/labels/create_service.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Labels - class CreateService - def initialize(current_user, project, params = {}) - @current_user = current_user - @group = project.group - @project = project - @params = params.dup - end - - def execute - find_or_create_label - end - - private - - attr_reader :current_user, :group, :project, :params - - def available_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute - end - - def find_or_create_label - new_label = available_labels.find_by(title: title) - new_label ||= project.labels.create(params) - - new_label - end - - def title - params[:title] || params[:name] - end - end -end diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb new file mode 100644 index 00000000000..74291312c4e --- /dev/null +++ b/app/services/labels/find_or_create_service.rb @@ -0,0 +1,33 @@ +module Labels + class FindOrCreateService + def initialize(current_user, project, params = {}) + @current_user = current_user + @group = project.group + @project = project + @params = params.dup + end + + def execute + find_or_create_label + end + + private + + attr_reader :current_user, :group, :project, :params + + def available_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute + end + + def find_or_create_label + new_label = available_labels.find_by(title: title) + new_label ||= project.labels.create(params) + + new_label + end + + def title + params[:title] || params[:name] + end + end +end diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 65b4bdbaff9..f91b3724aef 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -43,7 +43,7 @@ module Labels def find_or_create_label!(label) params = label.attributes.slice('title', 'description', 'color') - new_label = CreateService.new(current_user, project, params).execute + new_label = FindOrCreateService.new(current_user, project, params).execute new_label.id end diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index f154ee689cf..ce4d87a0741 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -75,7 +75,7 @@ module Gitlab def create_label(name) params = { title: name, color: nice_label_color(name) } - ::Labels::CreateService.new(project.owner, project, params).execute + ::Labels::FindOrCreateService.new(project.owner, project, params).execute end def user_info(person_id) diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb index 8ed1574c4fc..942dfb3312b 100644 --- a/lib/gitlab/github_import/label_formatter.rb +++ b/lib/gitlab/github_import/label_formatter.rb @@ -15,7 +15,7 @@ module Gitlab def create! params = attributes.except(:project) - service = ::Labels::CreateService.new(project.owner, project, params) + service = ::Labels::FindOrCreateService.new(project.owner, project, params) label = service.execute raise ActiveRecord::RecordInvalid.new(label) unless label.persisted? diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 904a228aeef..b16a5654096 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -234,7 +234,7 @@ module Gitlab def create_label(name) params = { name: name, color: nice_label_color(name) } - ::Labels::CreateService.new(project.owner, project, params).execute + ::Labels::FindOrCreateService.new(project.owner, project, params).execute end def format_content(raw_content) diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 6788eca7146..01a2c19ab23 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -19,7 +19,7 @@ module Gitlab ] labels.each do |params| - ::Labels::CreateService.new(project.owner, project).execute(params) + ::Labels::FindOrCreateService.new(project.owner, project).execute(params) end end end diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb deleted file mode 100644 index 1e4bc294b46..00000000000 --- a/spec/services/labels/create_service_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' - -describe Labels::CreateService, services: true do - describe '#execute' do - let(:user) { create(:user) } - let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } - - let(:params) do - { - title: 'Security', - description: 'Security related stuff.', - color: '#FF0000' - } - end - - subject(:service) { described_class.new(user, project, params) } - - before do - project.team << [user, :developer] - end - - context 'when label does not exist at group level' do - it 'creates a new label at project level' do - expect { service.execute }.to change(project.labels, :count).by(1) - end - end - - context 'when label exists at group level' do - it 'returns the group label' do - group_label = create(:group_label, group: group, title: 'Security') - - expect(service.execute).to eq group_label - end - end - - context 'when label does not exist at group level' do - it 'creates a new label at project leve' do - expect { service.execute }.to change(project.labels, :count).by(1) - end - end - - context 'when label exists at project level' do - it 'returns the project label' do - project_label = create(:label, project: project, title: 'Security') - - expect(service.execute).to eq project_label - end - end - end -end diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb new file mode 100644 index 00000000000..cbfc63de811 --- /dev/null +++ b/spec/services/labels/find_or_create_service_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Labels::FindOrCreateService, services: true do + describe '#execute' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + + let(:params) do + { + title: 'Security', + description: 'Security related stuff.', + color: '#FF0000' + } + end + + subject(:service) { described_class.new(user, project, params) } + + before do + project.team << [user, :developer] + end + + context 'when label does not exist at group level' do + it 'creates a new label at project level' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at group level' do + it 'returns the group label' do + group_label = create(:group_label, group: group, title: 'Security') + + expect(service.execute).to eq group_label + end + end + + context 'when label does not exist at group level' do + it 'creates a new label at project leve' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at project level' do + it 'returns the project label' do + project_label = create(:label, project: project, title: 'Security') + + expect(service.execute).to eq project_label + end + end + end +end -- cgit v1.2.1 From e6957a6b4776c47e7f21bd7494e4efafa63501ca Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:59:31 -0200 Subject: Remove order by label type on LabelsFinder --- app/finders/labels_finder.rb | 2 +- app/views/shared/issuable/_filter.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index a5802171b6c..6ace14a4bb5 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -31,7 +31,7 @@ class LabelsFinder < UnionFinder end def sort(items) - items.reorder(title: :asc, type: :desc) + items.reorder(title: :asc) end def with_title(items) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index f8018fc3de0..8c2036a1cde 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -27,7 +27,7 @@ = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title, :type).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } + = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } .filter-item.inline.reset-filters %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters -- cgit v1.2.1 From 1d8b74fee34af0f13e69a3363417493746279488 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 14:47:17 -0200 Subject: Avoid touch label links that does not belongs to project when moving it --- app/services/labels/transfer_service.rb | 57 ++++++++++++++++++++------- spec/services/labels/transfer_service_spec.rb | 29 ++++++++------ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index f91b3724aef..514679ed29d 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -4,48 +4,75 @@ # module Labels class TransferService - def initialize(current_user, group, project) + def initialize(current_user, old_group, project) @current_user = current_user - @group = group + @old_group = old_group @project = project end def execute - return unless group.present? + return unless old_group.present? Label.transaction do - labels_to_transfer = Label.where(id: label_links.select(:label_id)) - labels_to_transfer.find_each do |label| new_label_id = find_or_create_label!(label) next if new_label_id == label.id - LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) - LabelPriority.where(project_id: project.id, label_id: label.id).update_all(label_id: new_label_id) + update_label_links(group_labels_applied_to_issues, old_label_id: label.id, new_label_id: new_label_id) + update_label_links(group_labels_applied_to_merge_requests, old_label_id: label.id, new_label_id: new_label_id) + update_label_priorities(old_label_id: label.id, new_label_id: new_label_id) end end end private - attr_reader :current_user, :group, :project + attr_reader :current_user, :old_group, :project + + def labels_to_transfer + label_ids = [] + label_ids << group_labels_applied_to_issues.select(:id) + label_ids << group_labels_applied_to_merge_requests.select(:id) - def label_links - label_link_ids = [] - label_link_ids << LabelLink.where(target: project.issues, label: group.labels).select(:id) - label_link_ids << LabelLink.where(target: project.merge_requests, label: group.labels).select(:id) + union = Gitlab::SQL::Union.new(label_ids) - union = Gitlab::SQL::Union.new(label_link_ids) + Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq + end + + def group_labels_applied_to_issues + Label.joins(:issues). + where( + issues: { project_id: project.id }, + labels: { type: 'GroupLabel', group_id: old_group.id } + ) + end - LabelLink.where("label_links.id IN (#{union.to_sql})") + def group_labels_applied_to_merge_requests + Label.joins(:merge_requests). + where( + merge_requests: { target_project_id: project.id }, + labels: { type: 'GroupLabel', group_id: old_group.id } + ) end def find_or_create_label!(label) - params = label.attributes.slice('title', 'description', 'color') + params = label.attributes.slice('title', 'description', 'color') new_label = FindOrCreateService.new(current_user, project, params).execute new_label.id end + + def update_label_links(labels, old_label_id:, new_label_id:) + LabelLink.joins(:label). + merge(labels). + where(label_id: old_label_id). + update_all(label_id: new_label_id) + end + + def update_label_priorities(old_label_id:, new_label_id:) + LabelPriority.where(project_id: project.id, label_id: old_label_id). + update_all(label_id: new_label_id) + end end end diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index cb09c16698a..ddf3527dc0f 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -5,33 +5,38 @@ describe Labels::TransferService, services: true do let(:user) { create(:user) } let(:group_1) { create(:group) } let(:group_2) { create(:group) } - let(:project) { create(:project, namespace: group_2) } + let(:group_3) { create(:group) } + let(:project_1) { create(:project, namespace: group_2) } + let(:project_2) { create(:project, namespace: group_3) } let(:group_label_1) { create(:group_label, group: group_1, name: 'Group Label 1') } let(:group_label_2) { create(:group_label, group: group_1, name: 'Group Label 2') } let(:group_label_3) { create(:group_label, group: group_1, name: 'Group Label 3') } let(:group_label_4) { create(:group_label, group: group_2, name: 'Group Label 4') } - let(:project_label_1) { create(:label, project: project, name: 'Project Label 1') } + let(:group_label_5) { create(:group_label, group: group_3, name: 'Group Label 5') } + let(:project_label_1) { create(:label, project: project_1, name: 'Project Label 1') } - subject(:service) { described_class.new(user, group_1, project) } + subject(:service) { described_class.new(user, group_1, project_1) } before do - create(:labeled_issue, project: project, labels: [group_label_1]) - create(:labeled_issue, project: project, labels: [group_label_4]) - create(:labeled_issue, project: project, labels: [project_label_1]) - create(:labeled_merge_request, source_project: project, labels: [group_label_1, group_label_2]) + create(:labeled_issue, project: project_1, labels: [group_label_1]) + create(:labeled_issue, project: project_1, labels: [group_label_4]) + create(:labeled_issue, project: project_1, labels: [project_label_1]) + create(:labeled_issue, project: project_2, labels: [group_label_5]) + create(:labeled_merge_request, source_project: project_1, labels: [group_label_1, group_label_2]) + create(:labeled_merge_request, source_project: project_2, labels: [group_label_5]) end it 'recreates the missing group labels at project level' do - expect { service.execute }.to change(project.labels, :count).by(2) + expect { service.execute }.to change(project_1.labels, :count).by(2) end it 'recreates label priorities related to the missing group labels' do - create(:label_priority, project: project, label: group_label_1, priority: 1) + create(:label_priority, project: project_1, label: group_label_1, priority: 1) service.execute - new_project_label = project.labels.find_by(title: group_label_1.title) + new_project_label = project_1.labels.find_by(title: group_label_1.title) expect(new_project_label.id).not_to eq group_label_1.id expect(new_project_label.priorities).not_to be_empty end @@ -39,13 +44,13 @@ describe Labels::TransferService, services: true do it 'does not recreate missing group labels that are not applied to issues or merge requests' do service.execute - expect(project.labels.where(title: group_label_3.title)).to be_empty + expect(project_1.labels.where(title: group_label_3.title)).to be_empty end it 'does not recreate missing group labels that already exist in the project group' do service.execute - expect(project.labels.where(title: group_label_4.title)).to be_empty + expect(project_1.labels.where(title: group_label_4.title)).to be_empty end end end -- cgit v1.2.1 From 2fad811d0d028a7bb663ae9c2ff1ed3b251a82a0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Oct 2016 17:27:10 +0000 Subject: Spaces before `}`! --- app/views/projects/cycle_analytics/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index ef24ec0cf29..b647882efa0 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -2,7 +2,7 @@ - page_title "Cycle Analytics" = render "projects/pipelines/head" -#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}} +#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) }} .bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"} = icon('times', class: 'dismiss-icon', "@click" => "dismissLanding()") -- cgit v1.2.1 From c90a31ebcf56aa095e471edb2cd21df887318d1a Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 19 Oct 2016 22:09:18 +0300 Subject: Remove show_menu_above attribute from issuable dropdowns. --- app/views/shared/issuable/_form.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index a7944a60130..ee5c21f0762 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -88,19 +88,19 @@ - if issuable.assignee_id = f.hidden_field :assignee_id = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } }) + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label" + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" - if has_due_date .col-lg-6 .form-group -- cgit v1.2.1 From 2fabd1a123e2bcbf8f372679cacd50fc1da11252 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 22:14:57 +0300 Subject: Change the order of tested methods in project_members_controller_spec Signed-off-by: Dmitriy Zaporozhets --- .../projects/project_members_controller_spec.rb | 88 +++++++++++----------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index ea56bd72912..8519ebc1d5f 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -4,50 +4,6 @@ describe Projects::ProjectMembersController do let(:user) { create(:user) } let(:project) { create(:project, :public) } - describe 'POST apply_import' do - let(:another_project) { create(:project, :private) } - let(:member) { create(:user) } - - before do - project.team << [user, :master] - another_project.team << [member, :guest] - sign_in(user) - end - - shared_context 'import applied' do - before do - post(:apply_import, namespace_id: project.namespace, - project_id: project, - source_project_id: another_project.id) - end - end - - context 'when user can access source project members' do - before { another_project.team << [user, :guest] } - include_context 'import applied' - - it 'imports source project members' do - expect(project.team_members).to include member - expect(response).to set_flash.to 'Successfully imported' - expect(response).to redirect_to( - namespace_project_project_members_path(project.namespace, project) - ) - end - end - - context 'when user is not member of a source project' do - include_context 'import applied' - - it 'does not import team members' do - expect(project.team_members).not_to include member - end - - it 'responds with not found' do - expect(response.status).to eq 404 - end - end - end - describe 'GET index' do it 'renders index with 200 status code' do get :index, namespace_id: project.namespace, project_id: project @@ -228,4 +184,48 @@ describe Projects::ProjectMembersController do end end end + + describe 'POST apply_import' do + let(:another_project) { create(:project, :private) } + let(:member) { create(:user) } + + before do + project.team << [user, :master] + another_project.team << [member, :guest] + sign_in(user) + end + + shared_context 'import applied' do + before do + post(:apply_import, namespace_id: project.namespace, + project_id: project, + source_project_id: another_project.id) + end + end + + context 'when user can access source project members' do + before { another_project.team << [user, :guest] } + include_context 'import applied' + + it 'imports source project members' do + expect(project.team_members).to include member + expect(response).to set_flash.to 'Successfully imported' + expect(response).to redirect_to( + namespace_project_project_members_path(project.namespace, project) + ) + end + end + + context 'when user is not member of a source project' do + include_context 'import applied' + + it 'does not import team members' do + expect(project.team_members).not_to include member + end + + it 'responds with not found' do + expect(response.status).to eq 404 + end + end + end end -- cgit v1.2.1 From 97762a5858864dd39aa744e19bc16b555832ebba Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 17:27:58 -0200 Subject: Use LabelsFinder on Google Code importer --- lib/gitlab/google_code_import/importer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index b16a5654096..6a68e786b4f 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -101,7 +101,8 @@ module Gitlab state: raw_issue['state'] == 'closed' ? 'closed' : 'opened' ) - issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) + issue_labels = ::LabelsFinder.new(project.owner, project_id: project.id, title: labels).execute + issue.update_attribute(:label_ids, issue_labels.pluck(:id)) import_issue_comments(issue, comments) end -- cgit v1.2.1 From edef2d15f9f3b357074b73db3b26acc5b7ab2d5a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 17:28:14 -0200 Subject: Use LabelsFinder on Fogbuz importer --- lib/gitlab/fogbugz_import/importer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index ce4d87a0741..65ee85ca5a9 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -133,7 +133,8 @@ module Gitlab updated_at: DateTime.parse(bug['dtLastUpdated']) ) - issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) + issue_labels = ::LabelsFinder.new(project.owner, project_id: project.id, title: labels).execute + issue.update_attribute(:label_ids, issue_labels.pluck(:id)) import_issue_comments(issue, comments) end -- cgit v1.2.1