diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json index 0c1f32b88e..7b9193680f 100644 --- a/public/language/en-GB/flags.json +++ b/public/language/en-GB/flags.json @@ -6,8 +6,11 @@ "no-flags": "Hooray! No flags found.", "assignee": "Assignee", "update": "Update", + "updated": "Updated", "notes": "Flag Notes", "add-note": "Add Note", + "history": "Flag History", + "back": "Back to Flags List", "state": "State", "state-open": "New/Open", diff --git a/public/src/client/flags/detail.js b/public/src/client/flags/detail.js index d23a1941b5..aef21ec925 100644 --- a/public/src/client/flags/detail.js +++ b/public/src/client/flags/detail.js @@ -2,7 +2,7 @@ /* globals define */ -define('forum/flags/detail', ['components'], function (components) { +define('forum/flags/detail', ['components', 'translator'], function (components, translator) { var Flags = {}; Flags.init = function () { @@ -18,11 +18,12 @@ define('forum/flags/detail', ['components'], function (components) { socket.emit('flags.update', { flagId: ajaxify.data.flagId, data: $('#attributes').serializeArray() - }, function (err) { + }, function (err, history) { if (err) { return app.alertError(err.message); } else { app.alertSuccess('[[flags:updated]]'); + Flags.reloadHistory(history); } }); break; @@ -31,12 +32,13 @@ define('forum/flags/detail', ['components'], function (components) { socket.emit('flags.appendNote', { flagId: ajaxify.data.flagId, note: document.getElementById('note').value - }, function (err, notes) { + }, function (err, payload) { if (err) { return app.alertError(err.message); } else { app.alertSuccess('[[flags:note-added]]'); - Flags.reloadNotes(notes); + Flags.reloadNotes(payload.notes); + Flags.reloadHistory(payload.history); } }); break; @@ -56,5 +58,18 @@ define('forum/flags/detail', ['components'], function (components) { }); }; + Flags.reloadHistory = function (history) { + templates.parse('flags/detail', 'history', { + history: history + }, function (html) { + translator.translate(html, function (translated) { + var wrapperEl = components.get('flag/history'); + wrapperEl.empty(); + wrapperEl.html(translated); + wrapperEl.find('span.timeago').timeago(); + }); + }); + }; + return Flags; }); diff --git a/src/flags.js b/src/flags.js index 323a2a1e07..3dc1e51f32 100644 --- a/src/flags.js +++ b/src/flags.js @@ -24,7 +24,7 @@ Flags.get = function (flagId, callback) { // First stage async.apply(async.parallel, { base: async.apply(db.getObject.bind(db), 'flag:' + flagId), - history: async.apply(db.getSortedSetRevRange.bind(db), 'flag:' + flagId + ':history', 0, -1), + history: async.apply(Flags.getHistory, flagId), notes: async.apply(Flags.getNotes, flagId) }), function (data, next) { @@ -156,7 +156,7 @@ Flags.getNotes = function (flagId, callback) { next(null, notes, uids); }, function (notes, uids, next) { - user.getUsersData(uids, function (err, users) { + user.getUsersFields(uids, ['username', 'userslug', 'picture'], function (err, users) { if (err) { return next(err); } @@ -398,13 +398,61 @@ Flags.update = function (flagId, uid, changeset, callback) { async.apply(db.setObject, 'flag:' + flagId, changeset), // Append history async.apply(Flags.appendHistory, flagId, uid, Object.keys(changeset)) - ], next); + ], function (err, data) { + return next(err); + }); } ], callback); }; +Flags.getHistory = function (flagId, callback) { + var history; + var uids = []; + async.waterfall([ + async.apply(db.getSortedSetRevRangeWithScores.bind(db), 'flag:' + flagId + ':history', 0, -1), + function (_history, next) { + history = _history.map(function (entry) { + try { + entry.value = JSON.parse(entry.value); + } catch (e) { + return callback(e); + } + + uids.push(entry.value[0]); + + return { + uid: entry.value[0], + fields: entry.value[1], + datetime: entry.score, + datetimeISO: new Date(entry.score).toISOString() + }; + }); + + user.getUsersFields(uids, ['username', 'userslug', 'picture'], next); + } + ], function (err, users) { + if (err) { + return callback(err); + } + + history = history.map(function (event, idx) { + event.user = users[idx]; + return event; + }); + + callback(null, history); + }); +}; + Flags.appendHistory = function (flagId, uid, changeset, callback) { - return callback(); + var payload; + try { + payload = JSON.stringify([uid, changeset, Date.now()]); + } catch (e) { + return callback(e); + } + + db.sortedSetAdd('flag:' + flagId + ':history', Date.now(), payload, callback); }; Flags.appendNote = function (flagId, uid, note, callback) { @@ -417,7 +465,7 @@ Flags.appendNote = function (flagId, uid, note, callback) { async.waterfall([ async.apply(db.sortedSetAdd, 'flag:' + flagId + ':notes', Date.now(), payload), - async.apply(Flags.getNotes, flagId) + async.apply(Flags.appendHistory, flagId, uid, ['notes']) ], callback); }; diff --git a/src/socket.io/flags.js b/src/socket.io/flags.js index 28db7551a6..8b66dd094c 100644 --- a/src/socket.io/flags.js +++ b/src/socket.io/flags.js @@ -136,7 +136,8 @@ SocketFlags.update = function (socket, data, callback) { }, payload); flags.update(data.flagId, socket.uid, payload, next); - } + }, + async.apply(flags.getHistory, data.flagId) ], callback); }; @@ -160,6 +161,12 @@ SocketFlags.appendNote = function (socket, data, callback) { } flags.appendNote(data.flagId, socket.uid, data.note, next); + }, + function (next) { + async.parallel({ + "notes": async.apply(flags.getNotes, data.flagId), + "history": async.apply(flags.getHistory, data.flagId) + }, next); } ], callback); }; diff --git a/src/user/data.js b/src/user/data.js index 2e5bfb2218..5716208ae5 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -34,11 +34,20 @@ module.exports = function (User) { } } - if (!Array.isArray(uids) || !uids.length) { + // Eliminate duplicates and build ref table + var uniqueUids = uids.filter(function (uid, index) { + return index === uids.indexOf(uid); + }); + var ref = uniqueUids.reduce(function (memo, cur, idx) { + memo[cur] = idx; + return memo; + }, {}); + + if (!Array.isArray(uniqueUids) || !uniqueUids.length) { return callback(null, []); } - var keys = uids.map(function (uid) { + var keys = uniqueUids.map(function (uid) { return 'user:' + uid; }); @@ -60,6 +69,10 @@ module.exports = function (User) { return callback(err); } + users = uids.map(function (uid) { + return users[ref[uid]]; + }); + modifyUserData(users, fieldsToRemove, callback); }); };