@momentumcms/plugins-analytics 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +289 -43
- package/index.js +285 -43
- package/lib/analytics-admin-routes.cjs +74 -9
- package/lib/analytics-admin-routes.js +72 -7
- package/lib/page-view-tracker.cjs +208 -0
- package/lib/page-view-tracker.js +189 -0
- package/package.json +9 -5
- package/src/index.d.ts +3 -1
- package/src/lib/analytics-config.types.d.ts +29 -0
- package/src/lib/analytics-plugin.d.ts +1 -1
- package/src/lib/collectors/page-view-collector.d.ts +29 -0
- package/src/lib/page-view-tracker.d.ts +21 -0
- package/src/lib/page-view-tracker.utils.d.ts +28 -0
- package/src/lib/utils/content-route-matcher.d.ts +42 -0
package/index.js
CHANGED
|
@@ -831,8 +831,210 @@ function createApiCollectorMiddleware(emitter) {
|
|
|
831
831
|
};
|
|
832
832
|
}
|
|
833
833
|
|
|
834
|
-
// libs/plugins/analytics/src/lib/
|
|
834
|
+
// libs/plugins/analytics/src/lib/collectors/page-view-collector.ts
|
|
835
835
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
836
|
+
|
|
837
|
+
// libs/plugins/analytics/src/lib/utils/content-route-matcher.ts
|
|
838
|
+
function escapeRegex(str) {
|
|
839
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
840
|
+
}
|
|
841
|
+
function compileContentRoute(collection, pattern) {
|
|
842
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
843
|
+
const paramNames = [];
|
|
844
|
+
let staticCount = 0;
|
|
845
|
+
const regexParts = segments.map((seg) => {
|
|
846
|
+
if (seg.startsWith(":")) {
|
|
847
|
+
paramNames.push(seg.slice(1));
|
|
848
|
+
return "([^/]+)";
|
|
849
|
+
}
|
|
850
|
+
staticCount++;
|
|
851
|
+
return escapeRegex(seg);
|
|
852
|
+
});
|
|
853
|
+
const regexStr = "^/" + regexParts.join("/") + "/?$";
|
|
854
|
+
return {
|
|
855
|
+
collection,
|
|
856
|
+
pattern,
|
|
857
|
+
regex: new RegExp(regexStr),
|
|
858
|
+
paramNames,
|
|
859
|
+
staticSegments: staticCount
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function compileContentRoutes(routes) {
|
|
863
|
+
const compiled = Object.entries(routes).map(
|
|
864
|
+
([collection, pattern]) => compileContentRoute(collection, pattern)
|
|
865
|
+
);
|
|
866
|
+
compiled.sort((a, b) => b.staticSegments - a.staticSegments);
|
|
867
|
+
return compiled;
|
|
868
|
+
}
|
|
869
|
+
function matchContentRoute(path, routes) {
|
|
870
|
+
for (const route of routes) {
|
|
871
|
+
const match = route.regex.exec(path);
|
|
872
|
+
if (match) {
|
|
873
|
+
const params = {};
|
|
874
|
+
for (let i = 0; i < route.paramNames.length; i++) {
|
|
875
|
+
params[route.paramNames[i]] = match[i + 1];
|
|
876
|
+
}
|
|
877
|
+
return { collection: route.collection, params };
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return void 0;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// libs/plugins/analytics/src/lib/collectors/page-view-collector.ts
|
|
884
|
+
var DEFAULT_EXCLUDE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
885
|
+
".js",
|
|
886
|
+
".css",
|
|
887
|
+
".ico",
|
|
888
|
+
".png",
|
|
889
|
+
".jpg",
|
|
890
|
+
".jpeg",
|
|
891
|
+
".gif",
|
|
892
|
+
".svg",
|
|
893
|
+
".webp",
|
|
894
|
+
".avif",
|
|
895
|
+
".woff",
|
|
896
|
+
".woff2",
|
|
897
|
+
".ttf",
|
|
898
|
+
".eot",
|
|
899
|
+
".map",
|
|
900
|
+
".json",
|
|
901
|
+
".xml",
|
|
902
|
+
".txt",
|
|
903
|
+
".mp4",
|
|
904
|
+
".webm",
|
|
905
|
+
".ogg",
|
|
906
|
+
".mp3"
|
|
907
|
+
]);
|
|
908
|
+
var ALWAYS_EXCLUDED_PREFIXES = [
|
|
909
|
+
"/api/",
|
|
910
|
+
"/admin",
|
|
911
|
+
"/__vite",
|
|
912
|
+
"/@fs/",
|
|
913
|
+
"/@id/",
|
|
914
|
+
"/.analog/",
|
|
915
|
+
"/node_modules/"
|
|
916
|
+
];
|
|
917
|
+
var ALWAYS_EXCLUDED_EXACT = /* @__PURE__ */ new Set([
|
|
918
|
+
"/api",
|
|
919
|
+
"/favicon.ico",
|
|
920
|
+
"/robots.txt",
|
|
921
|
+
"/sitemap.xml",
|
|
922
|
+
"/sitemap-index.xml",
|
|
923
|
+
"/health",
|
|
924
|
+
"/healthz",
|
|
925
|
+
"/ready",
|
|
926
|
+
"/.well-known/security.txt"
|
|
927
|
+
]);
|
|
928
|
+
var BOT_PATTERN = /bot|crawl|spider|slurp|bingpreview|mediapartners|facebookexternalhit|linkedinbot|twitterbot|whatsapp|telegrambot|discordbot|applebot|duckduckbot|yandex|baidu|sogou|ia_archiver|semrush|ahref|mj12bot|dotbot|petalbot|bytespider/i;
|
|
929
|
+
function isBot(ua) {
|
|
930
|
+
if (!ua)
|
|
931
|
+
return false;
|
|
932
|
+
return BOT_PATTERN.test(ua);
|
|
933
|
+
}
|
|
934
|
+
function shouldExcludePath(path, excludeExtensions, excludePaths) {
|
|
935
|
+
if (ALWAYS_EXCLUDED_EXACT.has(path))
|
|
936
|
+
return true;
|
|
937
|
+
for (const prefix of ALWAYS_EXCLUDED_PREFIXES) {
|
|
938
|
+
if (path.startsWith(prefix))
|
|
939
|
+
return true;
|
|
940
|
+
}
|
|
941
|
+
for (const pattern of excludePaths) {
|
|
942
|
+
if (path.startsWith(pattern))
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
const dotIndex = path.lastIndexOf(".");
|
|
946
|
+
if (dotIndex !== -1) {
|
|
947
|
+
const ext = path.slice(dotIndex).toLowerCase();
|
|
948
|
+
if (excludeExtensions.has(ext))
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
function extractUserId(req) {
|
|
954
|
+
if (!("user" in req))
|
|
955
|
+
return void 0;
|
|
956
|
+
const user = req["user"];
|
|
957
|
+
if (user == null || typeof user !== "object")
|
|
958
|
+
return void 0;
|
|
959
|
+
if (!("id" in user))
|
|
960
|
+
return void 0;
|
|
961
|
+
const id = user.id;
|
|
962
|
+
return typeof id === "string" ? id : void 0;
|
|
963
|
+
}
|
|
964
|
+
function createPageViewCollectorMiddleware(emitter, options = {}) {
|
|
965
|
+
const excludeExtensions = options.excludeExtensions ? new Set(options.excludeExtensions) : DEFAULT_EXCLUDE_EXTENSIONS;
|
|
966
|
+
const excludePaths = options.excludePaths ?? [];
|
|
967
|
+
const onlySuccessful = options.onlySuccessful !== false;
|
|
968
|
+
const trackBots = options.trackBots === true;
|
|
969
|
+
const compiledRoutes = options.contentRoutes ? compileContentRoutes(options.contentRoutes) : void 0;
|
|
970
|
+
return (req, res, next) => {
|
|
971
|
+
if (req.method !== "GET") {
|
|
972
|
+
next();
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const path = req.path;
|
|
976
|
+
if (shouldExcludePath(path, excludeExtensions, excludePaths)) {
|
|
977
|
+
next();
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const ua = req.headers["user-agent"];
|
|
981
|
+
if (!trackBots && isBot(ua)) {
|
|
982
|
+
next();
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const start = Date.now();
|
|
986
|
+
res.once("finish", () => {
|
|
987
|
+
if (onlySuccessful && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const duration = Date.now() - start;
|
|
991
|
+
const parsed = parseUserAgent(ua);
|
|
992
|
+
const refHeader = req.headers["referer"] ?? req.headers["referrer"];
|
|
993
|
+
const referrer = Array.isArray(refHeader) ? refHeader[0] : refHeader;
|
|
994
|
+
let contentCollection;
|
|
995
|
+
let contentSlug;
|
|
996
|
+
if (compiledRoutes) {
|
|
997
|
+
const routeMatch = matchContentRoute(path, compiledRoutes);
|
|
998
|
+
if (routeMatch) {
|
|
999
|
+
contentCollection = routeMatch.collection;
|
|
1000
|
+
contentSlug = routeMatch.params["slug"];
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const event = {
|
|
1004
|
+
id: randomUUID3(),
|
|
1005
|
+
category: "page",
|
|
1006
|
+
name: "page_view",
|
|
1007
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1008
|
+
userId: extractUserId(req),
|
|
1009
|
+
properties: {
|
|
1010
|
+
method: req.method,
|
|
1011
|
+
path,
|
|
1012
|
+
statusCode: res.statusCode,
|
|
1013
|
+
...contentCollection != null ? { collection: contentCollection } : {},
|
|
1014
|
+
...contentSlug != null ? { slug: contentSlug } : {}
|
|
1015
|
+
},
|
|
1016
|
+
context: {
|
|
1017
|
+
source: "server",
|
|
1018
|
+
url: req.originalUrl,
|
|
1019
|
+
referrer,
|
|
1020
|
+
userAgent: ua,
|
|
1021
|
+
ip: req.ip ?? req.socket.remoteAddress,
|
|
1022
|
+
device: parsed.device,
|
|
1023
|
+
browser: parsed.browser,
|
|
1024
|
+
os: parsed.os,
|
|
1025
|
+
duration,
|
|
1026
|
+
statusCode: res.statusCode,
|
|
1027
|
+
...contentCollection != null ? { collection: contentCollection } : {}
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
emitter(event);
|
|
1031
|
+
});
|
|
1032
|
+
next();
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// libs/plugins/analytics/src/lib/ingest-handler.ts
|
|
1037
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
836
1038
|
import { Router as createRouter } from "express";
|
|
837
1039
|
|
|
838
1040
|
// libs/core/src/lib/collections/define-collection.ts
|
|
@@ -1126,7 +1328,7 @@ function createIngestRouter(options) {
|
|
|
1126
1328
|
}
|
|
1127
1329
|
const partial = raw;
|
|
1128
1330
|
const event = {
|
|
1129
|
-
id:
|
|
1331
|
+
id: randomUUID4(),
|
|
1130
1332
|
category: partial.category ?? "custom",
|
|
1131
1333
|
name: partial.name ?? "unknown",
|
|
1132
1334
|
// Server-side timestamp (prevents client clock skew)
|
|
@@ -1732,6 +1934,42 @@ function matchesDocumentUrl(eventUrl, documentPath) {
|
|
|
1732
1934
|
}
|
|
1733
1935
|
return pathname === documentPath || pathname === `${documentPath}/`;
|
|
1734
1936
|
}
|
|
1937
|
+
async function queryEventsByDocument(queryFn, eventName, collection, documentId, documentPath, options) {
|
|
1938
|
+
const queryBase = {
|
|
1939
|
+
...options.category ? { category: options.category } : {},
|
|
1940
|
+
name: eventName,
|
|
1941
|
+
from: options.from,
|
|
1942
|
+
to: options.to,
|
|
1943
|
+
limit: 1e3
|
|
1944
|
+
};
|
|
1945
|
+
const [serverResult, allResult] = await Promise.all([
|
|
1946
|
+
queryFn({ ...queryBase, collection }),
|
|
1947
|
+
queryFn(queryBase)
|
|
1948
|
+
]);
|
|
1949
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1950
|
+
const results = [];
|
|
1951
|
+
for (const e of [...serverResult.events, ...allResult.events]) {
|
|
1952
|
+
if (seen.has(e.id))
|
|
1953
|
+
continue;
|
|
1954
|
+
seen.add(e.id);
|
|
1955
|
+
const matchesCollection = e.context.collection === collection || e.properties["collection"] === collection;
|
|
1956
|
+
const matchesSlug = typeof e.properties["slug"] === "string" && e.properties["slug"] === documentId;
|
|
1957
|
+
if (matchesCollection && matchesSlug)
|
|
1958
|
+
results.push(e);
|
|
1959
|
+
}
|
|
1960
|
+
if (results.length > 0)
|
|
1961
|
+
return results;
|
|
1962
|
+
const urlResult = await queryFn({ ...queryBase, search: documentPath });
|
|
1963
|
+
return urlResult.events.filter((e) => matchesDocumentUrl(e.context.url, documentPath));
|
|
1964
|
+
}
|
|
1965
|
+
function countByBlockType(events) {
|
|
1966
|
+
const map = /* @__PURE__ */ new Map();
|
|
1967
|
+
for (const event of events) {
|
|
1968
|
+
const bt = String(event.properties["blockType"] ?? "unknown");
|
|
1969
|
+
map.set(bt, (map.get(bt) ?? 0) + 1);
|
|
1970
|
+
}
|
|
1971
|
+
return map;
|
|
1972
|
+
}
|
|
1735
1973
|
function createContentPerformanceRouter(adapter) {
|
|
1736
1974
|
const router = Router2();
|
|
1737
1975
|
router.get("/content-performance", requireAdmin, async (req, res) => {
|
|
@@ -1750,16 +1988,15 @@ function createContentPerformanceRouter(adapter) {
|
|
|
1750
1988
|
return;
|
|
1751
1989
|
}
|
|
1752
1990
|
const documentPath = `/${collection}/${documentId}`;
|
|
1753
|
-
const
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
(e) => matchesDocumentUrl(e.context.url, documentPath)
|
|
1991
|
+
const queryFn = adapter.query.bind(adapter);
|
|
1992
|
+
const dateRange = { from, to };
|
|
1993
|
+
const pageViewEvents = await queryEventsByDocument(
|
|
1994
|
+
queryFn,
|
|
1995
|
+
"page_view",
|
|
1996
|
+
collection,
|
|
1997
|
+
documentId,
|
|
1998
|
+
documentPath,
|
|
1999
|
+
{ category: "page", ...dateRange }
|
|
1763
2000
|
);
|
|
1764
2001
|
const visitorSet = /* @__PURE__ */ new Set();
|
|
1765
2002
|
const referrerMap = /* @__PURE__ */ new Map();
|
|
@@ -1774,38 +2011,26 @@ function createContentPerformanceRouter(adapter) {
|
|
|
1774
2011
|
}
|
|
1775
2012
|
let blockEngagement;
|
|
1776
2013
|
try {
|
|
1777
|
-
const [
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2014
|
+
const [impressionEvents, hoverEvents] = await Promise.all([
|
|
2015
|
+
queryEventsByDocument(
|
|
2016
|
+
queryFn,
|
|
2017
|
+
"block_impression",
|
|
2018
|
+
collection,
|
|
2019
|
+
documentId,
|
|
2020
|
+
documentPath,
|
|
2021
|
+
dateRange
|
|
2022
|
+
),
|
|
2023
|
+
queryEventsByDocument(
|
|
2024
|
+
queryFn,
|
|
2025
|
+
"block_hover",
|
|
2026
|
+
collection,
|
|
2027
|
+
documentId,
|
|
2028
|
+
documentPath,
|
|
2029
|
+
dateRange
|
|
2030
|
+
)
|
|
1792
2031
|
]);
|
|
1793
|
-
const
|
|
1794
|
-
|
|
1795
|
-
);
|
|
1796
|
-
const hoverEvents = hoverResult.events.filter(
|
|
1797
|
-
(e) => matchesDocumentUrl(e.context.url, documentPath)
|
|
1798
|
-
);
|
|
1799
|
-
const impressionMap = /* @__PURE__ */ new Map();
|
|
1800
|
-
for (const event of impressionEvents) {
|
|
1801
|
-
const bt = String(event.properties["blockType"] ?? "unknown");
|
|
1802
|
-
impressionMap.set(bt, (impressionMap.get(bt) ?? 0) + 1);
|
|
1803
|
-
}
|
|
1804
|
-
const hoverMap = /* @__PURE__ */ new Map();
|
|
1805
|
-
for (const event of hoverEvents) {
|
|
1806
|
-
const bt = String(event.properties["blockType"] ?? "unknown");
|
|
1807
|
-
hoverMap.set(bt, (hoverMap.get(bt) ?? 0) + 1);
|
|
1808
|
-
}
|
|
2032
|
+
const impressionMap = countByBlockType(impressionEvents);
|
|
2033
|
+
const hoverMap = countByBlockType(hoverEvents);
|
|
1809
2034
|
const allTypes = /* @__PURE__ */ new Set([...impressionMap.keys(), ...hoverMap.keys()]);
|
|
1810
2035
|
if (allTypes.size > 0) {
|
|
1811
2036
|
blockEngagement = [];
|
|
@@ -2113,6 +2338,19 @@ function analyticsPlugin(config) {
|
|
|
2113
2338
|
position: "before-api"
|
|
2114
2339
|
});
|
|
2115
2340
|
}
|
|
2341
|
+
if (config.trackPageViews !== false) {
|
|
2342
|
+
const pageViewOptions = typeof config.trackPageViews === "object" ? config.trackPageViews : {};
|
|
2343
|
+
const pageViewCollector = createPageViewCollectorMiddleware(
|
|
2344
|
+
(event) => eventStore.add(event),
|
|
2345
|
+
pageViewOptions
|
|
2346
|
+
);
|
|
2347
|
+
registerMiddleware({
|
|
2348
|
+
path: "/",
|
|
2349
|
+
handler: pageViewCollector,
|
|
2350
|
+
position: "root"
|
|
2351
|
+
});
|
|
2352
|
+
logger.info("Page view tracking enabled");
|
|
2353
|
+
}
|
|
2116
2354
|
if (config.contentPerformance !== false) {
|
|
2117
2355
|
const contentPerfRouter = createContentPerformanceRouter(config.adapter);
|
|
2118
2356
|
registerMiddleware({
|
|
@@ -2197,16 +2435,20 @@ export {
|
|
|
2197
2435
|
TrackingRules,
|
|
2198
2436
|
analyticsPlugin,
|
|
2199
2437
|
attachBlockTracking,
|
|
2438
|
+
compileContentRoutes,
|
|
2200
2439
|
createAnalyticsMiddleware,
|
|
2201
2440
|
createAnalyticsQueryRouter,
|
|
2202
2441
|
createApiCollectorMiddleware,
|
|
2203
2442
|
createContentPerformanceRouter,
|
|
2204
2443
|
createIngestRouter,
|
|
2444
|
+
createPageViewCollectorMiddleware,
|
|
2205
2445
|
createRuleEngine,
|
|
2206
2446
|
createTracker,
|
|
2207
2447
|
createTrackingRulesRouter,
|
|
2208
2448
|
injectBlockAnalyticsFields,
|
|
2209
2449
|
injectCollectionCollector,
|
|
2450
|
+
isBot,
|
|
2451
|
+
matchContentRoute,
|
|
2210
2452
|
parseUserAgent,
|
|
2211
2453
|
postgresAnalyticsAdapter
|
|
2212
2454
|
};
|
|
@@ -291,12 +291,13 @@ var analytics_dashboard_page_exports = {};
|
|
|
291
291
|
__export(analytics_dashboard_page_exports, {
|
|
292
292
|
AnalyticsDashboardPage: () => AnalyticsDashboardPage
|
|
293
293
|
});
|
|
294
|
-
var import_core4, import_common2, import_ui2, import_core5, import_outline2, AnalyticsDashboardPage;
|
|
294
|
+
var import_core4, import_common2, import_router, import_ui2, import_core5, import_outline2, AnalyticsDashboardPage;
|
|
295
295
|
var init_analytics_dashboard_page = __esm({
|
|
296
296
|
"libs/plugins/analytics/src/lib/dashboard/analytics-dashboard.page.ts"() {
|
|
297
297
|
"use strict";
|
|
298
298
|
import_core4 = require("@angular/core");
|
|
299
299
|
import_common2 = require("@angular/common");
|
|
300
|
+
import_router = require("@angular/router");
|
|
300
301
|
import_ui2 = require("@momentumcms/ui");
|
|
301
302
|
import_core5 = require("@ng-icons/core");
|
|
302
303
|
import_outline2 = require("@ng-icons/heroicons/outline");
|
|
@@ -306,6 +307,8 @@ var init_analytics_dashboard_page = __esm({
|
|
|
306
307
|
constructor() {
|
|
307
308
|
this.analytics = (0, import_core4.inject)(AnalyticsService);
|
|
308
309
|
this.platformId = (0, import_core4.inject)(import_core4.PLATFORM_ID);
|
|
310
|
+
this.router = (0, import_core4.inject)(import_router.Router);
|
|
311
|
+
this.route = (0, import_core4.inject)(import_router.ActivatedRoute);
|
|
309
312
|
/** Date range options */
|
|
310
313
|
this.dateRanges = [
|
|
311
314
|
{
|
|
@@ -343,15 +346,12 @@ var init_analytics_dashboard_page = __esm({
|
|
|
343
346
|
this.currentPage = (0, import_core4.signal)(1);
|
|
344
347
|
/** Events per page */
|
|
345
348
|
this.pageSize = 20;
|
|
346
|
-
/**
|
|
349
|
+
/** Events from server query (filtered server-side by category when selected) */
|
|
347
350
|
this.filteredEvents = (0, import_core4.computed)(() => {
|
|
348
351
|
const result = this.analytics.events();
|
|
349
352
|
if (!result)
|
|
350
353
|
return [];
|
|
351
|
-
|
|
352
|
-
if (cat === "all")
|
|
353
|
-
return result.events;
|
|
354
|
-
return result.events.filter((e) => e.category === cat);
|
|
354
|
+
return result.events;
|
|
355
355
|
});
|
|
356
356
|
/** Total pages for pagination */
|
|
357
357
|
this.totalPages = (0, import_core4.computed)(() => {
|
|
@@ -371,6 +371,22 @@ var init_analytics_dashboard_page = __esm({
|
|
|
371
371
|
ngOnInit() {
|
|
372
372
|
if (!(0, import_common2.isPlatformBrowser)(this.platformId))
|
|
373
373
|
return;
|
|
374
|
+
const params = this.route.snapshot.queryParams;
|
|
375
|
+
if (params["range"] && this.dateRanges.some((r) => r.value === params["range"])) {
|
|
376
|
+
this.selectedRange.set(params["range"]);
|
|
377
|
+
}
|
|
378
|
+
if (params["category"] && this.categoryFilters.some((c) => c.value === params["category"])) {
|
|
379
|
+
this.selectedCategory.set(params["category"]);
|
|
380
|
+
}
|
|
381
|
+
if (params["search"]) {
|
|
382
|
+
this.searchTerm.set(params["search"]);
|
|
383
|
+
}
|
|
384
|
+
if (params["page"]) {
|
|
385
|
+
const page = parseInt(params["page"], 10);
|
|
386
|
+
if (!isNaN(page) && page > 0) {
|
|
387
|
+
this.currentPage.set(page);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
374
390
|
void this.refresh();
|
|
375
391
|
}
|
|
376
392
|
/**
|
|
@@ -381,13 +397,15 @@ var init_analytics_dashboard_page = __esm({
|
|
|
381
397
|
const from = dateRange?.getFrom();
|
|
382
398
|
const search = this.searchTerm() || void 0;
|
|
383
399
|
const page = this.currentPage();
|
|
400
|
+
const category = this.selectedCategory();
|
|
384
401
|
await Promise.all([
|
|
385
402
|
this.analytics.fetchSummary({ from }),
|
|
386
403
|
this.analytics.queryEvents({
|
|
387
404
|
limit: this.pageSize,
|
|
388
405
|
page,
|
|
389
406
|
from,
|
|
390
|
-
search
|
|
407
|
+
search,
|
|
408
|
+
category: category !== "all" ? category : void 0
|
|
391
409
|
})
|
|
392
410
|
]);
|
|
393
411
|
}
|
|
@@ -397,13 +415,17 @@ var init_analytics_dashboard_page = __esm({
|
|
|
397
415
|
setDateRange(range) {
|
|
398
416
|
this.selectedRange.set(range.value);
|
|
399
417
|
this.currentPage.set(1);
|
|
418
|
+
this.syncUrlParams();
|
|
400
419
|
void this.refresh();
|
|
401
420
|
}
|
|
402
421
|
/**
|
|
403
|
-
* Set category filter.
|
|
422
|
+
* Set category filter and re-query server.
|
|
404
423
|
*/
|
|
405
424
|
setCategory(category) {
|
|
406
425
|
this.selectedCategory.set(category);
|
|
426
|
+
this.currentPage.set(1);
|
|
427
|
+
this.syncUrlParams();
|
|
428
|
+
void this.refresh();
|
|
407
429
|
}
|
|
408
430
|
/**
|
|
409
431
|
* Handle search input.
|
|
@@ -413,6 +435,7 @@ var init_analytics_dashboard_page = __esm({
|
|
|
413
435
|
if (target instanceof HTMLInputElement) {
|
|
414
436
|
this.searchTerm.set(target.value);
|
|
415
437
|
this.currentPage.set(1);
|
|
438
|
+
this.syncUrlParams();
|
|
416
439
|
void this.refresh();
|
|
417
440
|
}
|
|
418
441
|
}
|
|
@@ -421,8 +444,26 @@ var init_analytics_dashboard_page = __esm({
|
|
|
421
444
|
*/
|
|
422
445
|
goToPage(page) {
|
|
423
446
|
this.currentPage.set(page);
|
|
447
|
+
this.syncUrlParams();
|
|
424
448
|
void this.refresh();
|
|
425
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Sync current filter state to URL query params.
|
|
452
|
+
*/
|
|
453
|
+
syncUrlParams() {
|
|
454
|
+
const queryParams = {
|
|
455
|
+
range: this.selectedRange() !== "all" ? this.selectedRange() : null,
|
|
456
|
+
category: this.selectedCategory() !== "all" ? this.selectedCategory() : null,
|
|
457
|
+
search: this.searchTerm() || null,
|
|
458
|
+
page: this.currentPage() > 1 ? String(this.currentPage()) : null
|
|
459
|
+
};
|
|
460
|
+
void this.router.navigate([], {
|
|
461
|
+
relativeTo: this.route,
|
|
462
|
+
queryParams,
|
|
463
|
+
queryParamsHandling: "merge",
|
|
464
|
+
replaceUrl: true
|
|
465
|
+
});
|
|
466
|
+
}
|
|
426
467
|
/**
|
|
427
468
|
* Get total content operations count.
|
|
428
469
|
*/
|
|
@@ -1120,12 +1161,13 @@ var content_performance_page_exports = {};
|
|
|
1120
1161
|
__export(content_performance_page_exports, {
|
|
1121
1162
|
ContentPerformancePage: () => ContentPerformancePage
|
|
1122
1163
|
});
|
|
1123
|
-
var import_core7, import_common3, import_ui3, import_core8, import_outline3, ContentPerformancePage;
|
|
1164
|
+
var import_core7, import_common3, import_router2, import_ui3, import_core8, import_outline3, ContentPerformancePage;
|
|
1124
1165
|
var init_content_performance_page = __esm({
|
|
1125
1166
|
"libs/plugins/analytics/src/lib/dashboard/content-performance.page.ts"() {
|
|
1126
1167
|
"use strict";
|
|
1127
1168
|
import_core7 = require("@angular/core");
|
|
1128
1169
|
import_common3 = require("@angular/common");
|
|
1170
|
+
import_router2 = require("@angular/router");
|
|
1129
1171
|
import_ui3 = require("@momentumcms/ui");
|
|
1130
1172
|
import_core8 = require("@ng-icons/core");
|
|
1131
1173
|
import_outline3 = require("@ng-icons/heroicons/outline");
|
|
@@ -1134,6 +1176,8 @@ var init_content_performance_page = __esm({
|
|
|
1134
1176
|
constructor() {
|
|
1135
1177
|
this.service = (0, import_core7.inject)(ContentPerformanceService);
|
|
1136
1178
|
this.platformId = (0, import_core7.inject)(import_core7.PLATFORM_ID);
|
|
1179
|
+
this.router = (0, import_core7.inject)(import_router2.Router);
|
|
1180
|
+
this.route = (0, import_core7.inject)(import_router2.ActivatedRoute);
|
|
1137
1181
|
this.dateRanges = [
|
|
1138
1182
|
{
|
|
1139
1183
|
label: "24h",
|
|
@@ -1183,16 +1227,25 @@ var init_content_performance_page = __esm({
|
|
|
1183
1227
|
ngOnInit() {
|
|
1184
1228
|
if (!(0, import_common3.isPlatformBrowser)(this.platformId))
|
|
1185
1229
|
return;
|
|
1230
|
+
const params = this.route.snapshot.queryParams;
|
|
1231
|
+
if (params["range"] && this.dateRanges.some((r) => r.value === params["range"])) {
|
|
1232
|
+
this.selectedRange.set(params["range"]);
|
|
1233
|
+
}
|
|
1234
|
+
if (params["search"]) {
|
|
1235
|
+
this.searchTerm.set(params["search"]);
|
|
1236
|
+
}
|
|
1186
1237
|
void this.fetchData();
|
|
1187
1238
|
}
|
|
1188
1239
|
setDateRange(range) {
|
|
1189
1240
|
this.selectedRange.set(range.value);
|
|
1190
1241
|
this.expandedRow.set(null);
|
|
1242
|
+
this.syncUrlParams();
|
|
1191
1243
|
void this.fetchData();
|
|
1192
1244
|
}
|
|
1193
1245
|
onSearch(event) {
|
|
1194
1246
|
if (event.target instanceof HTMLInputElement) {
|
|
1195
1247
|
this.searchTerm.set(event.target.value);
|
|
1248
|
+
this.syncUrlParams();
|
|
1196
1249
|
}
|
|
1197
1250
|
}
|
|
1198
1251
|
toggleRow(url) {
|
|
@@ -1203,6 +1256,18 @@ var init_content_performance_page = __esm({
|
|
|
1203
1256
|
const from = range?.getFrom();
|
|
1204
1257
|
await this.service.fetchTopPages({ from });
|
|
1205
1258
|
}
|
|
1259
|
+
syncUrlParams() {
|
|
1260
|
+
const queryParams = {
|
|
1261
|
+
range: this.selectedRange() !== "all" ? this.selectedRange() : null,
|
|
1262
|
+
search: this.searchTerm() || null
|
|
1263
|
+
};
|
|
1264
|
+
void this.router.navigate([], {
|
|
1265
|
+
relativeTo: this.route,
|
|
1266
|
+
queryParams,
|
|
1267
|
+
queryParamsHandling: "merge",
|
|
1268
|
+
replaceUrl: true
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1206
1271
|
};
|
|
1207
1272
|
ContentPerformancePage = __decorateClass([
|
|
1208
1273
|
(0, import_core7.Component)({
|