feat: flags list sorting, closes #8569

v1.18.x
Julian Lam 5 years ago
parent cabe62a06c
commit 346db0d84d

@ -57,6 +57,11 @@
"state-rejected": "Rejected", "state-rejected": "Rejected",
"no-assignee": "Not Assigned", "no-assignee": "Not Assigned",
"sort": "Sort by",
"sort-newest": "Newest first",
"sort-oldest": "Oldest first",
"sort-reports": "Most reports",
"modal-title": "Report Inappropriate Content", "modal-title": "Report Inappropriate Content",
"modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.",
"modal-reason-spam": "Spam", "modal-reason-spam": "Spam",

@ -28,6 +28,7 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart)
filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]); filtersEl.find('[name="' + filter + '"]').val(ajaxify.data.filters[filter]);
} }
} }
filtersEl.find('[name="sort"]').val(ajaxify.data.sort);
document.getElementById('apply-filters').addEventListener('click', function () { document.getElementById('apply-filters').addEventListener('click', function () {
var payload = filtersEl.serializeArray().filter(function (item) { var payload = filtersEl.serializeArray().filter(function (item) {

@ -18,18 +18,23 @@ const modsController = module.exports;
modsController.flags = {}; modsController.flags = {};
modsController.flags.list = async function (req, res, next) { modsController.flags.list = async function (req, res, next) {
let validFilters = ['assignee', 'state', 'reporterId', 'type', 'targetUid', 'cid', 'quick', 'page', 'perPage']; const validFilters = ['assignee', 'state', 'reporterId', 'type', 'targetUid', 'cid', 'quick', 'page', 'perPage'];
const validSorts = ['newest', 'oldest', 'reports'];
// Reset filters if explicitly requested // Reset filters if explicitly requested
if (parseInt(req.query.reset, 10) === 1) { if (parseInt(req.query.reset, 10) === 1) {
delete req.session.flags_filters; delete req.session.flags_filters;
delete req.session.flags_sort;
} }
const [isAdminOrGlobalMod, moderatedCids, data] = await Promise.all([ const results = await Promise.all([
user.isAdminOrGlobalMod(req.uid), user.isAdminOrGlobalMod(req.uid),
user.getModeratedCids(req.uid), user.getModeratedCids(req.uid),
plugins.fireHook('filter:flags.validateFilters', { filters: validFilters }), plugins.fireHook('filter:flags.validateFilters', { filters: validFilters }),
plugins.fireHook('filter:flags.validateSort', { sorts: validSorts }),
]); ]);
const [isAdminOrGlobalMod, moderatedCids,, { sorts }] = results;
let [,, { filters }] = results;
if (!(isAdminOrGlobalMod || !!moderatedCids.length)) { if (!(isAdminOrGlobalMod || !!moderatedCids.length)) {
return next(new Error('[[error:no-privileges]]')); return next(new Error('[[error:no-privileges]]'));
@ -39,10 +44,8 @@ modsController.flags.list = async function (req, res, next) {
res.locals.cids = moderatedCids; res.locals.cids = moderatedCids;
} }
validFilters = data.filters; // Parse query string params for filters, eliminate non-valid filters
filters = filters.reduce(function (memo, cur) {
// Parse query string params for filters
let filters = validFilters.reduce(function (memo, cur) {
if (req.query.hasOwnProperty(cur)) { if (req.query.hasOwnProperty(cur)) {
memo[cur] = req.query[cur]; memo[cur] = req.query[cur];
} }
@ -71,15 +74,32 @@ modsController.flags.list = async function (req, res, next) {
} }
// Pagination doesn't count as a filter // Pagination doesn't count as a filter
if (Object.keys(filters).length === 1 && filters.hasOwnProperty('page')) { if (
(Object.keys(filters).length === 1 && filters.hasOwnProperty('page')) ||
(Object.keys(filters).length === 2 && filters.hasOwnProperty('page') && filters.hasOwnProperty('perPage'))
) {
hasFilter = false; hasFilter = false;
} }
// Save filters into session unless removed // Parse sort from query string
let sort;
if (!req.query.sort && req.session.hasOwnProperty('flags_sort')) {
sort = req.session.flags_sort;
} else {
sort = sorts.includes(req.query.sort) ? req.query.sort : null;
}
hasFilter = hasFilter || !!sort;
// Save filters and sorting into session unless removed
req.session.flags_filters = filters; req.session.flags_filters = filters;
req.session.flags_sort = sort;
const [flagsData, analyticsData, categoriesData] = await Promise.all([ const [flagsData, analyticsData, categoriesData] = await Promise.all([
flags.list(filters, req.uid), flags.list({
filters: filters,
sort: sort,
uid: req.uid,
}),
analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30), analytics.getDailyStatsForSet('analytics:flags', Date.now(), 30),
categories.buildForSelect(req.uid, 'read'), categories.buildForSelect(req.uid, 'read'),
]); ]);
@ -90,6 +110,7 @@ modsController.flags.list = async function (req, res, next) {
categories: filterCategories(res.locals.cids, categoriesData), categories: filterCategories(res.locals.cids, categoriesData),
hasFilter: hasFilter, hasFilter: hasFilter,
filters: filters, filters: filters,
sort: sort || 'newest',
title: '[[pages:flags]]', title: '[[pages:flags]]',
pagination: pagination.create(flagsData.page, flagsData.pageCount, req.query), pagination: pagination.create(flagsData.page, flagsData.pageCount, req.query),
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[pages:flags]]' }]), breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[pages:flags]]' }]),

@ -112,8 +112,8 @@ Flags.get = async function (flagId) {
return data.flag; return data.flag;
}; };
Flags.list = async function (filters, uid) { Flags.list = async function (data) {
filters = filters || {}; const filters = data.filters || {};
let sets = []; let sets = [];
const orSets = []; const orSets = [];
@ -125,7 +125,7 @@ Flags.list = async function (filters, uid) {
for (var type in filters) { for (var type in filters) {
if (filters.hasOwnProperty(type)) { if (filters.hasOwnProperty(type)) {
if (Flags._filters.hasOwnProperty(type)) { if (Flags._filters.hasOwnProperty(type)) {
Flags._filters[type](sets, orSets, filters[type], uid); Flags._filters[type](sets, orSets, filters[type], data.uid);
} else { } else {
winston.warn('[flags/list] No flag filter type found: ' + type); winston.warn('[flags/list] No flag filter type found: ' + type);
} }
@ -151,6 +151,8 @@ Flags.list = async function (filters, uid) {
} }
} }
flagIds = await Flags.sort(flagIds, data.sort);
// Create subset for parsing based on page number (n=20) // Create subset for parsing based on page number (n=20)
const flagsPerPage = Math.abs(parseInt(filters.perPage, 10) || 1); const flagsPerPage = Math.abs(parseInt(filters.perPage, 10) || 1);
const pageCount = Math.ceil(flagIds.length / flagsPerPage); const pageCount = Math.ceil(flagIds.length / flagsPerPage);
@ -174,19 +176,42 @@ Flags.list = async function (filters, uid) {
}); });
})); }));
const data = await plugins.fireHook('filter:flags.list', { const payload = await plugins.fireHook('filter:flags.list', {
flags: flags, flags: flags,
page: filters.page, page: filters.page,
uid: uid, uid: data.uid,
}); });
return { return {
flags: data.flags, flags: payload.flags,
page: data.page, page: payload.page,
pageCount: pageCount, pageCount: pageCount,
}; };
}; };
Flags.sort = async function (flagIds, sort) {
switch (sort) {
// 'newest' is not handled because that is default
case 'oldest':
flagIds = flagIds.reverse();
break;
case 'reports': {
const keys = flagIds.map(id => `flag:${id}:reports`);
const heat = await db.sortedSetsCard(keys);
const mapped = heat.map((el, i) => ({
index: i, heat: el,
}));
mapped.sort(function (a, b) {
return b.heat - a.heat;
});
flagIds = mapped.map(obj => flagIds[obj.index]);
break;
}
}
return flagIds;
};
Flags.validate = async function (payload) { Flags.validate = async function (payload) {
const [target, reporter] = await Promise.all([ const [target, reporter] = await Promise.all([
Flags.getTarget(payload.type, payload.id, payload.uid), Flags.getTarget(payload.type, payload.id, payload.uid),

Loading…
Cancel
Save