diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..7b8ffc0
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,101 @@
+{
+ "env": {
+ "jasmine": true,
+ "node": true,
+ "mocha": true,
+ "browser": true,
+ "builtin": true
+ },
+ "parserOptions": {
+ "ecmaVersion": 7,
+ "sourceType": "module"
+ },
+ "globals":
+ {"Ember":true,
+ "jQuery":true,
+ "$":true,
+ "QUnit":true,
+ "RSVP":true,
+ "Discourse":true,
+ "Em":true,
+ "Handlebars":true,
+ "I18n":true,
+ "bootbox":true,
+ "moduleFor":true,
+ "moduleForComponent":true,
+ "Pretender":true,
+ "sandbox":true,
+ "controllerFor":true,
+ "test":true,
+ "visit":true,
+ "andThen":true,
+ "click":true,
+ "currentPath":true,
+ "currentRouteName":true,
+ "currentURL":true,
+ "fillIn":true,
+ "keyEvent":true,
+ "triggerEvent":true,
+ "count":true,
+ "exists":true,
+ "visible":true,
+ "invisible":true,
+ "asyncRender":true,
+ "selectDropdown":true,
+ "selectBox":true,
+ "asyncTestDiscourse":true,
+ "fixture":true,
+ "find":true,
+ "sinon":true,
+ "moment":true,
+ "_":true,
+ "alert":true,
+ "define":true,
+ "require":true,
+ "requirejs":true,
+ "hasModule":true,
+ "Blob":true,
+ "File":true},
+ "rules": {
+ "block-scoped-var": 2,
+ "dot-notation": 0,
+ "eqeqeq": [
+ 2,
+ "allow-null"
+ ],
+ "guard-for-in": 2,
+ "no-bitwise": 2,
+ "no-caller": 2,
+ "no-cond-assign": 0,
+ "no-debugger": 2,
+ "no-empty": 0,
+ "no-eval": 2,
+ "no-extend-native": 2,
+ "no-extra-parens": 0,
+ "no-inner-declarations": 2,
+ "no-irregular-whitespace": 2,
+ "no-iterator": 2,
+ "no-loop-func": 2,
+ "no-multi-str": 2,
+ "no-new": 2,
+ "no-plusplus": 0,
+ "no-proto": 2,
+ "no-script-url": 2,
+ "no-sequences": 2,
+ "no-shadow": 2,
+ "no-undef": 2,
+ "no-unused-vars": 2,
+ "no-with": 2,
+ "no-this-before-super": 2,
+ "semi": 2,
+ "strict": 0,
+ "valid-typeof": 2,
+ "wrap-iife": [
+ 2,
+ "inside"
+ ],
+ "no-mixed-spaces-and-tabs": 2,
+ "no-trailing-spaces": 2
+ },
+ "parser": "babel-eslint"
+}
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..a529980
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,112 @@
+AllCops:
+ TargetRubyVersion: 2.4
+ DisabledByDefault: true
+ Exclude:
+ - 'db/schema.rb'
+ - 'bundle/**/*'
+ - 'vendor/**/*'
+ - 'node_modules/**/*'
+
+# Prefer &&/|| over and/or.
+Style/AndOr:
+ Enabled: true
+
+# Do not use braces for hash literals when they are the last argument of a
+# method call.
+Style/BracesAroundHashParameters:
+ Enabled: true
+
+# Align `when` with `case`.
+Layout/CaseIndentation:
+ Enabled: true
+
+# Align comments with method definitions.
+Layout/CommentIndentation:
+ Enabled: true
+
+# No extra empty lines.
+Layout/EmptyLines:
+ Enabled: true
+
+# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
+Style/HashSyntax:
+ Enabled: true
+
+# Two spaces, no tabs (for indentation).
+Layout/IndentationWidth:
+ Enabled: true
+
+Layout/SpaceAfterColon:
+ Enabled: true
+
+Layout/SpaceAfterComma:
+ Enabled: true
+
+Layout/SpaceAroundEqualsInParameterDefault:
+ Enabled: true
+
+Layout/SpaceAroundKeyword:
+ Enabled: true
+
+Layout/SpaceAroundOperators:
+ Enabled: true
+
+Layout/SpaceBeforeFirstArg:
+ Enabled: true
+
+# Defining a method with parameters needs parentheses.
+Style/MethodDefParentheses:
+ Enabled: true
+
+# Use `foo {}` not `foo{}`.
+Layout/SpaceBeforeBlockBraces:
+ Enabled: true
+
+# Use `foo { bar }` not `foo {bar}`.
+Layout/SpaceInsideBlockBraces:
+ Enabled: true
+
+# Use `{ a: 1 }` not `{a:1}`.
+Layout/SpaceInsideHashLiteralBraces:
+ Enabled: true
+
+Layout/SpaceInsideParens:
+ Enabled: true
+
+# Detect hard tabs, no hard tabs.
+Layout/Tab:
+ Enabled: true
+
+# Blank lines should not have any spaces.
+Layout/TrailingBlankLines:
+ Enabled: true
+
+# No trailing whitespace.
+Layout/TrailingWhitespace:
+ Enabled: true
+
+Lint/Debugger:
+ Enabled: true
+
+Lint/BlockAlignment:
+ Enabled: true
+
+# Align `end` with the matching keyword or starting expression except for
+# assignments, where it should be aligned with the LHS.
+Lint/EndAlignment:
+ Enabled: true
+ EnforcedStyleAlignWith: variable
+
+# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
+Lint/RequireParentheses:
+ Enabled: true
+
+Layout/MultilineMethodCallIndentation:
+ Enabled: true
+ EnforcedStyle: indented
+
+Layout/AlignHash:
+ Enabled: true
+
+Bundler/OrderedGems:
+ Enabled: false
diff --git a/app/controllers/discourse_assign/assign_controller.rb b/app/controllers/discourse_assign/assign_controller.rb
new file mode 100644
index 0000000..b77f055
--- /dev/null
+++ b/app/controllers/discourse_assign/assign_controller.rb
@@ -0,0 +1,74 @@
+module DiscourseAssign
+ class AssignController < Admin::AdminController
+ before_action :ensure_logged_in
+
+ def suggestions
+ users = [current_user]
+ users += User
+ .where('admin OR moderator')
+ .where('users.id <> ?', current_user.id)
+ .joins("join (
+ SELECT value::integer user_id, MAX(created_at) last_assigned
+ FROM topic_custom_fields
+ WHERE name = 'assigned_to_id'
+ GROUP BY value::integer
+ ) as X ON X.user_id = users.id")
+ .order('X.last_assigned DESC')
+ .limit(6)
+
+ render json: 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 = TopicCustomField.where(
+ "topic_id = :topic_id AND name = 'assigned_to_id' AND value IS NOT NULL",
+ topic_id: topic_id
+ ).pluck(:value)
+
+ if assigned && user_id = assigned[0]
+ extras = nil
+ if user = User.where(id: user_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)
+ assigner = TopicAssigner.new(topic, current_user)
+ assigner.unassign
+
+ render json: success_json
+ end
+
+ def assign
+ topic_id = params.require(:topic_id)
+ username = params.require(:username)
+
+ topic = Topic.find(topic_id.to_i)
+ assign_to = User.find_by(username_lower: username.downcase)
+
+ raise Discourse::NotFound unless assign_to
+
+ assigner = TopicAssigner.new(topic, current_user)
+
+ # perhaps?
+ #Scheduler::Defer.later "assign topic" do
+ assigner.assign(assign_to)
+
+ render json: success_json
+ end
+ end
+end
diff --git a/assets/javascripts/discourse-assign/connectors/flagged-topic-details-header/assigned-to.hbs b/assets/javascripts/discourse-assign/connectors/flagged-topic-details-header/assigned-to.hbs
new file mode 100644
index 0000000..c9c4eb6
--- /dev/null
+++ b/assets/javascripts/discourse-assign/connectors/flagged-topic-details-header/assigned-to.hbs
@@ -0,0 +1,4 @@
+
+ {{flagged-topic-listener topic=topic}}
+ {{claim-topic topic=topic}}
+
diff --git a/assets/javascripts/discourse-assign/connectors/flagged-topic-header-row/add-assigned-header.hbs b/assets/javascripts/discourse-assign/connectors/flagged-topic-header-row/add-assigned-header.hbs
new file mode 100644
index 0000000..6473abd
--- /dev/null
+++ b/assets/javascripts/discourse-assign/connectors/flagged-topic-header-row/add-assigned-header.hbs
@@ -0,0 +1 @@
+{{i18n "discourse_assign.assigned"}} |
diff --git a/assets/javascripts/discourse-assign/connectors/flagged-topic-row/add-claim-controls.hbs b/assets/javascripts/discourse-assign/connectors/flagged-topic-row/add-claim-controls.hbs
new file mode 100644
index 0000000..849ffd0
--- /dev/null
+++ b/assets/javascripts/discourse-assign/connectors/flagged-topic-row/add-claim-controls.hbs
@@ -0,0 +1,3 @@
+
+ {{claim-topic topic=topic}}
+ |
diff --git a/assets/javascripts/discourse-assign/connectors/flagged-topic-row/add-claim-controls.js.es6 b/assets/javascripts/discourse-assign/connectors/flagged-topic-row/add-claim-controls.js.es6
new file mode 100644
index 0000000..66bf531
--- /dev/null
+++ b/assets/javascripts/discourse-assign/connectors/flagged-topic-row/add-claim-controls.js.es6
@@ -0,0 +1,7 @@
+export default {
+ actions: {
+ claim(topic) {
+ console.log('claim:', topic);
+ }
+ }
+}
diff --git a/assets/javascripts/discourse-assign/connectors/flagged-topics-before/add-listener.hbs b/assets/javascripts/discourse-assign/connectors/flagged-topics-before/add-listener.hbs
new file mode 100644
index 0000000..6eee759
--- /dev/null
+++ b/assets/javascripts/discourse-assign/connectors/flagged-topics-before/add-listener.hbs
@@ -0,0 +1 @@
+{{flagged-topic-listener flaggedTopics=flaggedTopics}}
diff --git a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6
index 0a378b9..759475e 100644
--- a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6
+++ b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6
@@ -6,6 +6,8 @@ import { observes } from 'ember-addons/ember-computed-decorators';
import Topic from 'discourse/models/topic';
import TopicFooterDropdown from 'discourse/components/topic-footer-mobile-dropdown';
import showModal from 'discourse/lib/show-modal';
+import { iconNode } from 'discourse-common/lib/icon-library';
+import { h } from 'virtual-dom';
function initialize(api, container) {
@@ -84,16 +86,28 @@ function initialize(api, container) {
});
}
+ api.createWidget('assigned-to', {
+ html(attrs) {
+ let { assignedToUser, href } = attrs;
+
+ return h('p.assigned-to', [
+ iconNode('user-plus'),
+ h('span.assign-text', I18n.t('discourse_assign.assigned_to')),
+ h('a', { attributes: { class: 'assigned-to-username', href } }, assignedToUser.username)
+ ]);
+ }
+ });
+
api.decorateWidget('post-contents:after-cooked', dec => {
if (dec.attrs.post_number === 1) {
const postModel = dec.getModel();
if (postModel) {
const assignedToUser = postModel.get('topic.assigned_to_user');
if (assignedToUser) {
- const path = postModel.get('topic.assignedToUserPath');
- const userLink = `${assignedToUser.username}`;
- const html = I18n.t('discourse_assign.assign_html', {userLink});
- return dec.rawHtml(html);
+ return dec.widget.attach('assigned-to', {
+ assignedToUser,
+ href: postModel.get('topic.assignedToUserPath')
+ });
}
}
}
diff --git a/assets/javascripts/discourse/components/claim-topic.js.es6 b/assets/javascripts/discourse/components/claim-topic.js.es6
new file mode 100644
index 0000000..e8916be
--- /dev/null
+++ b/assets/javascripts/discourse/components/claim-topic.js.es6
@@ -0,0 +1,44 @@
+import { ajax } from 'discourse/lib/ajax';
+import { popupAjaxError } from 'discourse/lib/ajax-error';
+
+export default Ember.Component.extend({
+ tagName: '',
+ claiming: false,
+ unassigning: false,
+
+ actions: {
+ unassign() {
+ this.set('unassigning', true);
+ return ajax("/assign/unassign", {
+ type: 'PUT',
+ data: { topic_id: this.get('topic.id') }
+ }).then(() => {
+ this.set('topic.assigned_to_user', null);
+ }).catch(popupAjaxError).finally(() => {
+ this.set('unassigning', false);
+ });
+ },
+
+ claim() {
+ this.set('claiming', true);
+
+ let topic = this.get('topic');
+ ajax(`/assign/claim/${topic.id}`, {
+ method: 'PUT'
+ }).then(() => {
+ this.set('topic.assigned_to_user', this.currentUser);
+ }).catch(e => {
+ if (e.jqXHR && e.jqXHR.responseJSON) {
+ let json = e.jqXHR.responseJSON;
+ if (json && json.extras) {
+ this.set('topic.assigned_to_user', json.extras.assigned_to);
+ }
+ }
+ return popupAjaxError(e);
+ }).finally(() => {
+ if (this.isDestroying || this.isDestroyed) { return; }
+ this.set('claiming', false);
+ });
+ }
+ }
+});
diff --git a/assets/javascripts/discourse/components/flagged-topic-listener.js.es6 b/assets/javascripts/discourse/components/flagged-topic-listener.js.es6
new file mode 100644
index 0000000..75b3e63
--- /dev/null
+++ b/assets/javascripts/discourse/components/flagged-topic-listener.js.es6
@@ -0,0 +1,25 @@
+function assignIfEqual(topic, data) {
+ if (topic && topic.id === data.topic_id) {
+ Ember.set(topic, 'assigned_to_user', data.assigned_to);
+ }
+}
+
+export default Ember.Component.extend({
+ didInsertElement() {
+ this._super();
+ this.messageBus.subscribe("/staff/topic-assignment", data => {
+ let flaggedTopics = this.get('flaggedTopics');
+ if (flaggedTopics) {
+ flaggedTopics.forEach(ft => assignIfEqual(ft.topic, data));
+ } else {
+ assignIfEqual(this.get('topic'), data);
+ }
+ });
+ },
+
+ willDestroyElement() {
+ this._super();
+ this.messageBus.unsubscribe("/staff/topic-assignment");
+ }
+});
+
diff --git a/assets/javascripts/discourse/templates/components/claim-topic.hbs b/assets/javascripts/discourse/templates/components/claim-topic.hbs
new file mode 100644
index 0000000..956d23a
--- /dev/null
+++ b/assets/javascripts/discourse/templates/components/claim-topic.hbs
@@ -0,0 +1,21 @@
+{{#if topic.assigned_to_user}}
+
+ {{avatar topic.assigned_to_user imageSize="small"}}
+
+ {{topic.assigned_to_user.username}}
+
+ {{d-button
+ icon="times"
+ class="btn-small unassign"
+ action=(action "unassign")
+ disabled=unassigning
+ title="discourse_assign.unassign.help"}}
+
+{{else}}
+ {{d-button class="btn-small assign"
+ icon="user-plus"
+ action=(action "claim")
+ disabled=claiming
+ label="discourse_assign.claim.title"
+ title="discourse_assign.claim.help"}}
+{{/if}}
diff --git a/assets/stylesheets/assigns.scss b/assets/stylesheets/assigns.scss
index fa6ade2..414fdef 100644
--- a/assets/stylesheets/assigns.scss
+++ b/assets/stylesheets/assigns.scss
@@ -1,6 +1,29 @@
-a.assigned-to .fa.fa-user-plus {
- margin-right: 2px;
- color: #999;
+.assigned-to {
+ .d-icon, i.fa {
+ margin-right: 0.5em;
+ color: $primary-medium;
+ }
+ .assign-text {
+ margin-right: 0.25em;
+ }
+}
+
+.assigned-to-user {
+ display: flex;
+ align-items: center;
+
+ img.avatar {
+ margin-right: 0.3em;
+ }
+
+ .unassign {
+ margin-left: 0.5em;
+ }
+}
+
+.topic-assigned-to {
+ min-width: 15%;
+ width: 15%;
}
.list-tags.assigned {
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 59ce115..60117b8 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -5,7 +5,7 @@ en:
unassigned: "unassigned %{who} %{when}"
discourse_assign:
assigned: "Assigned"
- assign_html: " Assigned to {{userLink}}
"
+ assigned_to: "Assigned to"
assign_notification: "{{username}} {{description}}
"
unassign:
title: "Unassign"
@@ -17,3 +17,6 @@ en:
title: "Assign Topic"
description: "Enter the username of the person you'd like to assign this topic"
assign: "Assign"
+ claim:
+ title: "claim"
+ help: "Assign topic to yourself"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 838b2a0..4275529 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -12,3 +12,4 @@ en:
discourse_assign:
assigned_to: "Topic assigned to @%{username}"
unassigned: "Topic was unassigned"
+ already_claimed: "That topic has already been claimed."
diff --git a/config/routes.rb b/config/routes.rb
new file mode 100644
index 0000000..e5404df
--- /dev/null
+++ b/config/routes.rb
@@ -0,0 +1,6 @@
+DiscourseAssign::Engine.routes.draw do
+ put "/claim/:topic_id" => "assign#claim"
+ put "/assign" => "assign#assign"
+ put "/unassign" => "assign#unassign"
+ get "/suggestions" => "assign#suggestions"
+end
diff --git a/lib/discourse_assign/engine.rb b/lib/discourse_assign/engine.rb
new file mode 100644
index 0000000..ddfdc43
--- /dev/null
+++ b/lib/discourse_assign/engine.rb
@@ -0,0 +1,6 @@
+module ::DiscourseAssign
+ class Engine < ::Rails::Engine
+ engine_name "discourse_assign"
+ isolate_namespace DiscourseAssign
+ end
+end
diff --git a/lib/discourse_assign/helpers.rb b/lib/discourse_assign/helpers.rb
new file mode 100644
index 0000000..4c8b882
--- /dev/null
+++ b/lib/discourse_assign/helpers.rb
@@ -0,0 +1,19 @@
+module DiscourseAssign
+ module Helpers
+ def self.build_assigned_to_user(assigned_to_user_id, topic)
+ if assigned_to_user_id && user = User.find_by(id: assigned_to_user_id)
+ assigned_at = TopicCustomField.where(
+ topic_id: topic.id,
+ name: "assigned_to_id"
+ ).pluck(:created_at).first
+
+ {
+ username: user.username,
+ name: user.name,
+ avatar_template: user.avatar_template,
+ assigned_at: assigned_at
+ }
+ end
+ end
+ end
+end
diff --git a/lib/topic_assigner.rb b/lib/topic_assigner.rb
new file mode 100644
index 0000000..886895e
--- /dev/null
+++ b/lib/topic_assigner.rb
@@ -0,0 +1,202 @@
+class ::TopicAssigner
+ def self.backfill_auto_assign
+ staff_mention = User.where('moderator OR admin')
+ .pluck('username')
+ .map { |name| "p.cooked ILIKE '%mention%@#{name}%'" }
+ .join(' OR ')
+
+ sql = < assign_to.username }
+ )
+
+ unless @assigned_by.id == assign_to.id
+
+ Notification.create!(
+ notification_type: Notification.types[:custom],
+ user_id: assign_to.id,
+ topic_id: @topic.id,
+ post_number: 1,
+ data: {
+ message: 'discourse_assign.assign_notification',
+ display_username: @assigned_by.username,
+ topic_title: @topic.title
+ }.to_json
+ )
+ end
+ end
+
+ true
+ end
+
+ def unassign(silent: false)
+ if assigned_to_id = @topic.custom_fields["assigned_to_id"]
+ @topic.custom_fields["assigned_to_id"] = nil
+ @topic.custom_fields["assigned_by_id"] = nil
+ @topic.save!
+
+ post = @topic.posts.where(post_number: 1).first
+ post.publish_change_to_clients!(:revised, reload_topic: true)
+
+ assigned_user = User.find_by(id: assigned_to_id)
+ MessageBus.publish(
+ "/staff/topic-assignment",
+ {
+ type: 'unassigned',
+ topic_id: @topic.id,
+ },
+ user_ids: staff_ids
+ )
+
+ UserAction.where(
+ action_type: UserAction::ASSIGNED,
+ target_post_id: post.id
+ ).destroy_all
+
+ # yank notification
+ Notification.where(
+ notification_type: Notification.types[:custom],
+ user_id: assigned_user.try(:id),
+ topic_id: @topic.id,
+ post_number: 1
+ ).where("data like '%discourse_assign.assign_notification%'").destroy_all
+
+ if SiteSetting.unassign_creates_tracking_post && !silent
+ post_type = SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper]
+ @topic.add_moderator_post(
+ @assigned_by, nil,
+ bump: false,
+ post_type: post_type,
+ custom_fields: { "action_code_who" => assigned_user&.username },
+ action_code: "unassigned"
+ )
+ end
+ end
+ end
+end
diff --git a/plugin.rb b/plugin.rb
index bb69d21..66ff9ee 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -6,392 +6,146 @@
enabled_site_setting :assign_enabled
register_asset 'stylesheets/assigns.scss'
+load File.expand_path('../lib/discourse_assign/engine.rb', __FILE__)
+load File.expand_path('../lib/discourse_assign/helpers.rb', __FILE__)
+
+Discourse::Application.routes.append do
+ mount ::DiscourseAssign::Engine, at: "/assign"
+ get "topics/private-messages-assigned/:username" => "list#private_messages_assigned", as: "topics_private_messages_assigned", constraints: { username: /[\w.\-]+?/ }
+end
after_initialize do
+ require 'topic_assigner'
- module ::DiscourseAssign
- class Engine < ::Rails::Engine
- engine_name "discourse_assign"
- isolate_namespace DiscourseAssign
- end
- end
-
- class ::TopicAssigner
-
- def self.backfill_auto_assign
- staff_mention = User.where('moderator OR admin')
- .pluck('username')
- .map { |name| "p.cooked ILIKE '%mention%@#{name}%'" }
- .join(' OR ')
-
- sql = < assign_to.username}
- })
-
- unless @assigned_by.id == assign_to.id
-
- Notification.create!(notification_type: Notification.types[:custom],
- user_id: assign_to.id,
- topic_id: @topic.id,
- post_number: 1,
- data: {
- message: 'discourse_assign.assign_notification',
- display_username: @assigned_by.username,
- topic_title: @topic.title
- }.to_json
- )
- end
- end
-
- true
- end
-
- def unassign(silent: false)
- if assigned_to_id = @topic.custom_fields["assigned_to_id"]
- @topic.custom_fields["assigned_to_id"] = nil
- @topic.custom_fields["assigned_by_id"] = nil
- @topic.save!
-
- post = @topic.posts.where(post_number: 1).first
- post.publish_change_to_clients!(:revised, { reload_topic: true })
-
- assigned_user = User.find_by(id: assigned_to_id)
-
- UserAction.where(
- action_type: UserAction::ASSIGNED,
- target_post_id: post.id
- ).destroy_all
-
- # yank notification
- Notification.where(
- notification_type: Notification.types[:custom],
- user_id: assigned_user.try(:id),
- topic_id: @topic.id,
- post_number: 1
- ).where("data like '%discourse_assign.assign_notification%'")
- .destroy_all
-
- if SiteSetting.unassign_creates_tracking_post && !silent
- post_type = SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper]
- @topic.add_moderator_post(@assigned_by, nil,
- { bump: false,
- post_type: post_type,
- custom_fields: {"action_code_who" => assigned_user&.username},
- action_code: "unassigned"})
- end
- end
- end
- end
-
- class ::DiscourseAssign::AssignController < Admin::AdminController
- before_action :ensure_logged_in
-
- def suggestions
- users = [current_user]
- users += User
- .where('admin OR moderator')
- .where('users.id <> ?', current_user.id)
- .joins("join (
- SELECT value::integer user_id, MAX(created_at) last_assigned
- FROM topic_custom_fields
- WHERE name = 'assigned_to_id'
- GROUP BY value::integer
- ) as X ON X.user_id = users.id")
- .order('X.last_assigned DESC')
- .limit(6)
-
- render json: ActiveModel::ArraySerializer.new(users,
- scope: guardian, each_serializer: BasicUserSerializer)
- end
-
- def unassign
- topic_id = params.require(:topic_id)
- topic = Topic.find(topic_id.to_i)
- assigner = TopicAssigner.new(topic, current_user)
- assigner.unassign
-
- render json: success_json
- end
-
- def assign
- topic_id = params.require(:topic_id)
- username = params.require(:username)
-
- topic = Topic.find(topic_id.to_i)
- assign_to = User.find_by(username_lower: username.downcase)
-
- raise Discourse::NotFound unless assign_to
-
- assigner = TopicAssigner.new(topic, current_user)
-
- # perhaps?
- #Scheduler::Defer.later "assign topic" do
- assigner.assign(assign_to)
-
- render json: success_json
- end
-
- class ::Topic
- def assigned_to_user
- @assigned_to_user ||
- if user_id = custom_fields["assigned_to_id"]
- @assigned_to_user = User.find_by(id: user_id)
- end
- end
-
- def preload_assigned_to_user(assigned_to_user)
- @assigned_to_user = assigned_to_user
- end
- end
-
+ # We can remove this check once this method is stable
+ if respond_to?(:add_preloaded_topic_list_custom_field)
+ add_preloaded_topic_list_custom_field('assigned_to_id')
+ else
TopicList.preloaded_custom_fields << "assigned_to_id"
+ end
- TopicList.on_preload do |topics, topic_list|
- is_staff = topic_list.current_user && topic_list.current_user.staff?
- allowed_access = SiteSetting.assigns_public || is_staff
+ TopicList.on_preload do |topics, topic_list|
+ is_staff = topic_list.current_user && topic_list.current_user.staff?
+ allowed_access = SiteSetting.assigns_public || is_staff
- if allowed_access && topics.length > 0
- users = User.where("users.id in (
- SELECT value::int
- FROM topic_custom_fields
- WHERE name = 'assigned_to_id' AND topic_id IN (?)
- )", topics.map(&:id))
- .joins('join user_emails on user_emails.user_id = users.id AND user_emails.primary')
- .select(:id, 'user_emails.email', :username, :uploaded_avatar_id)
+ if allowed_access && topics.length > 0
+ users = User.where("users.id in (
+ SELECT value::int
+ FROM topic_custom_fields
+ WHERE name = 'assigned_to_id' AND topic_id IN (?)
+ )", topics.map(&:id))
+ .joins('join user_emails on user_emails.user_id = users.id AND user_emails.primary')
+ .select(:id, 'user_emails.email', :username, :uploaded_avatar_id)
- map = {}
- users.each { |u| map[u.id] = u }
+ map = {}
+ users.each { |u| map[u.id] = u }
- topics.each do |t|
- if id = t.custom_fields['assigned_to_id']
- t.preload_assigned_to_user(map[id.to_i])
- end
+ topics.each do |t|
+ if id = t.custom_fields['assigned_to_id']
+ t.preload_assigned_to_user(map[id.to_i])
end
end
end
+ end
- require_dependency 'topic_query'
- TopicQuery.add_custom_filter(:assigned) do |results, topic_query|
- if topic_query.guardian.is_staff? || SiteSetting.assigns_public
- username = topic_query.options[:assigned]
+ require_dependency 'topic_query'
+ TopicQuery.add_custom_filter(:assigned) do |results, topic_query|
+ if topic_query.guardian.is_staff? || SiteSetting.assigns_public
+ username = topic_query.options[:assigned]
- user_id = topic_query.guardian.user.id if username == "me"
+ user_id = topic_query.guardian.user.id if username == "me"
- special = ["*", "nobody"].include?(username)
+ special = ["*", "nobody"].include?(username)
- if username.present? && !special
- user_id ||= User.where(username_lower: username.downcase).pluck(:id).first
- end
+ if username.present? && !special
+ user_id ||= User.where(username_lower: username.downcase).pluck(:id).first
+ end
- if user_id || special
+ if user_id || special
- if username == "nobody"
- results = results.joins("LEFT JOIN topic_custom_fields tc_assign ON
- topics.id = tc_assign.topic_id AND
- tc_assign.name = 'assigned_to_id'")
- .where("tc_assign.name IS NULL")
+ if username == "nobody"
+ results = results.joins("LEFT JOIN topic_custom_fields tc_assign ON
+ topics.id = tc_assign.topic_id AND
+ tc_assign.name = 'assigned_to_id'")
+ .where("tc_assign.name IS NULL")
+ else
+
+ if username == "*"
+ filter = "AND tc_assign.value IS NOT NULL"
else
-
- if username == "*"
- filter = "AND tc_assign.value IS NOT NULL"
- else
- filter = "AND tc_assign.value = '#{user_id.to_i.to_s}'"
- end
-
- results = results.joins("JOIN topic_custom_fields tc_assign ON
- topics.id = tc_assign.topic_id AND
- tc_assign.name = 'assigned_to_id'
- #{filter}
- ")
+ filter = "AND tc_assign.value = '#{user_id.to_i.to_s}'"
end
+
+ results = results.joins("JOIN topic_custom_fields tc_assign ON
+ topics.id = tc_assign.topic_id AND
+ tc_assign.name = 'assigned_to_id'
+ #{filter}
+ ")
end
end
-
- results
end
- require_dependency 'topic_list_item_serializer'
- class ::TopicListItemSerializer
- has_one :assigned_to_user, serializer: BasicUserSerializer, embed: :objects
+ results
+ end
- def include_assigned_to_user?
- (SiteSetting.assigns_public || scope.is_staff?) && object.assigned_to_user
+ require_dependency 'topic_list_item_serializer'
+ class ::TopicListItemSerializer
+ has_one :assigned_to_user, serializer: BasicUserSerializer, embed: :objects
+ end
+
+ require_dependency 'list_controller'
+ class ::ListController
+ generate_message_route(:private_messages_assigned)
+ end
+
+ add_to_class(:topic_query, :list_private_messages_assigned) do |user|
+ list = private_messages_for(user, :all)
+ list = list.where("topics.id IN (
+ SELECT topic_id FROM topic_custom_fields WHERE name = 'assigned_to_id' AND value = ?
+ )", user.id.to_s)
+ create_list(:private_messages, {}, list)
+ end
+
+ add_to_class(:topic, :assigned_to_user) do
+ @assigned_to_user ||
+ if user_id = custom_fields["assigned_to_id"]
+ @assigned_to_user = User.find_by(id: user_id)
end
+ end
+
+ add_to_class(:topic, :preload_assigned_to_user) do |assigned_to_user|
+ @assigned_to_user = assigned_to_user
+ end
+
+ add_to_serializer(:topic_list_item, 'include_assigned_to_user?') do
+ (SiteSetting.assigns_public || scope.is_staff?) && object.assigned_to_user
+ end
+
+ add_to_serializer(:topic_view, :assigned_to_user, false) do
+ DiscourseAssign::Helpers.build_assigned_to_user(assigned_to_user_id, object.topic)
+ end
+
+ add_to_class(:topic_view_serializer, :assigned_to_user_id) do
+ id = object.topic.custom_fields["assigned_to_id"]
+ # a bit messy but race conditions can give us an array here, avoid
+ id && id.to_i rescue nil
+ end
+
+ add_to_serializer(:topic_view, 'include_assigned_to_user?') do
+ if SiteSetting.assigns_public || scope.is_staff?
+ # subtle but need to catch cases where stuff is not assigned
+ object.topic.custom_fields.keys.include?("assigned_to_id")
end
+ end
- require_dependency 'topic_view_serializer'
- class ::TopicViewSerializer
- attributes :assigned_to_user
+ add_to_serializer(:flagged_topic, :assigned_to_user) do
+ DiscourseAssign::Helpers.build_assigned_to_user(assigned_to_user_id, object)
+ end
- def assigned_to_user
- if assigned_to_user_id && user = User.find_by(id: assigned_to_user_id)
-
- assigned_at = TopicCustomField.where(
- topic_id: object.topic.id,
- name: "assigned_to_id"
- ).pluck(:created_at).first
-
- {
- username: user.username,
- name: user.name,
- avatar_template: user.avatar_template,
- assigned_at: assigned_at
- }
- end
- end
-
- def include_assigned_to_user?
- if SiteSetting.assigns_public || scope.is_staff?
- # subtle but need to catch cases where stuff is not assigned
- object.topic.custom_fields.keys.include?("assigned_to_id")
- end
- end
-
- def assigned_to_user_id
- id = object.topic.custom_fields["assigned_to_id"]
- # a bit messy but race conditions can give us an array here, avoid
- id && id.to_i rescue nil
- end
- end
-
- require_dependency 'topic_query'
- class ::TopicQuery
- def list_private_messages_assigned(user)
- list = private_messages_for(user, :all)
- list = list.where("topics.id IN (
- SELECT topic_id FROM topic_custom_fields WHERE name = 'assigned_to_id' AND value = ?
- )", user.id.to_s)
- create_list(:private_messages, {}, list)
- end
- end
-
- require_dependency 'list_controller'
- class ::ListController
- generate_message_route(:private_messages_assigned)
- end
-
- DiscourseAssign::Engine.routes.draw do
- put "/assign" => "assign#assign"
- put "/unassign" => "assign#unassign"
- get "/suggestions" => "assign#suggestions"
- end
-
- Discourse::Application.routes.append do
- mount ::DiscourseAssign::Engine, at: "/assign"
- get "topics/private-messages-assigned/:username" => "list#private_messages_assigned", as: "topics_private_messages_assigned", constraints: { username: /[\w.\-]+?/ }
- end
+ add_to_serializer(:flagged_topic, :assigned_to_user_id) do
+ id = object.custom_fields["assigned_to_id"]
+ # a bit messy but race conditions can give us an array here, avoid
+ id && id.to_i rescue nil
end
on(:post_created) do |post|