feat: show popular searches

isekai-main
Barış Soner Uşaklı 3 years ago
parent 63572c23ce
commit f4cf482a87

@ -56,8 +56,8 @@
"active-users.total": "Total", "active-users.total": "Total",
"active-users.connections": "Connections", "active-users.connections": "Connections",
"anonymous-registered-users": "Anonymous vs Registered Users", "guest-registered-users": "Guest vs Registered Users",
"anonymous": "Anonymous", "guest": "Guest",
"registered": "Registered", "registered": "Registered",
"user-presence": "User Presence", "user-presence": "User Presence",
@ -68,6 +68,7 @@
"unread": "Unread", "unread": "Unread",
"high-presence-topics": "High Presence Topics", "high-presence-topics": "High Presence Topics",
"popular-searches": "Popular Searches",
"graphs.page-views": "Page Views", "graphs.page-views": "Page Views",
"graphs.page-views-registered": "Page Views Registered", "graphs.page-views-registered": "Page Views Registered",
@ -75,7 +76,7 @@
"graphs.page-views-bot": "Page Views Bot", "graphs.page-views-bot": "Page Views Bot",
"graphs.unique-visitors": "Unique Visitors", "graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users", "graphs.registered-users": "Registered Users",
"graphs.anonymous-users": "Anonymous Users", "graphs.guest-users": "Guest Users",
"last-restarted-by": "Last restarted by", "last-restarted-by": "Last restarted by",
"no-users-browsing": "No users browsing", "no-users-browsing": "No users browsing",

@ -4,6 +4,7 @@
"dashboard/logins": "Logins", "dashboard/logins": "Logins",
"dashboard/users": "Users", "dashboard/users": "Users",
"dashboard/topics": "Topics", "dashboard/topics": "Topics",
"dashboard/searches": "Searches",
"section-general": "General", "section-general": "General",
"section-manage": "Manage", "section-manage": "Manage",

@ -124,7 +124,7 @@
border-color: rgba(151,187,205,1); border-color: rgba(151,187,205,1);
background-color: rgba(151,187,205,0.2); background-color: rgba(151,187,205,0.2);
} }
&.anonymous { &.guest {
border-color: #46BFBD; border-color: #46BFBD;
background-color: #5AD3D1; background-color: #5AD3D1;
} }

@ -78,6 +78,8 @@ paths:
$ref: 'read/admin/dashboard/users.yaml' $ref: 'read/admin/dashboard/users.yaml'
/api/admin/dashboard/topics: /api/admin/dashboard/topics:
$ref: 'read/admin/dashboard/topics.yaml' $ref: 'read/admin/dashboard/topics.yaml'
/api/admin/dashboard/searches:
$ref: 'read/admin/dashboard/searches.yaml'
"/api/admin/settings/{term}": "/api/admin/settings/{term}":
$ref: 'read/admin/settings/term.yaml' $ref: 'read/admin/settings/term.yaml'
/api/admin/settings/languages: /api/admin/settings/languages:

@ -0,0 +1,25 @@
get:
tags:
- admin
summary: Get detailed user registration analytics
responses:
"200":
description: A JSON object containing popular searches.
content:
application/json:
schema:
allOf:
- type: object
properties:
searches:
type: array
items:
type: object
properties:
value:
type: string
description: The string that was searched
score:
type: number
description: Number of times this string has been searched
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps

@ -150,7 +150,7 @@ define('admin/dashboard', ['Chart', 'translator', 'benchpress', 'bootbox'], func
t.translateKey('admin/dashboard:graphs.page-views-bot', []), t.translateKey('admin/dashboard:graphs.page-views-bot', []),
t.translateKey('admin/dashboard:graphs.unique-visitors', []), t.translateKey('admin/dashboard:graphs.unique-visitors', []),
t.translateKey('admin/dashboard:graphs.registered-users', []), t.translateKey('admin/dashboard:graphs.registered-users', []),
t.translateKey('admin/dashboard:graphs.anonymous-users', []), t.translateKey('admin/dashboard:graphs.guest-users', []),
t.translateKey('admin/dashboard:on-categories', []), t.translateKey('admin/dashboard:on-categories', []),
t.translateKey('admin/dashboard:reading-posts', []), t.translateKey('admin/dashboard:reading-posts', []),
t.translateKey('admin/dashboard:browsing-topics', []), t.translateKey('admin/dashboard:browsing-topics', []),
@ -469,11 +469,11 @@ define('admin/dashboard', ['Chart', 'translator', 'benchpress', 'bootbox'], func
}); });
} }
function updateRegisteredGraph(registered, anonymous) { function updateRegisteredGraph(registered, guest) {
$('#analytics-legend .registered').parent().find('.count').text(registered); $('#analytics-legend .registered').parent().find('.count').text(registered);
$('#analytics-legend .anonymous').parent().find('.count').text(anonymous); $('#analytics-legend .guest').parent().find('.count').text(guest);
graphs.registered.data.datasets[0].data[0] = registered; graphs.registered.data.datasets[0].data[0] = registered;
graphs.registered.data.datasets[0].data[1] = anonymous; graphs.registered.data.datasets[0].data[1] = guest;
graphs.registered.update(); graphs.registered.update();
} }

@ -4,6 +4,7 @@ const nconf = require('nconf');
const semver = require('semver'); const semver = require('semver');
const winston = require('winston'); const winston = require('winston');
const _ = require('lodash'); const _ = require('lodash');
const validator = require('validator');
const versions = require('../../admin/versions'); const versions = require('../../admin/versions');
const db = require('../../database'); const db = require('../../database');
@ -18,12 +19,13 @@ const emailer = require('../../emailer');
const dashboardController = module.exports; const dashboardController = module.exports;
dashboardController.get = async function (req, res) { dashboardController.get = async function (req, res) {
const [stats, notices, latestVersion, lastrestart, isAdmin] = await Promise.all([ const [stats, notices, latestVersion, lastrestart, isAdmin, popularSearches] = await Promise.all([
getStats(), getStats(),
getNotices(), getNotices(),
getLatestVersion(), getLatestVersion(),
getLastRestart(), getLastRestart(),
user.isAdministrator(req.uid), user.isAdministrator(req.uid),
getPopularSearches(),
]); ]);
const version = nconf.get('version'); const version = nconf.get('version');
@ -38,6 +40,7 @@ dashboardController.get = async function (req, res) {
canRestart: !!process.send, canRestart: !!process.send,
lastrestart: lastrestart, lastrestart: lastrestart,
showSystemControls: isAdmin, showSystemControls: isAdmin,
popularSearches: popularSearches,
}); });
}; };
@ -238,6 +241,11 @@ async function getLastRestart() {
return lastrestart; return lastrestart;
} }
async function getPopularSearches() {
const searches = await db.getSortedSetRevRangeWithScores('searches:all', 0, 9);
return searches.map(s => ({ value: validator.escape(String(s.value)), score: s.score }));
}
dashboardController.getLogins = async (req, res) => { dashboardController.getLogins = async (req, res) => {
let stats = await getStats(); let stats = await getStats();
stats = stats.filter(stat => stat.name === '[[admin/dashboard:logins]]').map(({ ...stat }) => { stats = stats.filter(stat => stat.name === '[[admin/dashboard:logins]]').map(({ ...stat }) => {
@ -327,3 +335,10 @@ dashboardController.getTopics = async (req, res) => {
topics: topicData, topics: topicData,
}); });
}; };
dashboardController.getSearches = async (req, res) => {
const searches = await db.getSortedSetRevRangeWithScores('searches:all', 0, 99);
res.render('admin/dashboard/searches', {
searches: searches.map(s => ({ value: validator.escape(String(s.value)), score: s.score })),
});
};

@ -89,7 +89,9 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl
} }
} catch (_err) { } catch (_err) {
winston.error(`${req.originalUrl}\n${_err.stack}`); winston.error(`${req.originalUrl}\n${_err.stack}`);
res.status(500).send(_err.message); if (!res.headersSent) {
res.status(500).send(_err.message);
}
} }
}; };

@ -11,6 +11,7 @@ module.exports = function (app, name, middleware, controllers) {
helpers.setupAdminPageRoute(app, `/${name}/dashboard/logins`, middleware, middlewares, controllers.admin.dashboard.getLogins); helpers.setupAdminPageRoute(app, `/${name}/dashboard/logins`, middleware, middlewares, controllers.admin.dashboard.getLogins);
helpers.setupAdminPageRoute(app, `/${name}/dashboard/users`, middleware, middlewares, controllers.admin.dashboard.getUsers); helpers.setupAdminPageRoute(app, `/${name}/dashboard/users`, middleware, middlewares, controllers.admin.dashboard.getUsers);
helpers.setupAdminPageRoute(app, `/${name}/dashboard/topics`, middleware, middlewares, controllers.admin.dashboard.getTopics); helpers.setupAdminPageRoute(app, `/${name}/dashboard/topics`, middleware, middlewares, controllers.admin.dashboard.getTopics);
helpers.setupAdminPageRoute(app, `/${name}/dashboard/searches`, middleware, middlewares, controllers.admin.dashboard.getSearches);
helpers.setupAdminPageRoute(app, `/${name}/manage/categories`, middleware, middlewares, controllers.admin.categories.getAll); helpers.setupAdminPageRoute(app, `/${name}/manage/categories`, middleware, middlewares, controllers.admin.categories.getAll);
helpers.setupAdminPageRoute(app, `/${name}/manage/categories/:category_id`, middleware, middlewares, controllers.admin.categories.get); helpers.setupAdminPageRoute(app, `/${name}/manage/categories/:category_id`, middleware, middlewares, controllers.admin.categories.get);

@ -45,6 +45,7 @@ async function searchInContent(data) {
async function doSearch(type, searchIn) { async function doSearch(type, searchIn) {
if (searchIn.includes(data.searchIn)) { if (searchIn.includes(data.searchIn)) {
await recordSearch(data.query);
return await plugins.hooks.fire('filter:search.query', { return await plugins.hooks.fire('filter:search.query', {
index: type, index: type,
content: data.query, content: data.query,
@ -94,6 +95,13 @@ async function searchInContent(data) {
return Object.assign(returnData, metadata); return Object.assign(returnData, metadata);
} }
async function recordSearch(query) {
const cleanedQuery = String(query).trim().toLowerCase().substr(0, 255);
if (cleanedQuery.length > 2) {
await db.sortedSetIncrBy('searches:all', 1, cleanedQuery);
}
}
async function filterAndSort(pids, data) { async function filterAndSort(pids, data) {
if (data.sortBy === 'relevance' && !data.replies && !data.timeRange && !data.hasTags && !plugins.hooks.hasListeners('filter:search.filterAndSort')) { if (data.sortBy === 'relevance' && !data.replies && !data.timeRange && !data.hasTags && !plugins.hooks.hasListeners('filter:search.filterAndSort')) {
return pids; return pids;

@ -4,22 +4,22 @@
<!-- IMPORT admin/partials/dashboard/stats.tpl --> <!-- IMPORT admin/partials/dashboard/stats.tpl -->
<div class="row"> <div class="row">
<div class="col-lg-4"> <div class="col-lg-3">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">[[admin/dashboard:anonymous-registered-users]]</div> <div class="panel-heading">[[admin/dashboard:guest-registered-users]]</div>
<div class="panel-body"> <div class="panel-body">
<div class="graph-container pie-chart legend-down"> <div class="graph-container pie-chart legend-down">
<canvas id="analytics-registered"></canvas> <canvas id="analytics-registered"></canvas>
<ul class="graph-legend" id="analytics-legend"> <ul class="graph-legend" id="analytics-legend">
<li><div class="registered"></div><span>(<span class="count"></span>) [[admin/dashboard:registered]]</span></li> <li><div class="registered"></div><span>(<span class="count"></span>) [[admin/dashboard:registered]]</span></li>
<li><div class="anonymous"></div><span>(<span class="count"></span>) [[admin/dashboard:anonymous]]</span></li> <li><div class="guest"></div><span>(<span class="count"></span>) [[admin/dashboard:guest]]</span></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-3">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">[[admin/dashboard:user-presence]]</div> <div class="panel-heading">[[admin/dashboard:user-presence]]</div>
<div class="panel-body"> <div class="panel-body">
@ -36,7 +36,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-3">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">[[admin/dashboard:high-presence-topics]]</div> <div class="panel-heading">[[admin/dashboard:high-presence-topics]]</div>
<div class="panel-body"> <div class="panel-body">
@ -47,6 +47,20 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-3">
<div class="panel panel-default">
<div class="panel-heading">[[admin/dashboard:popular-searches]]</div>
<div class="panel-body">
<div class="graph-container pie-chart legend-down">
<ul class="graph-legend" id="popular-searches-legend">
{{{ each popularSearches}}}
<li>({popularSearches.score}) {popularSearches.value}</li>
{{{ end }}}
</ul>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>

@ -0,0 +1,25 @@
<div class="row dashboard">
<div class="col-xs-12">
<a class="btn btn-link" href="{config.relative_path}/admin/dashboard">
<i class="fa fa-chevron-left"></i>
[[admin/dashboard:back-to-dashboard]]
</a>
<table class="table table-striped search-list">
<tbody>
{{{ if !searches.length}}}
<tr>
<td colspan=4" class="text-center"><em>[[admin/dashboard:details.no-searches]]</em></td>
</tr>
{{{ end }}}
{{{ each searches }}}
<tr>
<td>{searches.value}</a></td>
<td class="text-right">{searches.score}</td>
</tr>
{{{ end }}}
</tbody>
</table>
</div>
</div>

@ -184,6 +184,7 @@
<li><a href="{relative_path}/admin/dashboard/logins">[[admin/menu:dashboard/logins]]</a></li> <li><a href="{relative_path}/admin/dashboard/logins">[[admin/menu:dashboard/logins]]</a></li>
<li><a href="{relative_path}/admin/dashboard/users">[[admin/menu:dashboard/users]]</a></li> <li><a href="{relative_path}/admin/dashboard/users">[[admin/menu:dashboard/users]]</a></li>
<li><a href="{relative_path}/admin/dashboard/topics">[[admin/menu:dashboard/topics]]</a></li> <li><a href="{relative_path}/admin/dashboard/topics">[[admin/menu:dashboard/topics]]</a></li>
<li><a href="{relative_path}/admin/dashboard/searches">[[admin/menu:dashboard/searches]]</a></li>
</ul> </ul>
</li> </li>
{{{ end }}} {{{ end }}}

Loading…
Cancel
Save