mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 22:26:26 +08:00
FEATURE: View flags grouped by topic
This commit is contained in:
parent
bbbd974487
commit
40eba8cd93
10
app/assets/javascripts/admin/components/flag-counts.js.es6
Normal file
10
app/assets/javascripts/admin/components/flag-counts.js.es6
Normal file
|
@ -0,0 +1,10 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['flag-counts'],
|
||||
|
||||
@computed('details.flag_type_id')
|
||||
title(id) {
|
||||
return I18n.t(`admin.flags.summary.action_type_${id}`, { count: 1 });
|
||||
}
|
||||
});
|
10
app/assets/javascripts/admin/models/flag-type.js.es6
Normal file
10
app/assets/javascripts/admin/models/flag-type.js.es6
Normal file
|
@ -0,0 +1,10 @@
|
|||
import RestModel from 'discourse/models/rest';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default RestModel.extend({
|
||||
@computed('id')
|
||||
name(id) {
|
||||
return I18n.t(`admin.flags.summary.action_type_${id}`, { count: 1});
|
||||
}
|
||||
});
|
||||
|
|
@ -3,37 +3,35 @@ import AdminUser from 'admin/models/admin-user';
|
|||
import Topic from 'discourse/models/topic';
|
||||
import Post from 'discourse/models/post';
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
const FlaggedPost = Post.extend({
|
||||
|
||||
summary: function () {
|
||||
@computed
|
||||
summary() {
|
||||
return _(this.post_actions)
|
||||
.groupBy(function (a) { return a.post_action_type_id; })
|
||||
.map(function (v,k) { return I18n.t('admin.flags.summary.action_type_' + k, { count: v.length }); })
|
||||
.join(',');
|
||||
}.property(),
|
||||
},
|
||||
|
||||
flaggers: function () {
|
||||
var self = this;
|
||||
var flaggers = [];
|
||||
|
||||
_.each(this.post_actions, function (postAction) {
|
||||
flaggers.push({
|
||||
user: self.userLookup[postAction.user_id],
|
||||
topic: self.topicLookup[postAction.topic_id],
|
||||
@computed
|
||||
flaggers() {
|
||||
return this.post_actions.map(postAction => {
|
||||
return {
|
||||
user: this.userLookup[postAction.user_id],
|
||||
topic: this.topicLookup[postAction.topic_id],
|
||||
flagType: I18n.t('admin.flags.summary.action_type_' + postAction.post_action_type_id, { count: 1 }),
|
||||
flaggedAt: postAction.created_at,
|
||||
disposedBy: postAction.disposed_by_id ? self.userLookup[postAction.disposed_by_id] : null,
|
||||
disposedBy: postAction.disposed_by_id ? this.userLookup[postAction.disposed_by_id] : null,
|
||||
disposedAt: postAction.disposed_at,
|
||||
dispositionIcon: self.dispositionIcon(postAction.disposition),
|
||||
dispositionIcon: this.dispositionIcon(postAction.disposition),
|
||||
tookAction: postAction.staff_took_action
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
return flaggers;
|
||||
}.property(),
|
||||
|
||||
dispositionIcon: function (disposition) {
|
||||
dispositionIcon(disposition) {
|
||||
if (!disposition) { return null; }
|
||||
let icon;
|
||||
let title = 'admin.flags.dispositions.' + disposition;
|
||||
|
@ -45,68 +43,74 @@ const FlaggedPost = Post.extend({
|
|||
return iconHTML(icon, { title });
|
||||
},
|
||||
|
||||
wasEdited: function () {
|
||||
@computed('last_revised_at', 'post_actions.@each.created_at')
|
||||
wasEdited(lastRevisedAt) {
|
||||
if (Ember.isEmpty(this.get("last_revised_at"))) { return false; }
|
||||
var lastRevisedAt = Date.parse(this.get("last_revised_at"));
|
||||
lastRevisedAt = Date.parse(lastRevisedAt);
|
||||
return _.some(this.get("post_actions"), function (postAction) {
|
||||
return Date.parse(postAction.created_at) < lastRevisedAt;
|
||||
});
|
||||
}.property("last_revised_at", "post_actions.@each.created_at"),
|
||||
},
|
||||
|
||||
conversations: function () {
|
||||
var self = this;
|
||||
var conversations = [];
|
||||
@computed
|
||||
conversations() {
|
||||
let conversations = [];
|
||||
|
||||
_.each(this.post_actions, function (postAction) {
|
||||
this.post_actions.forEach(postAction => {
|
||||
if (postAction.conversation) {
|
||||
var conversation = {
|
||||
let conversation = {
|
||||
permalink: postAction.permalink,
|
||||
hasMore: postAction.conversation.has_more,
|
||||
response: {
|
||||
excerpt: postAction.conversation.response.excerpt,
|
||||
user: self.userLookup[postAction.conversation.response.user_id]
|
||||
user: this.userLookup[postAction.conversation.response.user_id]
|
||||
}
|
||||
};
|
||||
|
||||
if (postAction.conversation.reply) {
|
||||
conversation["reply"] = {
|
||||
conversation.reply = {
|
||||
excerpt: postAction.conversation.reply.excerpt,
|
||||
user: self.userLookup[postAction.conversation.reply.user_id]
|
||||
user: this.userLookup[postAction.conversation.reply.user_id]
|
||||
};
|
||||
}
|
||||
|
||||
conversations.push(conversation);
|
||||
}
|
||||
});
|
||||
|
||||
return conversations;
|
||||
}.property(),
|
||||
},
|
||||
|
||||
user: function() {
|
||||
@computed
|
||||
user() {
|
||||
return this.userLookup[this.user_id];
|
||||
}.property(),
|
||||
},
|
||||
|
||||
topic: function () {
|
||||
@computed
|
||||
topic() {
|
||||
return this.topicLookup[this.topic_id];
|
||||
}.property(),
|
||||
},
|
||||
|
||||
flaggedForSpam: function() {
|
||||
@computed('post_actions.@each.name_key')
|
||||
flaggedForSpam() {
|
||||
return !_.every(this.get('post_actions'), function(action) { return action.name_key !== 'spam'; });
|
||||
}.property('post_actions.@each.name_key'),
|
||||
},
|
||||
|
||||
topicFlagged: function() {
|
||||
@computed('post_actions.@each.targets_topic')
|
||||
topicFlagged() {
|
||||
return _.any(this.get('post_actions'), function(action) { return action.targets_topic; });
|
||||
}.property('post_actions.@each.targets_topic'),
|
||||
},
|
||||
|
||||
postAuthorFlagged: function() {
|
||||
@computed('post_actions.@each.targets_topic')
|
||||
postAuthorFlagged() {
|
||||
return _.any(this.get('post_actions'), function(action) { return !action.targets_topic; });
|
||||
}.property('post_actions.@each.targets_topic'),
|
||||
},
|
||||
|
||||
canDeleteAsSpammer: function() {
|
||||
return Discourse.User.currentProp('staff') && this.get('flaggedForSpam') && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted');
|
||||
}.property('flaggedForSpam'),
|
||||
@computed('flaggedForSpan')
|
||||
canDeleteAsSpammer(flaggedForSpam) {
|
||||
return Discourse.User.currentProp('staff') && flaggedForSpam && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted');
|
||||
},
|
||||
|
||||
deletePost: function() {
|
||||
deletePost() {
|
||||
if (this.get('post_number') === 1) {
|
||||
return ajax('/t/' + this.topic_id, { type: 'DELETE', cache: false });
|
||||
} else {
|
||||
|
@ -114,61 +118,58 @@ const FlaggedPost = Post.extend({
|
|||
}
|
||||
},
|
||||
|
||||
disagreeFlags: function () {
|
||||
disagreeFlags() {
|
||||
return ajax('/admin/flags/disagree/' + this.id, { type: 'POST', cache: false });
|
||||
},
|
||||
|
||||
deferFlags: function (deletePost) {
|
||||
deferFlags(deletePost) {
|
||||
return ajax('/admin/flags/defer/' + this.id, { type: 'POST', cache: false, data: { delete_post: deletePost } });
|
||||
},
|
||||
|
||||
agreeFlags: function (actionOnPost) {
|
||||
agreeFlags(actionOnPost) {
|
||||
return ajax('/admin/flags/agree/' + this.id, { type: 'POST', cache: false, data: { action_on_post: actionOnPost } });
|
||||
},
|
||||
|
||||
postHidden: Em.computed.alias('hidden'),
|
||||
postHidden: Ember.computed.alias('hidden'),
|
||||
|
||||
extraClasses: function() {
|
||||
var classes = [];
|
||||
@computed
|
||||
extraClasses() {
|
||||
let classes = [];
|
||||
if (this.get('hidden')) { classes.push('hidden-post'); }
|
||||
if (this.get('deleted')) { classes.push('deleted'); }
|
||||
return classes.join(' ');
|
||||
}.property(),
|
||||
|
||||
deleted: Em.computed.or('deleted_at', 'topic_deleted_at')
|
||||
},
|
||||
|
||||
deleted: Ember.computed.or('deleted_at', 'topic_deleted_at')
|
||||
});
|
||||
|
||||
FlaggedPost.reopenClass({
|
||||
findAll: function (filter, offset) {
|
||||
|
||||
findAll(args) {
|
||||
let { offset, filter } = args;
|
||||
offset = offset || 0;
|
||||
|
||||
var result = Em.A();
|
||||
let result = [];
|
||||
result.set('loading', true);
|
||||
|
||||
return ajax('/admin/flags/' + filter + '.json?offset=' + offset).then(function (data) {
|
||||
// users
|
||||
var userLookup = {};
|
||||
_.each(data.users, function (user) {
|
||||
userLookup[user.id] = AdminUser.create(user);
|
||||
});
|
||||
let userLookup = {};
|
||||
data.users.forEach(user => userLookup[user.id] = AdminUser.create(user));
|
||||
|
||||
// topics
|
||||
var topicLookup = {};
|
||||
_.each(data.topics, function (topic) {
|
||||
topicLookup[topic.id] = Topic.create(topic);
|
||||
});
|
||||
let topicLookup = {};
|
||||
data.topics.forEach(topic => topicLookup[topic.id] = Topic.create(topic));
|
||||
|
||||
// posts
|
||||
_.each(data.posts, function (post) {
|
||||
var f = FlaggedPost.create(post);
|
||||
data.posts.forEach(post => {
|
||||
let f = FlaggedPost.create(post);
|
||||
f.userLookup = userLookup;
|
||||
f.topicLookup = topicLookup;
|
||||
result.pushObject(f);
|
||||
});
|
||||
|
||||
result.set('loading', false);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return this.store.findAll('flagged-topic');
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set('flaggedTopics', model);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { loadTopicView } from 'discourse/models/topic';
|
||||
import FlaggedPost from 'admin/models/flagged-post';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
model(params) {
|
||||
let topicRecord = this.store.createRecord('topic', { id: params.id });
|
||||
let topic = loadTopicView(topicRecord).then(() => topicRecord);
|
||||
|
||||
return Ember.RSVP.hash({
|
||||
topic,
|
||||
flaggedPosts: FlaggedPost.findAll({ filter: 'active' })
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, hash) {
|
||||
controller.setProperties(hash);
|
||||
}
|
||||
});
|
|
@ -56,6 +56,9 @@ export default function() {
|
|||
this.route('adminFlags', { path: '/flags', resetNamespace: true }, function() {
|
||||
this.route('postsActive', { path: 'active' });
|
||||
this.route('postsOld', { path: 'old' });
|
||||
this.route('topics', { path: 'topics' }, function() {
|
||||
this.route('show', { path: ":id" });
|
||||
});
|
||||
});
|
||||
|
||||
this.route('adminLogs', { path: '/logs', resetNamespace: true }, function() {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<span class='type-name'>{{title}}</span>
|
||||
<span class='type-count'>x{{details.count}}</span>
|
|
@ -0,0 +1,5 @@
|
|||
{{#each users as |u|}}
|
||||
{{#link-to 'adminUser' u}}
|
||||
{{avatar u imageSize="small"}}
|
||||
{{/link-to}}
|
||||
{{/each}}
|
|
@ -0,0 +1,42 @@
|
|||
{{plugin-outlet name="flagged-topics-before" noTags=true args=(hash flaggedTopics=flaggedTopics)}}
|
||||
|
||||
<table class='flagged-topics'>
|
||||
<thead>
|
||||
{{plugin-outlet name="flagged-topic-header-row" noTags=true}}
|
||||
<th>{{i18n "admin.flags.flagged_topics.topic"}} </th>
|
||||
<th>{{i18n "admin.flags.flagged_topics.type"}}</th>
|
||||
<th>{{I18n "admin.flags.flagged_topics.users"}}</th>
|
||||
<th>{{i18n "admin.flags.flagged_topics.last_flagged"}}</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
|
||||
{{#each flaggedTopics as |ft|}}
|
||||
<tr class='flagged-topic'>
|
||||
{{plugin-outlet name="flagged-topic-row" noTags=true args=(hash topic=ft.topic)}}
|
||||
|
||||
<td class="topic-title">
|
||||
<a href={{ft.topic.relative_url}} target="_blank">{{replace-emoji ft.topic.fancy_title}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{#each ft.flag_counts as |fc|}}
|
||||
{{flag-counts details=fc}}
|
||||
{{/each}}
|
||||
</td>
|
||||
<td>
|
||||
{{flagged-topic-users users=ft.users tagName=""}}
|
||||
</td>
|
||||
<td>
|
||||
{{format-age ft.last_flag_at}}
|
||||
</td>
|
||||
<td>
|
||||
{{#link-to
|
||||
"adminFlags.topics.show"
|
||||
ft.id
|
||||
class="btn d-button no-text btn-small btn-primary"
|
||||
title=(i18n "admin.flags.show_details")}}
|
||||
{{d-icon "search"}}
|
||||
{{/link-to}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
11
app/assets/javascripts/admin/templates/flags-topics-show.hbs
Normal file
11
app/assets/javascripts/admin/templates/flags-topics-show.hbs
Normal file
|
@ -0,0 +1,11 @@
|
|||
<div class='flagged-topic-details'>
|
||||
<div class='topic-title'>
|
||||
{{topic-status topic=topic}}
|
||||
<h1>{{{topic.fancyTitle}}}</h1>
|
||||
</div>
|
||||
|
||||
{{plugin-outlet name="flagged-topic-details-header" args=(hash topic=topic)}}
|
||||
</div>
|
||||
<div class='topic-flags'>
|
||||
{{flagged-posts flaggedPosts=flaggedPosts filter="active"}}
|
||||
</div>
|
|
@ -1,6 +1,7 @@
|
|||
{{#admin-nav}}
|
||||
{{nav-item route='adminFlags.postsActive' label='admin.flags.active'}}
|
||||
{{nav-item route='adminFlags.postsOld' label='admin.flags.old'}}
|
||||
{{nav-item route='adminFlags.postsActive' label='admin.flags.active_posts'}}
|
||||
{{nav-item route='adminFlags.topics' label='admin.flags.topics'}}
|
||||
{{nav-item route='adminFlags.postsOld' label='admin.flags.old_posts' class='right'}}
|
||||
{{/admin-nav}}
|
||||
|
||||
<div class="admin-container">
|
||||
|
|
|
@ -194,7 +194,6 @@ export function buildResolver(baseName) {
|
|||
// (similar to how discourse lays out templates)
|
||||
findAdminTemplate(parsedName) {
|
||||
var decamelized = parsedName.fullNameWithoutType.decamelize();
|
||||
|
||||
if (decamelized.indexOf('components') === 0) {
|
||||
const compTemplate = Ember.TEMPLATES['admin/templates/' + decamelized];
|
||||
if (compTemplate) { return compTemplate; }
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { hashString } from 'discourse/lib/hash';
|
||||
|
||||
const ADMIN_MODELS = ['plugin', 'theme', 'embeddable-host', 'web-hook', 'web-hook-event'];
|
||||
const ADMIN_MODELS = [
|
||||
'plugin',
|
||||
'theme',
|
||||
'embeddable-host',
|
||||
'web-hook',
|
||||
'web-hook-event',
|
||||
'flagged-topic'
|
||||
];
|
||||
|
||||
export function Result(payload, responseJson) {
|
||||
this.payload = payload;
|
||||
|
@ -19,7 +26,6 @@ function rethrow(error) {
|
|||
|
||||
export default Ember.Object.extend({
|
||||
|
||||
|
||||
storageKey(type, findArgs, options) {
|
||||
if (options && options.cacheKey) {
|
||||
return options.cacheKey;
|
||||
|
|
|
@ -36,6 +36,12 @@ export default Ember.Component.extend({
|
|||
connectors: null,
|
||||
|
||||
init() {
|
||||
// This should be the future default
|
||||
if (this.get('noTags')) {
|
||||
this.set('tagName', '');
|
||||
this.set('connectorTagName', '');
|
||||
}
|
||||
|
||||
this._super();
|
||||
const name = this.get('name');
|
||||
if (name) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@import "common/foundation/helpers";
|
||||
|
||||
@import "common/admin/customize";
|
||||
@import "common/admin/flagged_topics";
|
||||
|
||||
$mobile-breakpoint: 700px;
|
||||
|
||||
|
|
28
app/assets/stylesheets/common/admin/flagged_topics.scss
Normal file
28
app/assets/stylesheets/common/admin/flagged_topics.scss
Normal file
|
@ -0,0 +1,28 @@
|
|||
.flag-counts {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
|
||||
.type-count {
|
||||
color: $primary-medium;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
.flagged-topic {
|
||||
td.topic-title {
|
||||
width: 400px;
|
||||
a {
|
||||
width: 400px;
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flagged-topic-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
14
app/controllers/admin/flagged_topics_controller.rb
Normal file
14
app/controllers/admin/flagged_topics_controller.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
require_dependency 'flag_query'
|
||||
|
||||
class Admin::FlaggedTopicsController < Admin::AdminController
|
||||
|
||||
def index
|
||||
result = FlagQuery.flagged_topics
|
||||
|
||||
render_json_dump({
|
||||
flagged_topics: serialize_data(result[:flagged_topics], FlaggedTopicSummarySerializer),
|
||||
users: serialize_data(result[:users], BasicUserSerializer),
|
||||
}, rest_serializer: true)
|
||||
end
|
||||
|
||||
end
|
|
@ -490,7 +490,7 @@ class ApplicationController < ActionController::Base
|
|||
# status - HTTP status code to return
|
||||
def render_json_error(obj, opts = {})
|
||||
opts = { status: opts } if opts.is_a?(Integer)
|
||||
render json: MultiJson.dump(create_errors_json(obj, opts[:type])), status: opts[:status] || 422
|
||||
render json: MultiJson.dump(create_errors_json(obj, opts)), status: opts[:status] || 422
|
||||
end
|
||||
|
||||
def success_json
|
||||
|
|
|
@ -132,6 +132,7 @@ module HasCustomFields
|
|||
if @preloaded.key?(key)
|
||||
@preloaded[key]
|
||||
else
|
||||
raise "#{@preloaded.inspect} -> #{key.inspect}"
|
||||
# for now you can not mix preload an non preload, it better just to fail
|
||||
raise StandardError, "Attempting to access a non preloaded custom field, this is disallowed to prevent N+1 queries."
|
||||
end
|
||||
|
|
29
app/serializers/flagged_topic_summary_serializer.rb
Normal file
29
app/serializers/flagged_topic_summary_serializer.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
class FlaggedTopicSummarySerializer < ActiveModel::Serializer
|
||||
|
||||
attributes(
|
||||
:id,
|
||||
:flag_counts,
|
||||
:user_ids,
|
||||
:last_flag_at
|
||||
)
|
||||
|
||||
has_one :topic, serializer: FlaggedTopicSerializer
|
||||
|
||||
def id
|
||||
topic.id
|
||||
end
|
||||
|
||||
def flag_counts
|
||||
object.flag_counts.map do |k, v|
|
||||
{ flag_type_id: k, count: v }
|
||||
end
|
||||
end
|
||||
|
||||
def user_ids
|
||||
object.user_ids
|
||||
end
|
||||
|
||||
def last_flag_at
|
||||
object.last_flag_at
|
||||
end
|
||||
end
|
|
@ -2596,8 +2596,9 @@ en:
|
|||
|
||||
flags:
|
||||
title: "Flags"
|
||||
old: "Old"
|
||||
active: "Active"
|
||||
active_posts: "Flagged Posts"
|
||||
old_posts: "Old Flagged Posts"
|
||||
topics: "Flagged Topics"
|
||||
|
||||
agree: "Agree"
|
||||
agree_title: "Confirm this flag as valid and correct"
|
||||
|
@ -2638,11 +2639,18 @@ en:
|
|||
system: "System"
|
||||
error: "Something went wrong"
|
||||
reply_message: "Reply"
|
||||
no_results: "There are no flags."
|
||||
no_results: "There are no flaged posts."
|
||||
topic_flagged: "This <strong>topic</strong> has been flagged."
|
||||
visit_topic: "Visit the topic to take action"
|
||||
was_edited: "Post was edited after the first flag"
|
||||
previous_flags_count: "This post has already been flagged {{count}} times."
|
||||
show_details: "Show flag details"
|
||||
|
||||
flagged_topics:
|
||||
topic: "Topic"
|
||||
type: "Type"
|
||||
users: "Users"
|
||||
last_flagged: "Last Flagged"
|
||||
|
||||
summary:
|
||||
action_type_3:
|
||||
|
|
|
@ -188,11 +188,14 @@ Discourse::Application.routes.draw do
|
|||
|
||||
get "flags" => "flags#index"
|
||||
get "flags/:filter" => "flags#index"
|
||||
get "flags/topics/:topic_id" => "flags#index"
|
||||
post "flags/agree/:id" => "flags#agree"
|
||||
post "flags/disagree/:id" => "flags#disagree"
|
||||
post "flags/defer/:id" => "flags#defer"
|
||||
|
||||
resources :flagged_topics, constraints: AdminConstraint.new
|
||||
resources :themes, constraints: AdminConstraint.new
|
||||
|
||||
post "themes/import" => "themes#import"
|
||||
post "themes/upload_asset" => "themes#upload_asset"
|
||||
get "themes/:id/preview" => "themes#preview"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'ostruct'
|
||||
|
||||
module FlagQuery
|
||||
|
||||
def self.flagged_posts_report(current_user, filter, offset = 0, per_page = 25)
|
||||
|
@ -128,6 +130,44 @@ module FlagQuery
|
|||
|
||||
end
|
||||
|
||||
def self.flagged_topics
|
||||
|
||||
results = PostAction
|
||||
.flags
|
||||
.active
|
||||
.includes(post: [:user, :topic])
|
||||
.order('post_actions.created_at DESC')
|
||||
|
||||
ft_by_id = {}
|
||||
users_by_id = {}
|
||||
topics_by_id = {}
|
||||
|
||||
results.each do |pa|
|
||||
if pa.post.present? && pa.post.topic.present?
|
||||
ft = ft_by_id[pa.post.topic.id] ||= OpenStruct.new(
|
||||
topic: pa.post.topic,
|
||||
flag_counts: {},
|
||||
user_ids: [],
|
||||
last_flag_at: pa.created_at
|
||||
)
|
||||
|
||||
topics_by_id[pa.post.topic.id] = pa.post.topic
|
||||
|
||||
ft.flag_counts[pa.post_action_type_id] ||= 0
|
||||
ft.flag_counts[pa.post_action_type_id] += 1
|
||||
|
||||
ft.user_ids << pa.post.user_id
|
||||
ft.user_ids.uniq!
|
||||
|
||||
users_by_id[pa.post.user_id] ||= pa.post.user
|
||||
end
|
||||
end
|
||||
|
||||
Topic.preload_custom_fields(topics_by_id.values, TopicList.preloaded_custom_fields)
|
||||
|
||||
{ flagged_topics: ft_by_id.values, users: users_by_id.values }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.excerpt(cooked)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
module JsonError
|
||||
|
||||
def create_errors_json(obj, type = nil)
|
||||
def create_errors_json(obj, opts = nil)
|
||||
opts ||= {}
|
||||
|
||||
errors = create_errors_array obj
|
||||
errors[:error_type] = type if type
|
||||
errors[:error_type] = opts[:type] if opts[:type]
|
||||
errors[:extras] = opts[:extras] if opts[:extras]
|
||||
errors
|
||||
end
|
||||
|
||||
|
|
11
test/javascripts/acceptance/admin-flags-topics-test.js.es6
Normal file
11
test/javascripts/acceptance/admin-flags-topics-test.js.es6
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { acceptance } from "helpers/qunit-helpers";
|
||||
acceptance("Admin - Flagged Topics", { loggedIn: true });
|
||||
|
||||
QUnit.test("topics with flags", assert => {
|
||||
visit("/admin/flags/topics");
|
||||
andThen(() => {
|
||||
assert.ok(exists('.watched-words-list'));
|
||||
assert.ok(!exists('.watched-words-list .watched-word'), "Don't show bad words by default.");
|
||||
});
|
||||
});
|
||||
|
|
@ -323,6 +323,14 @@ export default function() {
|
|||
]);
|
||||
});
|
||||
|
||||
this.get('/admin/flagged_topics', () => {
|
||||
return response(200, {
|
||||
"flagged_topics": [
|
||||
{ id: 1 }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
this.get('/admin/customize/site_texts', request => {
|
||||
|
||||
if (request.queryParams.overridden) {
|
||||
|
|
|
@ -13,7 +13,6 @@ export default function() {
|
|||
if (type === "adapter:rest") {
|
||||
if (!this._restAdapter) {
|
||||
this._restAdapter = RestAdapter.create({ owner: this });
|
||||
// this._restAdapter.container = this;
|
||||
}
|
||||
return this._restAdapter;
|
||||
}
|
||||
|
@ -37,4 +36,4 @@ export default function() {
|
|||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user