FEATURE: Add ability to claim topics on flagged topics page
This commit is contained in:
parent
99db4337fa
commit
7ca73b293a
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div class='assign-controls'>
|
||||||
|
{{flagged-topic-listener topic=topic}}
|
||||||
|
{{claim-topic topic=topic}}
|
||||||
|
</div>
|
|
@ -0,0 +1 @@
|
||||||
|
<th class='topic-assigned-to'>{{i18n "discourse_assign.assigned"}}</th>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<td>
|
||||||
|
{{claim-topic topic=topic}}
|
||||||
|
</td>
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default {
|
||||||
|
actions: {
|
||||||
|
claim(topic) {
|
||||||
|
console.log('claim:', topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{{flagged-topic-listener flaggedTopics=flaggedTopics}}
|
|
@ -6,6 +6,8 @@ import { observes } from 'ember-addons/ember-computed-decorators';
|
||||||
import Topic from 'discourse/models/topic';
|
import Topic from 'discourse/models/topic';
|
||||||
import TopicFooterDropdown from 'discourse/components/topic-footer-mobile-dropdown';
|
import TopicFooterDropdown from 'discourse/components/topic-footer-mobile-dropdown';
|
||||||
import showModal from 'discourse/lib/show-modal';
|
import showModal from 'discourse/lib/show-modal';
|
||||||
|
import { iconNode } from 'discourse-common/lib/icon-library';
|
||||||
|
import { h } from 'virtual-dom';
|
||||||
|
|
||||||
function initialize(api, container) {
|
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 => {
|
api.decorateWidget('post-contents:after-cooked', dec => {
|
||||||
if (dec.attrs.post_number === 1) {
|
if (dec.attrs.post_number === 1) {
|
||||||
const postModel = dec.getModel();
|
const postModel = dec.getModel();
|
||||||
if (postModel) {
|
if (postModel) {
|
||||||
const assignedToUser = postModel.get('topic.assigned_to_user');
|
const assignedToUser = postModel.get('topic.assigned_to_user');
|
||||||
if (assignedToUser) {
|
if (assignedToUser) {
|
||||||
const path = postModel.get('topic.assignedToUserPath');
|
return dec.widget.attach('assigned-to', {
|
||||||
const userLink = `<a href='${path}'>${assignedToUser.username}</a>`;
|
assignedToUser,
|
||||||
const html = I18n.t('discourse_assign.assign_html', {userLink});
|
href: postModel.get('topic.assignedToUserPath')
|
||||||
return dec.rawHtml(html);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
{{#if topic.assigned_to_user}}
|
||||||
|
<div class='assigned-to-user'>
|
||||||
|
{{avatar topic.assigned_to_user imageSize="small"}}
|
||||||
|
<span class='assigned-username'>
|
||||||
|
{{topic.assigned_to_user.username}}
|
||||||
|
</span>
|
||||||
|
{{d-button
|
||||||
|
icon="times"
|
||||||
|
class="btn-small unassign"
|
||||||
|
action=(action "unassign")
|
||||||
|
disabled=unassigning
|
||||||
|
title="discourse_assign.unassign.help"}}
|
||||||
|
</div>
|
||||||
|
{{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}}
|
|
@ -1,6 +1,29 @@
|
||||||
a.assigned-to .fa.fa-user-plus {
|
.assigned-to {
|
||||||
margin-right: 2px;
|
.d-icon, i.fa {
|
||||||
color: #999;
|
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 {
|
.list-tags.assigned {
|
||||||
|
|
|
@ -5,7 +5,7 @@ en:
|
||||||
unassigned: "unassigned %{who} %{when}"
|
unassigned: "unassigned %{who} %{when}"
|
||||||
discourse_assign:
|
discourse_assign:
|
||||||
assigned: "Assigned"
|
assigned: "Assigned"
|
||||||
assign_html: "<p class='assigned-to'><i class='fa-user-plus fa'></i> Assigned to {{userLink}}</p>"
|
assigned_to: "Assigned to"
|
||||||
assign_notification: "<i title='assigned' class='fa fa-user-plus'></i><p><span>{{username}}</span> {{description}}</p>"
|
assign_notification: "<i title='assigned' class='fa fa-user-plus'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||||
unassign:
|
unassign:
|
||||||
title: "Unassign"
|
title: "Unassign"
|
||||||
|
@ -17,3 +17,6 @@ en:
|
||||||
title: "Assign Topic"
|
title: "Assign Topic"
|
||||||
description: "Enter the username of the person you'd like to assign this topic"
|
description: "Enter the username of the person you'd like to assign this topic"
|
||||||
assign: "Assign"
|
assign: "Assign"
|
||||||
|
claim:
|
||||||
|
title: "claim"
|
||||||
|
help: "Assign topic to yourself"
|
||||||
|
|
|
@ -12,3 +12,4 @@ en:
|
||||||
discourse_assign:
|
discourse_assign:
|
||||||
assigned_to: "Topic assigned to @%{username}"
|
assigned_to: "Topic assigned to @%{username}"
|
||||||
unassigned: "Topic was unassigned"
|
unassigned: "Topic was unassigned"
|
||||||
|
already_claimed: "That topic has already been claimed."
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
module ::DiscourseAssign
|
||||||
|
class Engine < ::Rails::Engine
|
||||||
|
engine_name "discourse_assign"
|
||||||
|
isolate_namespace DiscourseAssign
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
|
@ -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 = <<SQL
|
||||||
|
SELECT p.topic_id, MAX(post_number) post_number
|
||||||
|
FROM posts p
|
||||||
|
JOIN topics t ON t.id = p.topic_id
|
||||||
|
LEFT JOIN topic_custom_fields tc ON tc.name = 'assigned_to_id' AND tc.topic_id = p.topic_id
|
||||||
|
WHERE p.user_id IN (SELECT id FROM users WHERE moderator OR admin) AND
|
||||||
|
( #{staff_mention} ) AND tc.value IS NULL AND NOT t.closed AND t.deleted_at IS NULL
|
||||||
|
GROUP BY p.topic_id
|
||||||
|
SQL
|
||||||
|
|
||||||
|
assigned = 0
|
||||||
|
puts
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.raw_connection.exec(sql).to_a.each do |row|
|
||||||
|
post = Post.find_by(post_number: row["post_number"].to_i,
|
||||||
|
topic_id: row["topic_id"].to_i)
|
||||||
|
assigned += 1 if post && auto_assign(post)
|
||||||
|
putc "."
|
||||||
|
end
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts "#{assigned} topics where automatically assigned to staff members"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.assign_self_passes?(post)
|
||||||
|
return false unless SiteSetting.assign_self_regex.present?
|
||||||
|
regex = Regexp.new(SiteSetting.assign_self_regex) rescue nil
|
||||||
|
|
||||||
|
!!(regex && regex.match(post.raw))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.assign_other_passes?(post)
|
||||||
|
return true unless SiteSetting.assign_other_regex.present?
|
||||||
|
regex = Regexp.new(SiteSetting.assign_other_regex) rescue nil
|
||||||
|
|
||||||
|
!!(regex && regex.match(post.raw))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.auto_assign(post, force: false)
|
||||||
|
|
||||||
|
if SiteSetting.unassign_on_close && post.topic && post.topic.closed
|
||||||
|
assigner = new(post.topic, Discourse.system_user)
|
||||||
|
assigner.unassign(silent: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless SiteSetting.assigns_by_staff_mention
|
||||||
|
|
||||||
|
if post.user && post.topic && post.user.staff?
|
||||||
|
can_assign = force || post.topic.custom_fields["assigned_to_id"].nil?
|
||||||
|
|
||||||
|
assign_other = assign_other_passes?(post) && mentioned_staff(post)
|
||||||
|
assign_self = assign_self_passes?(post) && post.user
|
||||||
|
|
||||||
|
if can_assign && is_last_staff_post?(post)
|
||||||
|
assigner = new(post.topic, post.user)
|
||||||
|
if assign_other
|
||||||
|
assigner.assign(assign_other, silent: true)
|
||||||
|
elsif assign_self
|
||||||
|
assigner.assign(assign_self, silent: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.is_last_staff_post?(post)
|
||||||
|
Post.exec_sql("SELECT 1 FROM posts p
|
||||||
|
JOIN users u ON u.id = p.user_id AND (moderator OR admin)
|
||||||
|
WHERE p.deleted_at IS NULL AND p.topic_id = :topic_id
|
||||||
|
having max(post_number) = :post_number
|
||||||
|
",
|
||||||
|
topic_id: post.topic_id,
|
||||||
|
post_number: post.post_number
|
||||||
|
).to_a.length == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.mentioned_staff(post)
|
||||||
|
mentions = post.raw_mentions
|
||||||
|
if mentions.present?
|
||||||
|
User.where('moderator OR admin')
|
||||||
|
.where('username_lower IN (?)', mentions.map(&:downcase))
|
||||||
|
.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(topic, user)
|
||||||
|
@assigned_by = user
|
||||||
|
@topic = topic
|
||||||
|
end
|
||||||
|
|
||||||
|
def staff_ids
|
||||||
|
User.real.staff.pluck(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def assign(assign_to, silent: false)
|
||||||
|
@topic.custom_fields["assigned_to_id"] = assign_to.id
|
||||||
|
@topic.custom_fields["assigned_by_id"] = @assigned_by.id
|
||||||
|
@topic.save!
|
||||||
|
|
||||||
|
first_post = @topic.posts.find_by(post_number: 1)
|
||||||
|
first_post.publish_change_to_clients!(:revised, reload_topic: true)
|
||||||
|
|
||||||
|
MessageBus.publish(
|
||||||
|
"/staff/topic-assignment",
|
||||||
|
{
|
||||||
|
type: 'assigned',
|
||||||
|
topic_id: @topic.id,
|
||||||
|
assigned_to: BasicUserSerializer.new(assign_to, root: false).as_json
|
||||||
|
},
|
||||||
|
user_ids: staff_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
UserAction.log_action!(
|
||||||
|
action_type: UserAction::ASSIGNED,
|
||||||
|
user_id: assign_to.id,
|
||||||
|
acting_user_id: @assigned_by.id,
|
||||||
|
target_post_id: first_post.id,
|
||||||
|
target_topic_id: @topic.id
|
||||||
|
)
|
||||||
|
|
||||||
|
post_type = SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper]
|
||||||
|
|
||||||
|
unless silent
|
||||||
|
@topic.add_moderator_post(
|
||||||
|
@assigned_by,
|
||||||
|
nil,
|
||||||
|
bump: false,
|
||||||
|
post_type: post_type,
|
||||||
|
action_code: "assigned",
|
||||||
|
custom_fields: { "action_code_who" => 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
|
470
plugin.rb
470
plugin.rb
|
@ -6,392 +6,146 @@
|
||||||
enabled_site_setting :assign_enabled
|
enabled_site_setting :assign_enabled
|
||||||
|
|
||||||
register_asset 'stylesheets/assigns.scss'
|
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
|
after_initialize do
|
||||||
|
require 'topic_assigner'
|
||||||
|
|
||||||
module ::DiscourseAssign
|
# We can remove this check once this method is stable
|
||||||
class Engine < ::Rails::Engine
|
if respond_to?(:add_preloaded_topic_list_custom_field)
|
||||||
engine_name "discourse_assign"
|
add_preloaded_topic_list_custom_field('assigned_to_id')
|
||||||
isolate_namespace DiscourseAssign
|
else
|
||||||
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 = <<SQL
|
|
||||||
SELECT p.topic_id, MAX(post_number) post_number
|
|
||||||
FROM posts p
|
|
||||||
JOIN topics t ON t.id = p.topic_id
|
|
||||||
LEFT JOIN topic_custom_fields tc ON tc.name = 'assigned_to_id' AND tc.topic_id = p.topic_id
|
|
||||||
WHERE p.user_id IN (SELECT id FROM users WHERE moderator OR admin) AND
|
|
||||||
( #{staff_mention} ) AND tc.value IS NULL AND NOT t.closed AND t.deleted_at IS NULL
|
|
||||||
GROUP BY p.topic_id
|
|
||||||
SQL
|
|
||||||
|
|
||||||
assigned = 0
|
|
||||||
puts
|
|
||||||
|
|
||||||
ActiveRecord::Base.connection.raw_connection.exec(sql).to_a.each do |row|
|
|
||||||
post = Post.find_by(post_number: row["post_number"].to_i,
|
|
||||||
topic_id: row["topic_id"].to_i)
|
|
||||||
assigned += 1 if post && auto_assign(post)
|
|
||||||
putc "."
|
|
||||||
end
|
|
||||||
|
|
||||||
puts
|
|
||||||
puts "#{assigned} topics where automatically assigned to staff members"
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.assign_self_passes?(post)
|
|
||||||
return false unless SiteSetting.assign_self_regex.present?
|
|
||||||
regex = Regexp.new(SiteSetting.assign_self_regex) rescue nil
|
|
||||||
|
|
||||||
!!(regex && regex.match(post.raw))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.assign_other_passes?(post)
|
|
||||||
return true unless SiteSetting.assign_other_regex.present?
|
|
||||||
regex = Regexp.new(SiteSetting.assign_other_regex) rescue nil
|
|
||||||
|
|
||||||
!!(regex && regex.match(post.raw))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.auto_assign(post, force: false)
|
|
||||||
|
|
||||||
if SiteSetting.unassign_on_close && post.topic && post.topic.closed
|
|
||||||
assigner = new(post.topic, Discourse.system_user)
|
|
||||||
assigner.unassign(silent: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
return unless SiteSetting.assigns_by_staff_mention
|
|
||||||
|
|
||||||
if post.user && post.topic && post.user.staff?
|
|
||||||
can_assign = force || post.topic.custom_fields["assigned_to_id"].nil?
|
|
||||||
|
|
||||||
assign_other = assign_other_passes?(post) && mentioned_staff(post)
|
|
||||||
assign_self = assign_self_passes?(post) && post.user
|
|
||||||
|
|
||||||
if can_assign && is_last_staff_post?(post)
|
|
||||||
assigner = new(post.topic, post.user)
|
|
||||||
if assign_other
|
|
||||||
assigner.assign(assign_other, silent: true)
|
|
||||||
elsif assign_self
|
|
||||||
assigner.assign(assign_self, silent: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.is_last_staff_post?(post)
|
|
||||||
Post.exec_sql("SELECT 1 FROM posts p
|
|
||||||
JOIN users u ON u.id = p.user_id AND (moderator OR admin)
|
|
||||||
WHERE p.deleted_at IS NULL AND p.topic_id = :topic_id
|
|
||||||
having max(post_number) = :post_number
|
|
||||||
",
|
|
||||||
topic_id: post.topic_id,
|
|
||||||
post_number: post.post_number
|
|
||||||
).to_a.length == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.mentioned_staff(post)
|
|
||||||
mentions = post.raw_mentions
|
|
||||||
if mentions.present?
|
|
||||||
User.where('moderator OR admin')
|
|
||||||
.where('username_lower IN (?)', mentions.map(&:downcase))
|
|
||||||
.first
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(topic, user)
|
|
||||||
@assigned_by = user
|
|
||||||
@topic = topic
|
|
||||||
end
|
|
||||||
|
|
||||||
def assign(assign_to, silent: false)
|
|
||||||
@topic.custom_fields["assigned_to_id"] = assign_to.id
|
|
||||||
@topic.custom_fields["assigned_by_id"] = @assigned_by.id
|
|
||||||
@topic.save!
|
|
||||||
|
|
||||||
first_post = @topic.posts.find_by(post_number: 1)
|
|
||||||
first_post.publish_change_to_clients!(:revised,
|
|
||||||
{ reload_topic: true })
|
|
||||||
|
|
||||||
|
|
||||||
UserAction.log_action!(action_type: UserAction::ASSIGNED,
|
|
||||||
user_id: assign_to.id,
|
|
||||||
acting_user_id: @assigned_by.id,
|
|
||||||
target_post_id: first_post.id,
|
|
||||||
target_topic_id: @topic.id)
|
|
||||||
|
|
||||||
post_type = SiteSetting.assigns_public ? Post.types[:small_action] : Post.types[:whisper]
|
|
||||||
|
|
||||||
unless silent
|
|
||||||
@topic.add_moderator_post(@assigned_by, nil,
|
|
||||||
{ bump: false,
|
|
||||||
post_type: post_type,
|
|
||||||
action_code: "assigned",
|
|
||||||
custom_fields: {"action_code_who" => 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
|
|
||||||
|
|
||||||
TopicList.preloaded_custom_fields << "assigned_to_id"
|
TopicList.preloaded_custom_fields << "assigned_to_id"
|
||||||
|
end
|
||||||
|
|
||||||
TopicList.on_preload do |topics, topic_list|
|
TopicList.on_preload do |topics, topic_list|
|
||||||
is_staff = topic_list.current_user && topic_list.current_user.staff?
|
is_staff = topic_list.current_user && topic_list.current_user.staff?
|
||||||
allowed_access = SiteSetting.assigns_public || is_staff
|
allowed_access = SiteSetting.assigns_public || is_staff
|
||||||
|
|
||||||
if allowed_access && topics.length > 0
|
if allowed_access && topics.length > 0
|
||||||
users = User.where("users.id in (
|
users = User.where("users.id in (
|
||||||
SELECT value::int
|
SELECT value::int
|
||||||
FROM topic_custom_fields
|
FROM topic_custom_fields
|
||||||
WHERE name = 'assigned_to_id' AND topic_id IN (?)
|
WHERE name = 'assigned_to_id' AND topic_id IN (?)
|
||||||
)", topics.map(&:id))
|
)", topics.map(&:id))
|
||||||
.joins('join user_emails on user_emails.user_id = users.id AND user_emails.primary')
|
.joins('join user_emails on user_emails.user_id = users.id AND user_emails.primary')
|
||||||
.select(:id, 'user_emails.email', :username, :uploaded_avatar_id)
|
.select(:id, 'user_emails.email', :username, :uploaded_avatar_id)
|
||||||
|
|
||||||
map = {}
|
map = {}
|
||||||
users.each { |u| map[u.id] = u }
|
users.each { |u| map[u.id] = u }
|
||||||
|
|
||||||
topics.each do |t|
|
topics.each do |t|
|
||||||
if id = t.custom_fields['assigned_to_id']
|
if id = t.custom_fields['assigned_to_id']
|
||||||
t.preload_assigned_to_user(map[id.to_i])
|
t.preload_assigned_to_user(map[id.to_i])
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
require_dependency 'topic_query'
|
require_dependency 'topic_query'
|
||||||
TopicQuery.add_custom_filter(:assigned) do |results, topic_query|
|
TopicQuery.add_custom_filter(:assigned) do |results, topic_query|
|
||||||
if topic_query.guardian.is_staff? || SiteSetting.assigns_public
|
if topic_query.guardian.is_staff? || SiteSetting.assigns_public
|
||||||
username = topic_query.options[:assigned]
|
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
|
if username.present? && !special
|
||||||
user_id ||= User.where(username_lower: username.downcase).pluck(:id).first
|
user_id ||= User.where(username_lower: username.downcase).pluck(:id).first
|
||||||
end
|
end
|
||||||
|
|
||||||
if user_id || special
|
if user_id || special
|
||||||
|
|
||||||
if username == "nobody"
|
if username == "nobody"
|
||||||
results = results.joins("LEFT JOIN topic_custom_fields tc_assign ON
|
results = results.joins("LEFT JOIN topic_custom_fields tc_assign ON
|
||||||
topics.id = tc_assign.topic_id AND
|
topics.id = tc_assign.topic_id AND
|
||||||
tc_assign.name = 'assigned_to_id'")
|
tc_assign.name = 'assigned_to_id'")
|
||||||
.where("tc_assign.name IS NULL")
|
.where("tc_assign.name IS NULL")
|
||||||
|
else
|
||||||
|
|
||||||
|
if username == "*"
|
||||||
|
filter = "AND tc_assign.value IS NOT NULL"
|
||||||
else
|
else
|
||||||
|
filter = "AND tc_assign.value = '#{user_id.to_i.to_s}'"
|
||||||
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}
|
|
||||||
")
|
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
results
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require_dependency 'topic_list_item_serializer'
|
results
|
||||||
class ::TopicListItemSerializer
|
end
|
||||||
has_one :assigned_to_user, serializer: BasicUserSerializer, embed: :objects
|
|
||||||
|
|
||||||
def include_assigned_to_user?
|
require_dependency 'topic_list_item_serializer'
|
||||||
(SiteSetting.assigns_public || scope.is_staff?) && object.assigned_to_user
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
require_dependency 'topic_view_serializer'
|
add_to_serializer(:flagged_topic, :assigned_to_user) do
|
||||||
class ::TopicViewSerializer
|
DiscourseAssign::Helpers.build_assigned_to_user(assigned_to_user_id, object)
|
||||||
attributes :assigned_to_user
|
end
|
||||||
|
|
||||||
def assigned_to_user
|
add_to_serializer(:flagged_topic, :assigned_to_user_id) do
|
||||||
if assigned_to_user_id && user = User.find_by(id: assigned_to_user_id)
|
id = object.custom_fields["assigned_to_id"]
|
||||||
|
# a bit messy but race conditions can give us an array here, avoid
|
||||||
assigned_at = TopicCustomField.where(
|
id && id.to_i rescue nil
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:post_created) do |post|
|
on(:post_created) do |post|
|
||||||
|
|
Loading…
Reference in New Issue