mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 08:36:12 +01:00
feat: show popular searches
This commit is contained in:
@@ -56,8 +56,8 @@
|
||||
"active-users.total": "Total",
|
||||
"active-users.connections": "Connections",
|
||||
|
||||
"anonymous-registered-users": "Anonymous vs Registered Users",
|
||||
"anonymous": "Anonymous",
|
||||
"guest-registered-users": "Guest vs Registered Users",
|
||||
"guest": "Guest",
|
||||
"registered": "Registered",
|
||||
|
||||
"user-presence": "User Presence",
|
||||
@@ -68,6 +68,7 @@
|
||||
"unread": "Unread",
|
||||
|
||||
"high-presence-topics": "High Presence Topics",
|
||||
"popular-searches": "Popular Searches",
|
||||
|
||||
"graphs.page-views": "Page Views",
|
||||
"graphs.page-views-registered": "Page Views Registered",
|
||||
@@ -75,7 +76,7 @@
|
||||
"graphs.page-views-bot": "Page Views Bot",
|
||||
"graphs.unique-visitors": "Unique Visitors",
|
||||
"graphs.registered-users": "Registered Users",
|
||||
"graphs.anonymous-users": "Anonymous Users",
|
||||
"graphs.guest-users": "Guest Users",
|
||||
"last-restarted-by": "Last restarted by",
|
||||
"no-users-browsing": "No users browsing",
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"dashboard/logins": "Logins",
|
||||
"dashboard/users": "Users",
|
||||
"dashboard/topics": "Topics",
|
||||
"dashboard/searches": "Searches",
|
||||
"section-general": "General",
|
||||
|
||||
"section-manage": "Manage",
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
border-color: rgba(151,187,205,1);
|
||||
background-color: rgba(151,187,205,0.2);
|
||||
}
|
||||
&.anonymous {
|
||||
&.guest {
|
||||
border-color: #46BFBD;
|
||||
background-color: #5AD3D1;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ paths:
|
||||
$ref: 'read/admin/dashboard/users.yaml'
|
||||
/api/admin/dashboard/topics:
|
||||
$ref: 'read/admin/dashboard/topics.yaml'
|
||||
/api/admin/dashboard/searches:
|
||||
$ref: 'read/admin/dashboard/searches.yaml'
|
||||
"/api/admin/settings/{term}":
|
||||
$ref: 'read/admin/settings/term.yaml'
|
||||
/api/admin/settings/languages:
|
||||
|
||||
25
public/openapi/read/admin/dashboard/searches.yaml
Normal file
25
public/openapi/read/admin/dashboard/searches.yaml
Normal file
@@ -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.unique-visitors', []),
|
||||
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:reading-posts', []),
|
||||
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 .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[1] = anonymous;
|
||||
graphs.registered.data.datasets[0].data[1] = guest;
|
||||
graphs.registered.update();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const nconf = require('nconf');
|
||||
const semver = require('semver');
|
||||
const winston = require('winston');
|
||||
const _ = require('lodash');
|
||||
const validator = require('validator');
|
||||
|
||||
const versions = require('../../admin/versions');
|
||||
const db = require('../../database');
|
||||
@@ -18,12 +19,13 @@ const emailer = require('../../emailer');
|
||||
const dashboardController = module.exports;
|
||||
|
||||
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(),
|
||||
getNotices(),
|
||||
getLatestVersion(),
|
||||
getLastRestart(),
|
||||
user.isAdministrator(req.uid),
|
||||
getPopularSearches(),
|
||||
]);
|
||||
const version = nconf.get('version');
|
||||
|
||||
@@ -38,6 +40,7 @@ dashboardController.get = async function (req, res) {
|
||||
canRestart: !!process.send,
|
||||
lastrestart: lastrestart,
|
||||
showSystemControls: isAdmin,
|
||||
popularSearches: popularSearches,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -238,6 +241,11 @@ async function getLastRestart() {
|
||||
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) => {
|
||||
let stats = await getStats();
|
||||
stats = stats.filter(stat => stat.name === '[[admin/dashboard:logins]]').map(({ ...stat }) => {
|
||||
@@ -327,3 +335,10 @@ dashboardController.getTopics = async (req, res) => {
|
||||
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) {
|
||||
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/users`, middleware, middlewares, controllers.admin.dashboard.getUsers);
|
||||
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/:category_id`, middleware, middlewares, controllers.admin.categories.get);
|
||||
|
||||
@@ -45,6 +45,7 @@ async function searchInContent(data) {
|
||||
|
||||
async function doSearch(type, searchIn) {
|
||||
if (searchIn.includes(data.searchIn)) {
|
||||
await recordSearch(data.query);
|
||||
return await plugins.hooks.fire('filter:search.query', {
|
||||
index: type,
|
||||
content: data.query,
|
||||
@@ -94,6 +95,13 @@ async function searchInContent(data) {
|
||||
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) {
|
||||
if (data.sortBy === 'relevance' && !data.replies && !data.timeRange && !data.hasTags && !plugins.hooks.hasListeners('filter:search.filterAndSort')) {
|
||||
return pids;
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
<!-- IMPORT admin/partials/dashboard/stats.tpl -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<div class="col-lg-3">
|
||||
<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="graph-container pie-chart legend-down">
|
||||
<canvas id="analytics-registered"></canvas>
|
||||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">[[admin/dashboard:user-presence]]</div>
|
||||
<div class="panel-body">
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">[[admin/dashboard:high-presence-topics]]</div>
|
||||
<div class="panel-body">
|
||||
@@ -47,6 +47,20 @@
|
||||
</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>
|
||||
|
||||
|
||||
25
src/views/admin/dashboard/searches.tpl
Normal file
25
src/views/admin/dashboard/searches.tpl
Normal file
@@ -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/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/searches">[[admin/menu:dashboard/searches]]</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{{{ end }}}
|
||||
|
||||
Reference in New Issue
Block a user