From 7d53b778de7fa1f0e939ad98c498fa4a1b245846 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 13 Sep 2016 12:02:39 -0400 Subject: [PATCH 1/6] WIP extending flags management interface with new options for state, assignee, notes, etc --- public/language/en_GB/topic.json | 12 +++ src/views/admin/manage/flags.tpl | 138 ++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 46 deletions(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index f177725d56..e38f4b1d84 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -36,6 +36,18 @@ "flag_title": "Flag this post for moderation", "flag_success": "This post has been flagged for moderation.", + "flag_manage_title": "Flagged post in %1", + "flag_manage_history": "Action History", + "flag_manage_no_history": "No event history to report", + "flag_manage_assignee": "Assignee", + "flag_manage_state": "State", + "flag_manage_state_open": "New/Open", + "flag_manage_state_wip": "Work in Progress", + "flag_manage_state_resolved": "Resolved", + "flag_manage_state_rejected": "Rejected", + "flag_manage_notes": "Shared Notes", + "flag_manage_update": "Update Flag Status", + "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.", "following_topic.message": "You will now be receiving notifications when somebody posts to this topic.", diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl index 1bd1726b97..1e12d5c51a 100644 --- a/src/views/admin/manage/flags.tpl +++ b/src/views/admin/manage/flags.tpl @@ -44,7 +44,7 @@
-
+
No flagged posts! @@ -52,54 +52,100 @@ -
-
-
-
- - - - -
{../user.icon:text}
- -
+
+ +
+
+
+
+
+ + + + +
{../user.icon:text}
+ +
- - {../user.username} - -
-

{posts.content}

+ + {../user.username} + +
+

{posts.content}

+
+ + + Posted in {posts.category.name}, • + Read More + + +
+
+
+ This post has been flagged {posts.flags} time(s): +
+ +
+
+ + +
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
[[topic:flag_manage_history]]
+ +
[[topic:flag_manage_no_history]]
+
- - - Posted in {posts.category.name}, • - Read More - -
-
-
-
- This post has been flagged {posts.flags} time(s): -
- -
-
- -
From 8dc57cba58721d075474810f61c82e9016db66e9 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 14 Sep 2016 21:06:55 -0400 Subject: [PATCH 2/6] allowing updating of flag data --- public/src/admin/manage/flags.js | 20 +++++++++++++++++-- src/controllers/admin/flags.js | 20 ++++++++++++++++++- src/posts/flags.js | 34 ++++++++++++++++++++++++++++++++ src/socket.io/posts/flag.js | 30 +++++++++++++++++++++++++++- src/user.js | 14 +++++++++++++ src/views/admin/manage/flags.tpl | 23 +++++++++++---------- 6 files changed, 127 insertions(+), 14 deletions(-) diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js index a686c4a07e..8885d7b8a5 100644 --- a/public/src/admin/manage/flags.js +++ b/public/src/admin/manage/flags.js @@ -4,8 +4,9 @@ define('admin/manage/flags', [ 'forum/infinitescroll', 'autocomplete', - 'Chart' -], function(infinitescroll, autocomplete, Chart) { + 'Chart', + 'components' +], function(infinitescroll, autocomplete, Chart, components) { var Flags = {}; @@ -21,6 +22,7 @@ define('admin/manage/flags', [ handleDelete(); handleInfiniteScroll(); handleGraphs(); + handleFormActions(); }; function handleDismiss() { @@ -150,5 +152,19 @@ define('admin/manage/flags', [ }); } + function handleFormActions() { + components.get('posts/flag').find('[component="posts/flag/update"]').on('click', function() { + var pid = $(this).parents('[component="posts/flag"]').attr('data-pid'); + var formData = $($(this).parents('form').get(0)).serializeArray(); + + socket.emit('posts.updateFlag', { + pid: pid, + data: formData + }, function(err) { + console.log(arguments); + }); + }); + } + return Flags; }); \ No newline at end of file diff --git a/src/controllers/admin/flags.js b/src/controllers/admin/flags.js index 340eda0a9d..f21cc773ad 100644 --- a/src/controllers/admin/flags.js +++ b/src/controllers/admin/flags.js @@ -2,6 +2,7 @@ var async = require('async'); var posts = require('../../posts'); +var user = require('../../user'); var analytics = require('../../analytics'); var flagsController = {}; @@ -25,15 +26,32 @@ flagsController.get = function(req, res, next) { }, analytics: function(next) { analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30, next); - } + }, + assignees: async.apply(user.getAdminsandGlobalMods) }, next); } ], function (err, results) { if (err) { return next(err); } + + // Minimise data set for assignees so tjs does less work + results.assignees = results.assignees.map(function(userObj) { + var keep = ['uid', 'username']; + for(var prop in userObj) { + if (userObj.hasOwnProperty(prop)) { + if (keep.indexOf(prop) === -1) { + delete userObj[prop]; + } + } + } + + return userObj; + }); + var data = { posts: results.posts, + assignees: results.assignees, analytics: results.analytics, next: stop + 1, byUsername: byUsername, diff --git a/src/posts/flags.js b/src/posts/flags.js index dcc2f32f1d..ba0196bedf 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -226,4 +226,38 @@ module.exports = function(Posts) { } ], callback); }; + + Posts.updateFlagData = function(pid, flagObj, callback) { + // Retrieve existing flag data to compare for history-saving purposes + var changes = []; + var changeset = {}; + var prop; + Posts.getPostData(pid, function(err, postData) { + // Track new additions + for(prop in flagObj) { + if (flagObj.hasOwnProperty(prop) && !postData.hasOwnProperty('flag:' + prop)) { + changes.push(prop); + } + + // Generate changeset for object modification + if (flagObj.hasOwnProperty(prop)) { + changeset['flag:' + prop] = flagObj[prop]; + } + } + + // Track changed items + for(prop in postData) { + if ( + postData.hasOwnProperty(prop) && prop.startsWith('flag:') && + flagObj.hasOwnProperty(prop.slice(5)) && + postData[prop] !== flagObj[prop.slice(5)] + ) { + changes.push(prop.slice(5)); + } + } + + // Save flag data into post hash + Posts.setPostFields(pid, changeset, callback); + }); + }; }; diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js index 2ad5dcd2b8..6ebf9c05d8 100644 --- a/src/socket.io/posts/flag.js +++ b/src/socket.io/posts/flag.js @@ -163,7 +163,35 @@ module.exports = function(SocketPosts) { }, function (posts, next) { next(null, {posts: posts, next: stop + 1}); - }, + } ], callback); }; + + SocketPosts.updateFlag = function(socket, data, callback) { + if (!data || !(data.pid && data.data)) { + return callback('[[error:invalid-data]]'); + } + + var payload = {}; + + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(socket.uid, next); + }, + function (isAdminOrGlobalModerator, next) { + if (!isAdminOrGlobalModerator) { + return next(new Error('[[no-privileges]]')); + } + + // Translate form data into object + payload = data.data.reduce(function(memo, cur) { + memo[cur.name] = cur.value; + return memo; + }, payload); + + next(null, data.pid, payload); + }, + async.apply(posts.updateFlagData) + ], callback); + } }; diff --git a/src/user.js b/src/user.js index d04b7c8cce..48abf3f28c 100644 --- a/src/user.js +++ b/src/user.js @@ -2,6 +2,7 @@ var async = require('async'); +var groups = require('./groups'); var plugins = require('./plugins'); var db = require('./database'); var topics = require('./topics'); @@ -260,6 +261,19 @@ var utils = require('../public/src/utils'); }); }; + User.getAdminsandGlobalMods = function(callback) { + async.parallel({ + admins: async.apply(groups.getMembers, 'administrators', 0, -1), + mods: async.apply(groups.getMembers, 'Global Moderators', 0, -1) + }, function(err, results) { + if (err) { + return callback(err); + } + + User.getUsersData(results.admins.concat(results.mods), callback); + }); + }; + User.addInterstitials = function(callback) { plugins.registerHook('core', { hook: 'filter:register.interstitial', diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl index 1e12d5c51a..04e7d86d1f 100644 --- a/src/views/admin/manage/flags.tpl +++ b/src/views/admin/manage/flags.tpl @@ -44,7 +44,7 @@
-
+
No flagged posts! @@ -52,7 +52,7 @@ -
+
From 4e6b2555d0d633511e98fbb7c4dc803cfccfc1cb Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 15 Sep 2016 10:53:24 -0400 Subject: [PATCH 4/6] moved flag history expansion to its own method, showing usernames in assignee history event --- public/language/en_GB/topic.json | 3 ++ src/posts/flags.js | 50 +++++++++++++++++++++++--------- src/views/admin/manage/flags.tpl | 2 +- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index e38f4b1d84..e3479cb989 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -47,6 +47,9 @@ "flag_manage_state_rejected": "Rejected", "flag_manage_notes": "Shared Notes", "flag_manage_update": "Update Flag Status", + "flag_manage_history_assignee": "Assigned to %1", + "flag_manage_history_state": "Updated state to %1", + "flag_manage_history_notes": "Updates flag notes", "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.", diff --git a/src/posts/flags.js b/src/posts/flags.js index 6a9ad6ecfd..62956ee161 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -195,24 +195,13 @@ module.exports = function(Posts) { if (post) { post.flagReasons = reasons[index]; - - // Expand flag history - try { - history = JSON.parse(post['flag:history'] || '[]'); - history.map(function(event) { - event.timestampISO = new Date(event.timestamp).toISOString(); - return event; - }); - post['flag:history'] = history; - } catch (e) { - winston.warn('[posts/getFlags] Unable to deserialise post flag history, likely malformed data'); - } } }); next(null, results.posts); }); - } + }, + async.apply(Posts.expandFlagHistory) ], callback); } @@ -305,4 +294,39 @@ module.exports = function(Posts) { Posts.setPostFields(pid, changeset, callback); }); }; + + Posts.expandFlagHistory = function(posts, callback) { + // Expand flag history + async.map(posts, function(post, next) { + try { + var history = JSON.parse(post['flag:history'] || '[]'); + } catch (e) { + winston.warn('[posts/getFlags] Unable to deserialise post flag history, likely malformed data'); + callback(e); + } + + async.map(history, function(event, next) { + event.timestampISO = new Date(event.timestamp).toISOString(); + + if (event.type === 'assignee') { + user.getUserField(parseInt(event.value, 10), 'username', function(err, username) { + if (err) { + return next(err); + } + + event.label = username || 'Unknown user'; + next(null, event); + }); + } else if (event.type === 'state') { + event.label = '[[topic:flag_manage_state_' + event.value + ']]'; + setImmediate(next.bind(null, null, event)); + } else { + setImmediate(next.bind(null, null, event)); + } + }, function(err, history) { + post['flag:history'] = history; + next(null, post); + }); + }, callback); + } }; diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl index 40416d3a70..1ffb5856b1 100644 --- a/src/views/admin/manage/flags.tpl +++ b/src/views/admin/manage/flags.tpl @@ -151,7 +151,7 @@
  • - Updated {../type} to {../value} + [[topic:flag_manage_history_{../type}, {../label}]]
  • From b12607b1de4c97ef086a4140e477d54e1150736a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 15 Sep 2016 12:33:36 -0400 Subject: [PATCH 5/6] added uid to flag history, fixed loading on IS, worked around tjs bug --- public/language/en_GB/topic.json | 2 +- public/less/flags.less | 4 ++ public/src/admin/manage/flags.js | 27 +++++----- src/controllers/admin/flags.js | 14 ------ src/posts/flags.js | 86 ++++++++++++++++++++++++++------ src/socket.io/posts/flag.js | 2 +- src/views/admin/manage/flags.tpl | 19 ++++--- 7 files changed, 103 insertions(+), 51 deletions(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index e3479cb989..091bdf359f 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -49,7 +49,7 @@ "flag_manage_update": "Update Flag Status", "flag_manage_history_assignee": "Assigned to %1", "flag_manage_history_state": "Updated state to %1", - "flag_manage_history_notes": "Updates flag notes", + "flag_manage_history_notes": "Updated flag notes", "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.", diff --git a/public/less/flags.less b/public/less/flags.less index da44e61552..29b5998933 100644 --- a/public/less/flags.less +++ b/public/less/flags.less @@ -31,4 +31,8 @@ .user-icon-style(24px, 1.5rem); } } + + [component="posts/flag/history"] .avatar { + margin-right: 1rem; + } } \ No newline at end of file diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js index 3b235677ca..12327526ca 100644 --- a/public/src/admin/manage/flags.js +++ b/public/src/admin/manage/flags.js @@ -24,7 +24,8 @@ define('admin/manage/flags', [ handleGraphs(); updateFlagDetails(ajaxify.data.posts); - handleFormActions(); + + components.get('posts/flags').on('click', '[component="posts/flag/update"]', updateFlag); }; function handleDismiss() { @@ -93,10 +94,14 @@ define('admin/manage/flags', [ after: $('[data-next]').attr('data-next') }, function(data, done) { if (data.posts && data.posts.length) { - app.parseAndTranslate('admin/manage/flags', 'posts', {posts: data.posts}, function(html) { + app.parseAndTranslate('admin/manage/flags', 'posts', { + posts: data.posts, + assignees: ajaxify.data.assignees + }, function(html) { $('[data-next]').attr('data-next', data.next); $('.post-container').append(html); html.find('img:not(.not-responsive)').addClass('img-responsive'); + updateFlagDetails(data.posts); done(); }); } else { @@ -177,17 +182,15 @@ define('admin/manage/flags', [ }); } - function handleFormActions() { - components.get('posts/flag').find('[component="posts/flag/update"]').on('click', function() { - var pid = $(this).parents('[component="posts/flag"]').attr('data-pid'); - var formData = $($(this).parents('form').get(0)).serializeArray(); + function updateFlag() { + var pid = $(this).parents('[component="posts/flag"]').attr('data-pid'); + var formData = $($(this).parents('form').get(0)).serializeArray(); - socket.emit('posts.updateFlag', { - pid: pid, - data: formData - }, function(err) { - console.log(arguments); - }); + socket.emit('posts.updateFlag', { + pid: pid, + data: formData + }, function(err) { + console.log(arguments); }); } diff --git a/src/controllers/admin/flags.js b/src/controllers/admin/flags.js index 61413ce1f0..f21cc773ad 100644 --- a/src/controllers/admin/flags.js +++ b/src/controllers/admin/flags.js @@ -35,20 +35,6 @@ flagsController.get = function(req, res, next) { return next(err); } - // Parse out flag data into its own object inside each post hash - results.posts = results.posts.map(function(postObj) { - for(var prop in postObj) { - postObj.flagData = postObj.flagData || {}; - - if (postObj.hasOwnProperty(prop) && prop.startsWith('flag:')) { - postObj.flagData[prop.slice(5)] = postObj[prop]; - delete postObj[prop]; - } - } - - return postObj; - }); - // Minimise data set for assignees so tjs does less work results.assignees = results.assignees.map(function(userObj) { var keep = ['uid', 'username']; diff --git a/src/posts/flags.js b/src/posts/flags.js index 62956ee161..82ea31dd00 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -201,7 +201,42 @@ module.exports = function(Posts) { next(null, results.posts); }); }, - async.apply(Posts.expandFlagHistory) + async.apply(Posts.expandFlagHistory), + function(posts, next) { + // Parse out flag data into its own object inside each post hash + posts = posts.map(function(postObj) { + for(var prop in postObj) { + postObj.flagData = postObj.flagData || {}; + + if (postObj.hasOwnProperty(prop) && prop.startsWith('flag:')) { + postObj.flagData[prop.slice(5)] = postObj[prop]; + + if (prop === 'flag:state') { + switch(postObj[prop]) { + case 'open': + postObj.flagData.labelClass = 'info'; + break; + case 'wip': + postObj.flagData.labelClass = 'warning'; + break; + case 'resolved': + postObj.flagData.labelClass = 'success'; + break; + case 'rejected': + postObj.flagData.labelClass = 'danger'; + break; + } + } + + delete postObj[prop]; + } + } + + return postObj; + }); + + setImmediate(next.bind(null, null, posts)); + } ], callback); } @@ -231,11 +266,12 @@ module.exports = function(Posts) { ], callback); }; - Posts.updateFlagData = function(pid, flagObj, callback) { + Posts.updateFlagData = function(uid, pid, flagObj, callback) { // Retrieve existing flag data to compare for history-saving purposes var changes = []; var changeset = {}; var prop; + Posts.getPostData(pid, function(err, postData) { // Track new additions for(prop in flagObj) { @@ -270,6 +306,7 @@ module.exports = function(Posts) { case 'assignee': // intentional fall-through case 'state': history.unshift({ + uid: uid, type: property, value: flagObj[property], timestamp: Date.now() @@ -278,6 +315,7 @@ module.exports = function(Posts) { case 'notes': history.unshift({ + uid: uid, type: property, timestamp: Date.now() }); @@ -308,21 +346,37 @@ module.exports = function(Posts) { async.map(history, function(event, next) { event.timestampISO = new Date(event.timestamp).toISOString(); - if (event.type === 'assignee') { - user.getUserField(parseInt(event.value, 10), 'username', function(err, username) { - if (err) { - return next(err); - } + async.parallel([ + function(next) { + user.getUserFields(event.uid, ['username', 'picture'], function(err, userData) { + if (err) { + return next(err); + } - event.label = username || 'Unknown user'; - next(null, event); - }); - } else if (event.type === 'state') { - event.label = '[[topic:flag_manage_state_' + event.value + ']]'; - setImmediate(next.bind(null, null, event)); - } else { - setImmediate(next.bind(null, null, event)); - } + event.user = userData; + next(); + }); + }, + function(next) { + if (event.type === 'assignee') { + user.getUserField(parseInt(event.value, 10), 'username', function(err, username) { + if (err) { + return next(err); + } + + event.label = username || 'Unknown user'; + next(null); + }); + } else if (event.type === 'state') { + event.label = '[[topic:flag_manage_state_' + event.value + ']]'; + setImmediate(next); + } else { + setImmediate(next); + } + } + ], function(err) { + next(err, event); + }) }, function(err, history) { post['flag:history'] = history; next(null, post); diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js index 6ebf9c05d8..5a44461ffb 100644 --- a/src/socket.io/posts/flag.js +++ b/src/socket.io/posts/flag.js @@ -189,7 +189,7 @@ module.exports = function(SocketPosts) { return memo; }, payload); - next(null, data.pid, payload); + next(null, socket.uid, data.pid, payload); }, async.apply(posts.updateFlagData) ], callback); diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl index 1ffb5856b1..5a9dca8edf 100644 --- a/src/views/admin/manage/flags.tpl +++ b/src/views/admin/manage/flags.tpl @@ -56,13 +56,13 @@
    -
    +
    @@ -102,7 +102,7 @@
    {../user.icon:text}
    {../user.username} - : "{../reason}" + : "{posts.flagReasons.reason}" @@ -122,7 +122,7 @@
    @@ -147,11 +147,16 @@
    [[topic:flag_manage_no_history]]
    -
      +
      • -
        - [[topic:flag_manage_history_{../type}, {../label}]] +
        + + + +
        {../user.icon:text}
        + + [[topic:flag_manage_history_{posts.flagData.history.type}, {posts.flagData.history.label}]]
      From 4897e861fb4e9d6ab45506597dbc46ce791678bb Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 15 Sep 2016 13:03:46 -0400 Subject: [PATCH 6/6] handling errors :rage2: --- public/language/en_GB/topic.json | 1 + public/src/admin/manage/flags.js | 6 +++++- src/posts/flags.js | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index 091bdf359f..36daa3d044 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -50,6 +50,7 @@ "flag_manage_history_assignee": "Assigned to %1", "flag_manage_history_state": "Updated state to %1", "flag_manage_history_notes": "Updated flag notes", + "flag_manage_saved": "Flag Details Updated", "deleted_message": "This topic has been deleted. Only users with topic management privileges can see it.", diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js index 12327526ca..ae44e9232c 100644 --- a/public/src/admin/manage/flags.js +++ b/public/src/admin/manage/flags.js @@ -190,7 +190,11 @@ define('admin/manage/flags', [ pid: pid, data: formData }, function(err) { - console.log(arguments); + if (err) { + return app.alertError(err.message); + } else { + app.alertSuccess('[[topic:flag_manage_saved]]'); + } }); } diff --git a/src/posts/flags.js b/src/posts/flags.js index 82ea31dd00..424a54084f 100644 --- a/src/posts/flags.js +++ b/src/posts/flags.js @@ -273,6 +273,10 @@ module.exports = function(Posts) { var prop; Posts.getPostData(pid, function(err, postData) { + if (err) { + return callback(err); + } + // Track new additions for(prop in flagObj) { if (flagObj.hasOwnProperty(prop) && !postData.hasOwnProperty('flag:' + prop)) { @@ -378,6 +382,10 @@ module.exports = function(Posts) { next(err, event); }) }, function(err, history) { + if (err) { + return next(err); + } + post['flag:history'] = history; next(null, post); });