diff --git a/app/assets/javascripts/admin/adapters/flagged-post.js.es6 b/app/assets/javascripts/admin/adapters/flagged-post.js.es6
index ed5237588c6..d75afc719fe 100644
--- a/app/assets/javascripts/admin/adapters/flagged-post.js.es6
+++ b/app/assets/javascripts/admin/adapters/flagged-post.js.es6
@@ -2,7 +2,9 @@ import RestAdapter from 'discourse/adapters/rest';
export default RestAdapter.extend({
pathFor(store, type, findArgs) {
- return `/admin/flags/${findArgs.filter}.json?rest_api=true`;
+ let args = Object.assign({ rest_api: true }, findArgs);
+ delete args.filter;
+ return `/admin/flags/${findArgs.filter}.json?${$.param(args)}`;
},
afterFindAll(results, helper) {
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6
index 8534d64b4fc..07fbe0763c0 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-agree-flag.js.es6
@@ -1,8 +1,8 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
+import DeleteSpammerModal from 'admin/mixins/delete-spammer-modal';
-export default Ember.Controller.extend(ModalFunctionality, {
+export default Ember.Controller.extend(ModalFunctionality, DeleteSpammerModal, {
removeAfter: null,
- deleteSpammer: null,
actions: {
agreeDeleteSpammer(user) {
diff --git a/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6
index f972465ae97..e8038edbca1 100644
--- a/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6
+++ b/app/assets/javascripts/admin/controllers/modals/admin-delete-flag.js.es6
@@ -1,15 +1,10 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
+import DeleteSpammerModal from 'admin/mixins/delete-spammer-modal';
-export default Ember.Controller.extend(ModalFunctionality, {
+export default Ember.Controller.extend(ModalFunctionality, DeleteSpammerModal, {
removeAfter: null,
actions: {
- deleteSpammer(user) {
- return this.removeAfter(user.deleteAsSpammer()).then(() => {
- this.send('closeModal');
- });
- },
-
deletePostDeferFlag() {
let flaggedPost = this.get('model');
this.removeAfter(flaggedPost.deferFlags(true)).then(() => {
diff --git a/app/assets/javascripts/admin/mixins/delete-spammer-modal.js.es6 b/app/assets/javascripts/admin/mixins/delete-spammer-modal.js.es6
new file mode 100644
index 00000000000..88eb5c0c563
--- /dev/null
+++ b/app/assets/javascripts/admin/mixins/delete-spammer-modal.js.es6
@@ -0,0 +1,26 @@
+import computed from 'ember-addons/ember-computed-decorators';
+
+export default Ember.Mixin.create({
+ adminTools: Ember.inject.service(),
+ spammerDetails: null,
+
+ onShow() {
+ let adminTools = this.get('adminTools');
+ let spammerDetails = adminTools.spammerDetails(this.get('model.user'));
+
+ this.setProperties({
+ spammerDetails,
+ canDeleteSpammer: spammerDetails.canDelete && this.get('model.flaggedForSpam')
+ });
+ },
+
+ actions: {
+ deleteSpammer() {
+ let spammerDetails = this.get('spammerDetails');
+ this.removeAfter(spammerDetails.deleteUser()).then(() => {
+ this.send('closeModal');
+ });
+ }
+ }
+
+});
diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6
index 18431757280..b31bf125654 100644
--- a/app/assets/javascripts/admin/models/admin-user.js.es6
+++ b/app/assets/javascripts/admin/models/admin-user.js.es6
@@ -463,59 +463,6 @@ const AdminUser = Discourse.User.extend({
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
},
- deleteAsSpammer() {
- return this.checkEmail().then(() => {
-
- let message = I18n.messageFormat('flagging.delete_confirm_MF', {
- "POSTS": this.get('post_count'),
- "TOPICS": this.get('topic_count'),
- email: this.get('email') || I18n.t("flagging.hidden_email_address"),
- ip_address: this.get('ip_address') || I18n.t("flagging.ip_address_missing")
- });
-
- let userId = this.get('id');
-
- return new Ember.RSVP.Promise((resolve, reject) => {
- const buttons = [
- {
- label: I18n.t("composer.cancel"),
- class: "cancel-inline",
- link: true
- },
- {
- label: `${iconHTML('exclamation-triangle')} ` + I18n.t("flagging.yes_delete_spammer"),
- class: "btn btn-danger confirm-delete",
- callback() {
- return ajax(`/admin/users/${userId}.json`, {
- type: 'DELETE',
- data: {
- delete_posts: true,
- block_email: true,
- block_urls: true,
- block_ip: true,
- delete_as_spammer: true,
- context: window.location.pathname
- }
- }).then(result => {
- if (result.deleted) {
- resolve();
- } else {
- throw 'failed to delete';
- }
- }).catch(() => {
- bootbox.alert(I18n.t("admin.user.delete_failed"));
- reject();
- });
- }
- }
- ];
-
- bootbox.dialog(message, buttons, {classes: "flagging-delete-spammer"});
- });
-
- });
- },
-
loadDetails() {
const user = this;
diff --git a/app/assets/javascripts/admin/routes/admin-flags-topics-show.js.es6 b/app/assets/javascripts/admin/routes/admin-flags-topics-show.js.es6
index fca10d61511..b75799bc8cb 100644
--- a/app/assets/javascripts/admin/routes/admin-flags-topics-show.js.es6
+++ b/app/assets/javascripts/admin/routes/admin-flags-topics-show.js.es6
@@ -1,5 +1,4 @@
import { loadTopicView } from 'discourse/models/topic';
-import FlaggedPost from 'admin/models/flagged-post';
export default Ember.Route.extend({
model(params) {
@@ -8,7 +7,7 @@ export default Ember.Route.extend({
return Ember.RSVP.hash({
topic,
- flaggedPosts: FlaggedPost.findAll({
+ flaggedPosts: this.store.findAll('flagged-post', {
filter: 'active',
topic_id: params.id
})
diff --git a/app/assets/javascripts/admin/services/admin-tools.js.es6 b/app/assets/javascripts/admin/services/admin-tools.js.es6
new file mode 100644
index 00000000000..650e078f57d
--- /dev/null
+++ b/app/assets/javascripts/admin/services/admin-tools.js.es6
@@ -0,0 +1,75 @@
+// A service that can act as a bridge between the front end Discourse application
+// and the admin application. Use this if you need front end code to access admin
+// modules. Inject it optionally, and if it exists go to town!
+
+import AdminUser from 'admin/models/admin-user';
+import { iconHTML } from 'discourse-common/lib/icon-library';
+import { ajax } from 'discourse/lib/ajax';
+
+export default Ember.Service.extend({
+
+ checkSpammer(userId) {
+ return AdminUser.find(userId).then(au => this.spammerDetails(au));
+ },
+
+ spammerDetails(adminUser) {
+ return {
+ deleteUser: () => this._deleteSpammer(adminUser),
+ canDelete: adminUser.get('can_be_deleted') && adminUser.get('can_delete_all_posts')
+ };
+ },
+
+ _deleteSpammer(adminUser) {
+ return adminUser.checkEmail().then(() => {
+
+ let message = I18n.messageFormat('flagging.delete_confirm_MF', {
+ "POSTS": adminUser.get('post_count'),
+ "TOPICS": adminUser.get('topic_count'),
+ email: adminUser.get('email') || I18n.t("flagging.hidden_email_address"),
+ ip_address: adminUser.get('ip_address') || I18n.t("flagging.ip_address_missing")
+ });
+
+ let userId = adminUser.get('id');
+
+ return new Ember.RSVP.Promise((resolve, reject) => {
+ const buttons = [
+ {
+ label: I18n.t("composer.cancel"),
+ class: "cancel-inline",
+ link: true
+ },
+ {
+ label: `${iconHTML('exclamation-triangle')} ` + I18n.t("flagging.yes_delete_spammer"),
+ class: "btn btn-danger confirm-delete",
+ callback() {
+ return ajax(`/admin/users/${userId}.json`, {
+ type: 'DELETE',
+ data: {
+ delete_posts: true,
+ block_email: true,
+ block_urls: true,
+ block_ip: true,
+ delete_as_spammer: true,
+ context: window.location.pathname
+ }
+ }).then(result => {
+ if (result.deleted) {
+ resolve();
+ } else {
+ throw 'failed to delete';
+ }
+ }).catch(() => {
+ bootbox.alert(I18n.t("admin.user.delete_failed"));
+ reject();
+ });
+ }
+ }
+ ];
+
+ bootbox.dialog(message, buttons, {classes: "flagging-delete-spammer"});
+ });
+
+ });
+ }
+
+});
diff --git a/app/assets/javascripts/admin/templates/flags-topics-show.hbs b/app/assets/javascripts/admin/templates/flags-topics-show.hbs
index 225b21177d4..04947802203 100644
--- a/app/assets/javascripts/admin/templates/flags-topics-show.hbs
+++ b/app/assets/javascripts/admin/templates/flags-topics-show.hbs
@@ -10,6 +10,7 @@
{{plugin-outlet name="flagged-topic-details-header" args=(hash topic=topic)}}
+
{{flagged-posts
flaggedPosts=flaggedPosts
diff --git a/app/assets/javascripts/admin/templates/modal/admin-agree-flag.hbs b/app/assets/javascripts/admin/templates/modal/admin-agree-flag.hbs
index a69ae42877e..b9d5a88383b 100644
--- a/app/assets/javascripts/admin/templates/modal/admin-agree-flag.hbs
+++ b/app/assets/javascripts/admin/templates/modal/admin-agree-flag.hbs
@@ -24,10 +24,10 @@
icon="thumbs-o-up"
label="admin.flags.agree_flag"}}
- {{#if model.canDeleteAsSpammer}}
+ {{#if canDeleteSpammer}}
{{d-button
title="admin.flags.delete_spammer_title"
- action=(action "agreeDeleteSpammer" model.user)
+ action="deleteSpammer"
class="btn-danger delete-spammer"
icon="exclamation-triangle"
label="admin.flags.delete_spammer"}}
diff --git a/app/assets/javascripts/admin/templates/modal/admin-delete-flag.hbs b/app/assets/javascripts/admin/templates/modal/admin-delete-flag.hbs
index d27c10d1127..571f332dbf0 100644
--- a/app/assets/javascripts/admin/templates/modal/admin-delete-flag.hbs
+++ b/app/assets/javascripts/admin/templates/modal/admin-delete-flag.hbs
@@ -13,11 +13,11 @@
icon="thumbs-o-up"
label="admin.flags.delete_post_agree_flag"}}
- {{#if model.canDeleteAsSpammer}}
+ {{#if canDeleteSpammer}}
{{d-button
class="btn-danger delete-spammer"
title="admin.flags.delete_spammer_title"
- action=(action "deleteSpammer" model.user)
+ action="deleteSpammer"
icon="exclamation-triangle"
label="admin.flags.delete_spammer"}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/components/flagged-posts.js.es6 b/app/assets/javascripts/discourse/components/flagged-posts.js.es6
index 4d520910bf4..67763d54e4f 100644
--- a/app/assets/javascripts/discourse/components/flagged-posts.js.es6
+++ b/app/assets/javascripts/discourse/components/flagged-posts.js.es6
@@ -1,5 +1,3 @@
-import FlaggedPost from 'admin/models/flagged-post';
-
export default Ember.Component.extend({
canAct: Ember.computed.equal('filter', 'active'),
showResolvedBy: Ember.computed.equal('filter', 'old'),
@@ -11,28 +9,10 @@ export default Ember.Component.extend({
},
loadMore() {
- if (this.get('allLoaded')) {
- return;
- }
-
const flaggedPosts = this.get('flaggedPosts');
-
- let args = {
- filter: this.get('query'),
- offset: flaggedPosts.length+1
- };
-
- let topic = this.get('topic');
- if (topic) {
- args.topic_id = topic.id;
+ if (flaggedPosts.get('canLoadMore')) {
+ flaggedPosts.loadMore();
}
-
- return FlaggedPost.findAll(args).then(data => {
- if (data.length === 0) {
- this.set('allLoaded', true);
- }
- flaggedPosts.addObjects(data);
- });
}
}
});
diff --git a/app/assets/javascripts/discourse/controllers/flag.js.es6 b/app/assets/javascripts/discourse/controllers/flag.js.es6
index 84377f8892f..b5f823500a3 100644
--- a/app/assets/javascripts/discourse/controllers/flag.js.es6
+++ b/app/assets/javascripts/discourse/controllers/flag.js.es6
@@ -3,17 +3,35 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ActionSummary from 'discourse/models/action-summary';
import { MAX_MESSAGE_LENGTH } from 'discourse/models/post-action-type';
import computed from 'ember-addons/ember-computed-decorators';
+import optionalService from 'discourse/lib/optional-service';
export default Ember.Controller.extend(ModalFunctionality, {
+ adminTools: optionalService(),
userDetails: null,
selected: null,
flagTopic: null,
message: null,
isWarning: false,
topicActionByName: null,
+ spammerDetails: null,
onShow() {
- this.set('selected', null);
+ this.setProperties({
+ selected: null,
+ spammerDetails: null
+ });
+
+ let adminTools = this.get('adminTools');
+ if (adminTools) {
+ adminTools.checkSpammer(this.get('model.user_id')).then(result => {
+ this.set('spammerDetails', result);
+ });
+ }
+ },
+
+ @computed('spammerDetails.canDelete', 'selected.name_key')
+ showDeleteSpammer(canDeleteSpammer, nameKey) {
+ return canDeleteSpammer && nameKey === 'spam';
},
@computed('flagTopic')
@@ -74,13 +92,10 @@ export default Ember.Controller.extend(ModalFunctionality, {
submitDisabled: Em.computed.not('submitEnabled'),
// Staff accounts can "take action"
- canTakeAction: function() {
- if (this.get("flagTopic")) return false;
-
- // We can only take actions on non-custom flags
- if (this.get('selected.is_custom_flag')) return false;
- return Discourse.User.currentProp('staff');
- }.property('selected.is_custom_flag'),
+ @computed('flagTopic', 'selected.is_custom_flag')
+ canTakeAction(flagTopic, isCustomFlag) {
+ return !flagTopic && !isCustomFlag && this.currentUser.get('staff');
+ },
submitText: function(){
if (this.get('selected.is_custom_flag')) {
@@ -91,6 +106,13 @@ export default Ember.Controller.extend(ModalFunctionality, {
}.property('selected.is_custom_flag'),
actions: {
+ deleteSpammer() {
+ let details = this.get('spammerDetails');
+ if (details) {
+ details.deleteUser().then(() => window.location.reload());
+ }
+ },
+
takeAction() {
this.send('createFlag', {takeAction: true});
this.set('model.hidden', true);
@@ -136,32 +158,9 @@ export default Ember.Controller.extend(ModalFunctionality, {
},
},
- canDeleteSpammer: function() {
- if (this.get("flagTopic")) return false;
-
- if (this.currentUser.get('staff') && this.get('selected.name_key') === 'spam') {
- return this.get('userDetails.can_be_deleted') &&
- this.get('userDetails.can_delete_all_posts');
- } else {
- return false;
- }
- }.property('selected.name_key', 'userDetails.can_be_deleted', 'userDetails.can_delete_all_posts'),
-
@computed('flagTopic', 'selected.name_key')
canSendWarning(flagTopic, nameKey) {
return !flagTopic && this.currentUser.get('staff') && nameKey === 'notify_user';
- },
-
- usernameChanged: function() {
- this.set('userDetails', null);
- this.fetchUserDetails();
- }.observes('model.username'),
-
- fetchUserDetails() {
- if (Discourse.User.currentProp('staff') && this.get('model.username')) {
- const AdminUser = requirejs('admin/models/admin-user').default;
- AdminUser.find(this.get('model.user_id')).then(user => this.set('userDetails', user));
- }
}
});
diff --git a/app/assets/javascripts/discourse/lib/optional-service.js.es6 b/app/assets/javascripts/discourse/lib/optional-service.js.es6
new file mode 100644
index 00000000000..e4053ccc7ef
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/optional-service.js.es6
@@ -0,0 +1,7 @@
+const { computed, getOwner, String: { dasherize } } = Ember;
+
+export default function(name) {
+ return computed(function(defaultName) {
+ return getOwner(this).lookup(`service:${name || dasherize(defaultName)}`);
+ });
+};
diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6
index e5138109b69..515c135ba8d 100644
--- a/app/assets/javascripts/discourse/models/store.js.es6
+++ b/app/assets/javascripts/discourse/models/store.js.es6
@@ -145,10 +145,12 @@ export default Ember.Object.extend({
const self = this;
return ajax(url).then(function(result) {
- const typeName = Ember.String.underscore(self.pluralize(type)),
- totalRows = result["total_rows_" + typeName] || result.get('totalRows'),
- loadMoreUrl = result["load_more_" + typeName],
- content = result[typeName].map(obj => self._hydrate(type, obj, result));
+ let typeName = Ember.String.underscore(self.pluralize(type));
+
+ let pageTarget = result.meta || result;
+ let totalRows = pageTarget["total_rows_" + typeName] || resultSet.get('totalRows');
+ let loadMoreUrl = pageTarget["load_more_" + typeName];
+ let content = result[typeName].map(obj => self._hydrate(type, obj, result));
resultSet.setProperties({ totalRows, loadMoreUrl });
resultSet.get('content').pushObjects(content);
@@ -192,12 +194,14 @@ export default Ember.Object.extend({
const typeName = Ember.String.underscore(this.pluralize(type));
const content = result[typeName].map(obj => this._hydrate(type, obj, result));
+ let pageTarget = result.meta || result;
+
const createArgs = {
content,
findArgs,
- totalRows: result["total_rows_" + typeName] || content.length,
- loadMoreUrl: result["load_more_" + typeName],
- refreshUrl: result['refresh_' + typeName],
+ totalRows: pageTarget["total_rows_" + typeName] || content.length,
+ loadMoreUrl: pageTarget["load_more_" + typeName],
+ refreshUrl: pageTarget['refresh_' + typeName],
store: this,
__type: type
};
diff --git a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6
index 51849ef4025..34181e46b41 100644
--- a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6
+++ b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6
@@ -30,7 +30,7 @@ export default {
DiscourseURL.appEvents = appEvents;
app.register('store:main', Store);
- inject(app, 'store', 'route', 'controller');
+ inject(app, 'store', 'route', 'controller', 'service');
const messageBus = window.MessageBus;
app.register('message-bus:main', messageBus, { instantiate: false });
diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6
index c53dd857129..a035ef6c70c 100644
--- a/app/assets/javascripts/discourse/routes/application.js.es6
+++ b/app/assets/javascripts/discourse/routes/application.js.es6
@@ -138,10 +138,6 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
});
},
- deleteSpammer(user) {
- user.deleteAsSpammer.then(() => window.location.reload());
- },
-
checkEmail(user) {
user.checkEmail();
},
diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6
index 5f920b58fd6..7fc7fd098fd 100644
--- a/app/assets/javascripts/discourse/routes/topic.js.es6
+++ b/app/assets/javascripts/discourse/routes/topic.js.es6
@@ -41,13 +41,13 @@ const TopicRoute = Discourse.Route.extend({
showFlags(model) {
let controller = showModal('flag', { model });
- controller.setProperties({ selected: null, flagTopic: false });
+ controller.setProperties({ flagTopic: false });
},
showFlagTopic() {
const model = this.modelFor('topic');
let controller = showModal('flag', { model });
- controller.setProperties({ selected: null, flagTopic: true });
+ controller.setProperties({ flagTopic: true });
},
showTopicStatusUpdate() {
diff --git a/app/assets/javascripts/discourse/templates/modal/flag.hbs b/app/assets/javascripts/discourse/templates/modal/flag.hbs
index 26463f09efb..4123a371bb7 100644
--- a/app/assets/javascripts/discourse/templates/modal/flag.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/flag.hbs
@@ -39,10 +39,10 @@
label="flagging.take_action"}}
{{/if}}
- {{#if canDeleteSpammer}}
+ {{#if showDeleteSpammer}}
{{d-button
class="btn-danger"
- action=(route-action "deleteSpammer" userDetails)
+ action="deleteSpammer"
disabled=submitDisabled
icon="exclamation-triangle"
label="flagging.delete_spammer"}}
diff --git a/app/assets/stylesheets/common/admin/flagging.scss b/app/assets/stylesheets/common/admin/flagging.scss
index b5f290c5962..03393450846 100644
--- a/app/assets/stylesheets/common/admin/flagging.scss
+++ b/app/assets/stylesheets/common/admin/flagging.scss
@@ -152,6 +152,7 @@
.flagged-topic-details {
display: flex;
justify-content: space-between;
+ margin-bottom: 2em;
}
.delete-flag-modal, .agree-flag-modal {
diff --git a/app/controllers/admin/flagged_topics_controller.rb b/app/controllers/admin/flagged_topics_controller.rb
index d84d89680c3..82e7b68b27f 100644
--- a/app/controllers/admin/flagged_topics_controller.rb
+++ b/app/controllers/admin/flagged_topics_controller.rb
@@ -5,10 +5,13 @@ 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)
+ render_json_dump(
+ {
+ flagged_topics: serialize_data(result[:flagged_topics], FlaggedTopicSummarySerializer),
+ users: serialize_data(result[:users], BasicUserSerializer),
+ },
+ rest_serializer: true
+ )
end
end
diff --git a/app/controllers/admin/flags_controller.rb b/app/controllers/admin/flags_controller.rb
index 16d4b03fb69..a14809476ff 100644
--- a/app/controllers/admin/flags_controller.rb
+++ b/app/controllers/admin/flags_controller.rb
@@ -10,40 +10,51 @@ class Admin::FlagsController < Admin::AdminController
# we may get out of sync, fix it here
PostAction.update_flagged_posts_count
- posts, topics, users, post_actions = FlagQuery.flagged_posts_report(
+ offset = params[:offset].to_i
+ per_page = Admin::FlagsController.flags_per_page
+
+ posts, topics, users, post_actions, total_rows = FlagQuery.flagged_posts_report(
current_user,
filter: params[:filter],
- offset: params[:offset].to_i,
+ offset: offset,
topic_id: params[:topic_id],
- per_page: Admin::FlagsController.flags_per_page,
+ per_page: per_page,
rest_api: params[:rest_api].present?
)
- if posts.blank?
- render json: { posts: [], topics: [], users: [] }
- else
- if params[:rest_api]
- render_json_dump(
- {
- flagged_posts: posts,
- topics: serialize_data(topics, FlaggedTopicSerializer),
- users: serialize_data(users, FlaggedUserSerializer),
- post_actions: post_actions
- },
- rest_serializer: true,
- meta: {
- types: {
- disposed_by: 'user'
- }
- }
- )
- else
- render_json_dump(
- posts: posts,
- topics: serialize_data(topics, FlaggedTopicSerializer),
- users: serialize_data(users, FlaggedUserSerializer)
+ if params[:rest_api]
+ meta = {
+ types: {
+ disposed_by: 'user'
+ }
+ }
+
+ if (total_rows || 0) > (offset + per_page)
+ meta[:total_rows_flagged_posts] = total_rows
+ meta[:load_more_flagged_posts] = admin_flags_filtered_path(
+ filter: params[:filter],
+ offset: offset + per_page,
+ rest_api: params[:rest_api],
+ topic_id: params[:topic_id]
)
end
+
+ render_json_dump(
+ {
+ flagged_posts: posts,
+ topics: serialize_data(topics, FlaggedTopicSerializer),
+ users: serialize_data(users, FlaggedUserSerializer),
+ post_actions: post_actions
+ },
+ rest_serializer: true,
+ meta: meta
+ )
+ else
+ render_json_dump(
+ posts: posts,
+ topics: serialize_data(topics, FlaggedTopicSerializer),
+ users: serialize_data(users, FlaggedUserSerializer)
+ )
end
end
diff --git a/app/models/post.rb b/app/models/post.rb
index d47bf02b456..853204830d0 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -317,7 +317,7 @@ class Post < ActiveRecord::Base
end
def archetype
- topic.archetype
+ topic&.archetype
end
def self.regular_order
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 6730091581b..22547b743f7 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -272,7 +272,7 @@ class Topic < ActiveRecord::Base
end
def has_flags?
- FlagQuery.flagged_post_actions("active")
+ FlagQuery.flagged_post_actions(filter: "active")
.where("topics.id" => id)
.exists?
end
diff --git a/config/routes.rb b/config/routes.rb
index 33389b5e107..2ebd987a426 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -187,7 +187,7 @@ Discourse::Application.routes.draw do
put "customize/embedding" => "embedding#update", constraints: AdminConstraint.new
get "flags" => "flags#index"
- get "flags/:filter" => "flags#index"
+ get "flags/:filter" => "flags#index", as: 'flags_filtered'
get "flags/topics/:topic_id" => "flags#index"
post "flags/agree/:id" => "flags#agree"
post "flags/disagree/:id" => "flags#disagree"
diff --git a/lib/flag_query.rb b/lib/flag_query.rb
index dbbdd7964ee..91deb31dc59 100644
--- a/lib/flag_query.rb
+++ b/lib/flag_query.rb
@@ -4,10 +4,8 @@ module FlagQuery
def self.flagged_posts_report(current_user, opts = nil)
opts ||= {}
- filter = opts[:filter] || 'active'
offset = opts[:offset] || 0
per_page = opts[:per_page] || 25
- topic_id = opts[:topic_id]
actions = flagged_post_actions(opts)
@@ -21,6 +19,8 @@ module FlagQuery
)
end
+ total_rows = actions.count
+
post_ids = actions.limit(per_page)
.offset(offset)
.group(:post_id)
@@ -28,8 +28,6 @@ module FlagQuery
.pluck(:post_id)
.uniq
- return nil if post_ids.blank?
-
posts = SqlBuilder.new("
SELECT p.id,
p.cooked,
@@ -129,11 +127,14 @@ module FlagQuery
posts,
Topic.with_deleted.where(id: topic_ids.to_a).to_a,
User.includes(:user_stat).where(id: user_ids.to_a).to_a,
- all_post_actions
+ all_post_actions,
+ total_rows
]
end
- def self.flagged_post_actions(opts)
+ def self.flagged_post_actions(opts = nil)
+ opts ||= {}
+
post_actions = PostAction.flags
.joins("INNER JOIN posts ON posts.id = post_actions.post_id")
.joins("INNER JOIN topics ON topics.id = posts.topic_id")
diff --git a/spec/components/flag_query_spec.rb b/spec/components/flag_query_spec.rb
index 5a63539ea69..7e39c61feff 100644
--- a/spec/components/flag_query_spec.rb
+++ b/spec/components/flag_query_spec.rb
@@ -57,7 +57,7 @@ describe FlagQuery do
posts = FlagQuery.flagged_posts_report(admin, topic_id: post.topic_id)
expect(posts).to be_present
posts = FlagQuery.flagged_posts_report(admin, topic_id: -1)
- expect(posts).to be_blank
+ expect(posts[0]).to be_blank
# chuck post in category a mod can not see and make sure its missing
category = Fabricate(:category)
diff --git a/spec/requests/admin/flagged_topics_controller_spec.rb b/spec/requests/admin/flagged_topics_controller_spec.rb
new file mode 100644
index 00000000000..594caff1ed7
--- /dev/null
+++ b/spec/requests/admin/flagged_topics_controller_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+RSpec.describe Admin::FlaggedTopicsController do
+ let(:admin) { Fabricate(:admin) }
+ let!(:flag) { Fabricate(:flag) }
+
+ before do
+ sign_in(admin)
+ end
+
+ it "returns a list of flagged topics" do
+ get "/admin/flagged_topics.json"
+ expect(response).to be_success
+
+ data = ::JSON.parse(response.body)
+ expect(data['flagged_topics']).to be_present
+ expect(data['users']).to be_present
+ end
+end
diff --git a/test/javascripts/acceptance/admin-flags-test.js.es6 b/test/javascripts/acceptance/admin-flags-test.js.es6
index aa799b99a6a..e891e568c4e 100644
--- a/test/javascripts/acceptance/admin-flags-test.js.es6
+++ b/test/javascripts/acceptance/admin-flags-test.js.es6
@@ -6,6 +6,8 @@ QUnit.test("flagged posts", assert => {
andThen(() => {
assert.equal(find('.flagged-posts .flagged-post').length, 1);
assert.equal(find('.flagged-post .flaggers .flagger').length, 1, 'shows who flagged it');
+ assert.equal(find('.flagged-post-response').length, 2);
+ assert.equal(find('.flagged-post-response:eq(0) img.avatar').length, 1);
});
});
diff --git a/test/javascripts/helpers/flag-pretender.js.es6 b/test/javascripts/helpers/flag-pretender.js.es6
index 68ffd308664..f42221453c2 100644
--- a/test/javascripts/helpers/flag-pretender.js.es6
+++ b/test/javascripts/helpers/flag-pretender.js.es6
@@ -49,7 +49,17 @@ export default function(helpers) {
id: 1,
user_id: eviltrout.id,
post_action_type_id: 8,
- name_key: 'spam'
+ name_key: 'spam',
+ conversation: {
+ response: {
+ user_id: eviltrout.id,
+ excerpt: "hello",
+ },
+ reply: {
+ user_id: eviltrout.id,
+ excerpt: "goodbye"
+ }
+ }
}],
"__rest_serializer": "1"
});
diff --git a/test/javascripts/helpers/store-pretender.js.es6 b/test/javascripts/helpers/store-pretender.js.es6
index c47dec3ab93..d86f1260e81 100644
--- a/test/javascripts/helpers/store-pretender.js.es6
+++ b/test/javascripts/helpers/store-pretender.js.es6
@@ -28,7 +28,26 @@ export default function(helpers) {
});
this.get('/fruits', function() {
- return response({ __rest_serializer: "1", fruits, farmers, colors, extras: {hello: 'world'} });
+ return response({
+ __rest_serializer: "1",
+ fruits,
+ farmers,
+ colors,
+ extras: {hello: 'world'}
+ });
+ });
+
+ this.get('/barns/:id', function() {
+ return response({
+ __rest_serializer: "1",
+ meta: {
+ types: {
+ owner: "farmer"
+ }
+ },
+ barn: { id: 1234, owner_id: farmers[0].id },
+ farmers: [farmers[0]],
+ });
});
this.get('/widgets/:widget_id', function(request) {
@@ -65,10 +84,14 @@ export default function(helpers) {
if (qp.id) { result = result.filterBy('id', parseInt(qp.id)); }
}
- return response({ widgets: result,
- total_rows_widgets: 4,
- load_more_widgets: '/load-more-widgets',
- refresh_widgets: '/widgets?refresh=true' });
+ return response({
+ widgets: result,
+ meta: {
+ total_rows_widgets: 4,
+ load_more_widgets: '/load-more-widgets',
+ refresh_widgets: '/widgets?refresh=true'
+ }
+ });
});
this.get('/load-more-widgets', function() {
@@ -76,4 +99,4 @@ export default function(helpers) {
});
this.delete('/widgets/:widget_id', success);
-};
\ No newline at end of file
+};
diff --git a/test/javascripts/models/store-test.js.es6 b/test/javascripts/models/store-test.js.es6
index f532ad6f77d..6499262e8b1 100644
--- a/test/javascripts/models/store-test.js.es6
+++ b/test/javascripts/models/store-test.js.es6
@@ -142,6 +142,13 @@ QUnit.test('find embedded', function(assert) {
});
});
+QUnit.test('meta types', function(assert) {
+ const store = createStore();
+ return store.find('barn', 1).then(function(f) {
+ assert.equal(f.get('owner.name'), 'Old MacDonald', 'it has the embedded farmer');
+ });
+});
+
QUnit.test('findAll embedded', function(assert) {
const store = createStore();
return store.findAll('fruit').then(function(fruits) {
@@ -156,4 +163,4 @@ QUnit.test('findAll embedded', function(assert) {
assert.equal(fruits.objectAt(2).get('farmer.name'), 'Luke Skywalker');
});
-});
\ No newline at end of file
+});