FEATURE: Assign to group (#182)

FEATURE: Assign to group (#182)

Ability to assign groups.

To assign group, user must have a right to send a message to that group.

In addition, 2 jobs were introduced, - AssignNotification and UnassignNotification to inform interested users in the background about the new assignment.

diff --git a/app/controllers/discourse_assign/assign_controller.rb b/app/controllers/discourse_assign/assign_controller.rb
index 98b1406..fe73720 100644
--- a/app/controllers/discourse_assign/assign_controller.rb
+++ b/app/controllers/discourse_assign/assign_controller.rb
@@ -13,6 +13,7 @@ module DiscourseAssign
           JOIN(
             SELECT assigned_to_id user_id, MAX(created_at) last_assigned
             FROM assignments
+            WHERE assignments.assigned_to_type = 'User'
             GROUP BY assigned_to_id
             HAVING COUNT(*) < #{SiteSetting.max_assigned_topics}
           ) as X ON X.user_id = users.id
@@ -24,34 +25,11 @@ module DiscourseAssign
 
       render json: {
         assign_allowed_on_groups: Group.visible_groups(current_user).assign_allowed_groups.pluck(:name),
-        suggestions: ActiveModel::ArraySerializer.new(users, scope: guardian, each_serializer: BasicUserSerializer)
+        assign_allowed_for_groups: Group.visible_groups(current_user).messageable(current_user).pluck(:name),
+        suggestions: ActiveModel::ArraySerializer.new(users, scope: guardian, each_serializer: BasicUserSerializer),
       }
     end
 
-    def claim
-      topic_id = params.require(:topic_id).to_i
-      topic = Topic.find(topic_id)
-
-      assigned_id = Assignment
-        .where(topic_id: topic_id)
-        .where.not(assigned_to_id: nil)
-        .pluck_first(:assigned_to_id)
-
-      if assigned_id
-        if user = User.where(id: assigned_id).first
-          extras = {
-            assigned_to: serialize_data(user, BasicUserSerializer, root: false)
-          }
-        end
-
-        return render_json_error(I18n.t('discourse_assign.already_claimed'), extras: extras)
-      end
-
-      assigner = TopicAssigner.new(topic, current_user)
-      assigner.assign(current_user)
-      render json: success_json
-    end
-
     def unassign
       topic_id = params.require(:topic_id)
       topic = Topic.find(topic_id.to_i)
@@ -63,10 +41,11 @@ module DiscourseAssign
 
     def assign
       topic_id = params.require(:topic_id)
-      username = params.require(:username)
+      username = params.permit(:username)['username']
+      group_name = params.permit(:group_name)['group_name']
 
       topic = Topic.find(topic_id.to_i)
-      assign_to = User.find_by(username_lower: username.downcase)
+      assign_to = username.present? ? User.find_by(username_lower: username.downcase) : Group.where("LOWER(name) = ?", group_name.downcase).first
 
       raise Discourse::NotFound unless assign_to
 
@@ -111,7 +90,7 @@ module DiscourseAssign
 
       topics.each do |topic|
         user_id = assignments[topic.id]
-        topic.preload_assigned_to_user(users_map[user_id]) if user_id
+        topic.preload_assigned_to(users_map[user_id]) if user_id
       end
 
       render json: { topics: serialize_data(topics, AssignedTopicSerializer) }
@@ -131,7 +110,7 @@ module DiscourseAssign
 
       members = User
         .joins("LEFT OUTER JOIN group_users g ON g.user_id = users.id")
-        .joins("LEFT OUTER JOIN assignments a ON a.assigned_to_id = users.id")
+        .joins("LEFT OUTER JOIN assignments a ON a.assigned_to_id = users.id AND a.assigned_to_type = 'User'")
         .joins("LEFT OUTER JOIN topics t ON t.id = a.topic_id")
         .where("g.group_id = ? AND users.id > 0 AND t.deleted_at IS NULL", group.id)
         .where("a.assigned_to_id IS NOT NULL")
@@ -148,8 +127,12 @@ module DiscourseAssign
       end
 
       assignment_count = Topic
-        .joins("JOIN assignments a ON a.topic_id = topics.id AND a.assigned_to_id IS NOT NULL")
-        .where("a.assigned_to_id IN (SELECT group_users.user_id FROM group_users WHERE (group_id IN (SELECT id FROM groups WHERE name = ?)))", group.name)
+        .joins("JOIN assignments a ON a.topic_id = topics.id AND a.assigned_to_id IS NOT NULL ")
+        .where(<<~SQL, group_id: group.id)
+          (a.assigned_to_id IN (SELECT group_users.user_id FROM group_users WHERE group_id = :group_id) AND a.assigned_to_type = 'User')
+          OR
+          (a.assigned_to_id = :group_id AND a.assigned_to_type = 'Group')
+        SQL
         .where("topics.deleted_at IS NULL")
         .count
 
@@ -158,19 +141,27 @@ module DiscourseAssign
 
     private
 
-    def translate_failure(reason, user)
+    def translate_failure(reason, assign_to)
       case reason
       when :already_assigned
-        { error: I18n.t('discourse_assign.already_assigned', username: user.username) }
+        { error: I18n.t('discourse_assign.already_assigned', username: assign_to.username) }
       when :forbidden_assign_to
-        { error: I18n.t('discourse_assign.forbidden_assign_to', username: user.username) }
+        { error: I18n.t('discourse_assign.forbidden_assign_to', username: assign_to.username) }
       when :forbidden_assignee_not_pm_participant
-        { error: I18n.t('discourse_assign.forbidden_assignee_not_pm_participant', username: user.username) }
+        { error: I18n.t('discourse_assign.forbidden_assignee_not_pm_participant', username: assign_to.username) }
       when :forbidden_assignee_cant_see_topic
-        { error: I18n.t('discourse_assign.forbidden_assignee_cant_see_topic', username: user.username) }
+        { error: I18n.t('discourse_assign.forbidden_assignee_cant_see_topic', username: assign_to.username) }
+      when :group_already_assigned
+        { error: I18n.t('discourse_assign.group_already_assigned', group: assign_to.name) }
+      when :forbidden_group_assign_to
+        { error: I18n.t('discourse_assign.forbidden_group_assign_to', group: assign_to.name) }
+      when :forbidden_group_assignee_not_pm_participant
+        { error: I18n.t('discourse_assign.forbidden_group_assignee_not_pm_participant', group: assign_to.name) }
+      when :forbidden_group_assignee_cant_see_topic
+        { error: I18n.t('discourse_assign.forbidden_group_assignee_cant_see_topic', group: assign_to.name) }
       else
         max = SiteSetting.max_assigned_topics
-        { error: I18n.t('discourse_assign.too_many_assigns', username: user.username, max: max) }
+        { error: I18n.t('discourse_assign.too_many_assigns', username: assign_to.username, max: max) }
       end
     end
 
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index 936c1cc..426c22f 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -2,4 +2,6 @@
 
 class Assignment < ActiveRecord::Base
   belongs_to :topic
+  belongs_to :assigned_to, polymorphic: true
+  belongs_to :assigned_by_user, class_name: "User"
 end
diff --git a/app/serializers/assigned_topic_serializer.rb b/app/serializers/assigned_topic_serializer.rb
index f6f18bb..18dbc08 100644
--- a/app/serializers/assigned_topic_serializer.rb
+++ b/app/serializers/assigned_topic_serializer.rb
@@ -6,8 +6,25 @@ class AssignedTopicSerializer < BasicTopicSerializer
   attributes :excerpt,
              :category_id,
              :created_at,
-             :updated_at
+             :updated_at,
+             :assigned_to_user,
+             :assigned_to_group
 
   has_one :user, serializer: BasicUserSerializer, embed: :objects
-  has_one :assigned_to_user, serializer: BasicUserSerializer, embed: :objects
+
+  def assigned_to_user
+    BasicUserSerializer.new(object.assigned_to, scope: scope, root: false).as_json
+  end
+
+  def include_assigned_to_user?
+    object.assigned_to.is_a?(User)
+  end
+
+  def assigned_to_group
+    BasicGroupSerializer.new(object.assigned_to, scope: scope, root: false).as_json
+  end
+
+  def include_assigned_to_group?
+    object.assigned_to.is_a?(Group)
+  end
 end
diff --git a/assets/javascripts/discourse-assign/controllers/assign-user.js.es6 b/assets/javascripts/discourse-assign/controllers/assign-user.js.es6
index 7a84d56..8e4b46b 100644
--- a/assets/javascripts/discourse-assign/controllers/assign-user.js.es6
+++ b/assets/javascripts/discourse-assign/controllers/assign-user.js.es6

[... diff too long, it was truncated ...]

GitHub sha: d807491df2d1170dadac7b8c19bbdf49d23dbc6f

This commit appears in #182 which was approved by jjaffeux. It was merged by lis2.