From 0e2f26dd2a10ed876f96b0496dff2de6780eeaea Mon Sep 17 00:00:00 2001
From: Alfredo Sumaran <alfredo@gitlab.com>
Date: Mon, 2 May 2016 18:19:46 -0500
Subject: [PATCH] Prioritize labels functionality

---
 app/assets/javascripts/LabelManager.js.coffee | 79 +++++++++++++++++++
 app/assets/javascripts/dispatcher.js.coffee   |  2 +
 app/assets/stylesheets/pages/labels.scss      | 12 +++
 app/controllers/projects/labels_controller.rb | 22 +++++-
 app/models/label.rb                           |  2 +
 app/views/projects/labels/_label.html.haml    | 12 ++-
 app/views/projects/labels/index.html.haml     | 27 ++++---
 config/routes.rb                              |  2 +
 8 files changed, 146 insertions(+), 12 deletions(-)
 create mode 100644 app/assets/javascripts/LabelManager.js.coffee

diff --git a/app/assets/javascripts/LabelManager.js.coffee b/app/assets/javascripts/LabelManager.js.coffee
new file mode 100644
index 0000000000..056a03651b
--- /dev/null
+++ b/app/assets/javascripts/LabelManager.js.coffee
@@ -0,0 +1,79 @@
+class @LabelManager
+  constructor: (opts = {}) ->
+    # Defaults
+    {
+      @togglePriorityButton = $('.js-toggle-priority')
+      @prioritizedLabels = $('.js-prioritized-labels')
+      @otherLabels = $('.js-other-labels')
+    } = opts
+
+    @prioritizedLabels.sortable(
+      items: 'li'
+      update: @onPrioritySortUpdate.bind(@)
+    )
+
+    @bindEvents()
+
+  bindEvents: ->
+    @togglePriorityButton.on 'click', @, @onTogglePriorityClick
+
+  onTogglePriorityClick: (e) ->
+    e.preventDefault()
+    _this = e.data
+    $btn = $(e.currentTarget)
+    $label = $("##{$btn.data('domId')}")
+    action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
+    _this.toggleLabelPriority($label, action)
+
+  toggleLabelPriority: ($label, action, pasive = false) ->
+    _this = @
+    url = $label.find('.js-toggle-priority').data 'url'
+
+    $target = @prioritizedLabels
+    $from = @otherLabels
+
+    # Optimistic update
+    if action is 'remove'
+      $target = @otherLabels
+      $from = @prioritizedLabels
+
+    if $from.find('li').length is 1
+      $from.find('.empty-message').show()
+
+    if not $target.find('li').length
+      $target.find('.empty-message').hide()
+
+    $label.detach().appendTo($target)
+
+    # Return if we are not persisting state
+    return if pasive
+
+    xhr = $.post url
+
+    # If request fails, put label back to Other labels group
+    xhr.fail ->
+      _this.toggleLabelPriority($label, 'remove', true)
+
+      # Show a message
+      new Flash('Unable to update label prioritization at this time' , 'alert')
+
+  onPrioritySortUpdate: ->
+    @savePrioritySort()
+
+  savePrioritySort: ->
+    xhr = $.post
+            url: @prioritizedLabels.data('url')
+            data:
+              label_ids: @getSortedLabelsIds()
+
+    xhr.done ->
+      console.log 'done'
+
+    xhr.fail ->
+      console.log 'fail'
+
+  getSortedLabelsIds: ->
+    sortedIds = []
+    @prioritizedLabels.find('li').each ->
+      sortedIds.push $(@).data 'id'
+    sortedIds
\ No newline at end of file
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index ec54006045..49fc7ef2e1 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -100,6 +100,8 @@ class Dispatcher
         shortcut_handler = new ShortcutsNavigation()
       when 'projects:labels:new', 'projects:labels:edit'
         new Labels()
+      when 'projects:labels:index'
+        new LabelManager()
       when 'projects:network:show'
         # Ensure we don't create a particular shortcut handler here. This is
         # already created, where the network graph is created.
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index e179bdf004..4b0b512db8 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -138,3 +138,15 @@
     }
   }
 }
+
+.prioritized-labels {
+  .add-priority {
+    display: none;
+  }
+}
+
+.other-labels {
+  .remove-priority {
+    display: none;
+  }
+}
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index ff771ea6d9..88d745e6ba 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -11,7 +11,8 @@ class Projects::LabelsController < Projects::ApplicationController
   respond_to :js, :html
 
   def index
-    @labels = @project.labels.page(params[:page])
+    @labels = @project.labels.prioritized(false).page(params[:page])
+    @prioritized = @project.labels.prioritized
 
     respond_to do |format|
       format.html
@@ -71,6 +72,25 @@ class Projects::LabelsController < Projects::ApplicationController
     end
   end
 
+  def toggle_priority
+    priority = label.priority
+
+    respond_to do |format|
+      if label.update_attributes(priority: !priority)
+        format.json { render json: label }
+      else
+        message = label.errors.full_messages.uniq.join('. ')
+        format.json { render json: { message: message }, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  def set_sorting
+    respond_to do |format|
+      format.json { render json: {message: 'success'}}
+    end
+  end
+
   protected
 
   def module_enabled
diff --git a/app/models/label.rb b/app/models/label.rb
index e5ad11983b..59e7afe53f 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -19,6 +19,7 @@ class Label < ActiveRecord::Base
 
   validates :color, color: true, allow_blank: false
   validates :project, presence: true, unless: Proc.new { |service| service.template? }
+  validates :priority, presence: false, default: false
 
   # Don't allow '?', '&', and ',' for label titles
   validates :title,
@@ -29,6 +30,7 @@ class Label < ActiveRecord::Base
   default_scope { order(title: :asc) }
 
   scope :templates, ->  { where(template: true) }
+  scope :prioritized, ->(value = true) { where(priority: value) }
 
   alias_attribute :name, :title
 
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 294fec422c..eda75b64e7 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,4 +1,12 @@
-%li{ id: dom_id(label), data: { id: label.id } }
+- label_css_id = dom_id(label)
+%li{id: label_css_id, :"data-id" => label.id}
+  %a.js-toggle-priority{:href => "#",
+    :"data-url" => toggle_priority_namespace_project_label_path(@project.namespace, @project, label),
+    :"data-dom-id" => "#{label_css_id}" }
+    %span.add-priority
+      (+)
+    %span.remove-priority
+      (-)
   = render "shared/label_row", label: label
   .pull-info-right
     %span.append-right-20
@@ -24,4 +32,4 @@
 
 - if current_user
   :javascript
-    new Subscription('##{dom_id(label)} .label-subscription');
+    new Subscription('##{label_css_id} .label-subscription');
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 2557d1a4d5..d71db7545e 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -10,13 +10,22 @@
         New label
 
 .labels
-  - if @labels.present?
-    %ul.content-list.manage-labels-list
-      = 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}.
+  .prioritized-labels
+    %h5 Prioritized Label
+    %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_sorting_namespace_project_labels_path(@project.namespace, @project) }
+      - if @prioritized.present?
+        = render @prioritized
       - else
-        No labels created
+        %p.empty-message No prioritized labels yet
+  .other-labels
+    %h5 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
diff --git a/config/routes.rb b/config/routes.rb
index 7e735541f7..5aa8a0fe8a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -719,10 +719,12 @@ Rails.application.routes.draw do
         resources :labels, constraints: { id: /\d+/ } do
           collection do
             post :generate
+            post :set_sorting
           end
 
           member do
             post :toggle_subscription
+            post :toggle_priority
           end
         end
 
-- 
GitLab