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 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