@tachybase/module-instrumentation 1.2.6 → 1.2.8

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.
@@ -0,0 +1,2 @@
1
+ import { ISchema } from '@tachybase/schema';
2
+ export declare const createHistoryConfig: ISchema;
@@ -0,0 +1,2 @@
1
+ import { ISchema } from '@tachybase/schema';
2
+ export declare const schemaHistoryConfigs: ISchema;
@@ -0,0 +1,2 @@
1
+ import { ISchema } from '@tachybase/schema';
2
+ export declare const schemaStatisticsQuery: ISchema;
@@ -0,0 +1,2 @@
1
+ import { ISchema } from '@tachybase/schema';
2
+ export declare const updateHistoryConfig: ISchema;
@@ -0,0 +1 @@
1
+ export declare const statisticsHistoricalPane: () => import("react/jsx-runtime").JSX.Element;
@@ -1,12 +1,14 @@
1
1
  module.exports = {
2
- "@tachybase/client": "1.2.6",
3
- "antd": "5.22.5",
4
2
  "react": "18.3.1",
3
+ "@tachybase/client": "1.2.8",
4
+ "@tachybase/components": "1.2.8",
5
+ "@tachybase/schema": "1.2.8",
6
+ "antd": "5.22.5",
7
+ "@ant-design/icons": "6.0.0",
5
8
  "lodash": "4.17.21",
6
- "@tachybase/database": "1.2.6",
7
- "@tachybase/actions": "1.2.6",
8
- "@tachybase/server": "1.2.6",
9
- "@tachybase/schema": "1.2.6",
10
- "@tachybase/utils": "1.2.6",
11
- "dayjs": "1.11.13"
9
+ "@tachybase/database": "1.2.8",
10
+ "@tachybase/actions": "1.2.8",
11
+ "@tachybase/server": "1.2.8",
12
+ "dayjs": "1.11.13",
13
+ "@tachybase/utils": "1.2.8"
12
14
  };
@@ -1,19 +1,45 @@
1
1
  {
2
2
  "Action": "API操作",
3
+ "Add condition": "添加条件",
4
+ "After": "在此之后",
5
+ "All": "全部",
3
6
  "All users": "总用户数",
7
+ "Any": "任意",
4
8
  "Api audit": "启用日志",
9
+ "Before": "在此之前",
10
+ "Conditions": "条件",
5
11
  "Custom Instrumentation": "自定义埋点",
12
+ "Instrumentation filter": "埋点筛选",
6
13
  "Instrumentation key": "埋点键",
7
14
  "Instrumentation log": "埋点日志",
8
15
  "Instrumentation type": "埋点类型",
9
16
  "Instrumentation values": "值",
17
+ "Meet": "满足",
18
+ "Meta": "元数据",
19
+ "On": "在某天",
20
+ "On today": "今日",
10
21
  "Other users": "其他用户",
22
+ "Payload": "自定义数据",
23
+ "RangeDays": "近N天",
24
+ "Refresh": "刷新",
11
25
  "Resource name": "API资源",
12
26
  "Server tracking configuration": "后端埋点配置",
27
+ "Statistics Historical": "历史数据分析",
28
+ "Statistics details": "日志查询",
13
29
  "Statistics options": "数据来源",
14
30
  "Statistics title": "数据标题",
31
+ "Time filter": "时间筛选",
15
32
  "Today active": "今日活跃",
16
33
  "Total Users": "总用户",
17
34
  "Tracking options": "埋点配置",
18
- "Tracking statistics": "埋点分析"
35
+ "Tracking statistics": "埋点分析",
36
+ "equals": "等于",
37
+ "exists": "存在",
38
+ "greater than": "大于",
39
+ "greater than or equal": "大于等于",
40
+ "includes": "包含",
41
+ "is null": "为空",
42
+ "less than": "小于",
43
+ "less than or equal": "小于等于",
44
+ "not equals": "不等于"
19
45
  }
@@ -2,4 +2,5 @@ import { Context } from '@tachybase/actions';
2
2
  export declare class TrackingController {
3
3
  create(ctx: Context, next: () => Promise<any>): Promise<any>;
4
4
  list(ctx: Context, next: () => Promise<any>): Promise<any>;
5
+ query(ctx: Context, next: () => Promise<any>): Promise<any>;
5
6
  }
@@ -67,9 +67,10 @@ __export(tracking_controller_exports, {
67
67
  module.exports = __toCommonJS(tracking_controller_exports);
68
68
  var import_utils = require("@tachybase/utils");
69
69
  var import_getActiveUser = require("../hooks/getActiveUser");
70
+ var import_getQueryResult = require("../hooks/getQueryResult");
70
71
  var import_getStatistics = require("../hooks/getStatistics");
71
- var _list_dec, _create_dec, _TrackingController_decorators, _init;
72
- _TrackingController_decorators = [(0, import_utils.Controller)("instrumentation")], _create_dec = [(0, import_utils.Action)("create", { acl: "public" })], _list_dec = [(0, import_utils.Action)("list", { acl: "private" })];
72
+ var _query_dec, _list_dec, _create_dec, _TrackingController_decorators, _init;
73
+ _TrackingController_decorators = [(0, import_utils.Controller)("instrumentation")], _create_dec = [(0, import_utils.Action)("create", { acl: "public" })], _list_dec = [(0, import_utils.Action)("list", { acl: "private" })], _query_dec = [(0, import_utils.Action)("query", { acl: "private" })];
73
74
  class TrackingController {
74
75
  constructor() {
75
76
  __runInitializers(_init, 5, this);
@@ -99,22 +100,47 @@ class TrackingController {
99
100
  return next();
100
101
  }
101
102
  async list(ctx, next) {
102
- var _a;
103
+ var _a, _b, _c;
103
104
  const userCount = await ctx.db.getRepository("users").count();
104
105
  const ActiveUsers = await (0, import_getActiveUser.getDailyActiveUser)(ctx);
105
106
  const allData = await ctx.db.getRepository("trackingEvents").find();
106
107
  const configs = await ctx.db.getRepository("statisticsConfig").find();
108
+ const historyConfigs = await ctx.db.getRepository("trackingHistoryOptions").find();
107
109
  let customData = {};
108
110
  let customDataByTime = {};
109
111
  for (const config of configs) {
110
- if ((_a = config.statisticsOptions) == null ? void 0 : _a.timeGroup) {
111
- const grouped = (0, import_getStatistics.groupDataByTime)(allData, config.statisticsOptions);
112
- customDataByTime[config.title] = grouped;
112
+ if ((_a = config.statisticsOptions) == null ? void 0 : _a.collection) {
113
+ try {
114
+ const collectionName = config.statisticsOptions.collection.trim();
115
+ if (!collectionName) {
116
+ throw new Error("Collection name is empty or invalid.");
117
+ }
118
+ const collectionFilter = config.statisticsOptions.collectionFilter;
119
+ if (collectionFilter && typeof collectionFilter !== "object") {
120
+ throw new Error("Collection filter is invalid. It must be an object.");
121
+ }
122
+ const count = await ctx.db.getRepository(collectionName).count({
123
+ filter: collectionFilter || {}
124
+ });
125
+ customData[config.title] = count;
126
+ } catch (error) {
127
+ console.error(
128
+ `Error fetching count for collection "${(_b = config.statisticsOptions) == null ? void 0 : _b.collection}":`,
129
+ error.message
130
+ );
131
+ customData[config.title] = 0;
132
+ }
113
133
  } else {
114
134
  const count = (0, import_getStatistics.countDataByEventFrequency)(allData, config.statisticsOptions);
115
135
  customData[config.title] = count;
116
136
  }
117
137
  }
138
+ for (const historyConfig of historyConfigs) {
139
+ if ((_c = historyConfig.historyOptions) == null ? void 0 : _c.timeGroup) {
140
+ const grouped = (0, import_getStatistics.groupDataByTime)(allData, historyConfig.historyOptions);
141
+ customDataByTime[historyConfig.title] = grouped;
142
+ }
143
+ }
118
144
  const result = {
119
145
  users: { ...ActiveUsers, userCount },
120
146
  customData,
@@ -123,10 +149,18 @@ class TrackingController {
123
149
  ctx.body = result;
124
150
  return next();
125
151
  }
152
+ async query(ctx, next) {
153
+ const { values: configs } = ctx.action.params;
154
+ const allData = await ctx.db.getRepository("trackingEvents").find();
155
+ const queryResult = (0, import_getQueryResult.getDataByEventFrequency)(allData, configs);
156
+ ctx.body = queryResult;
157
+ return next();
158
+ }
126
159
  }
127
160
  _init = __decoratorStart(null);
128
161
  __decorateElement(_init, 1, "create", _create_dec, TrackingController);
129
162
  __decorateElement(_init, 1, "list", _list_dec, TrackingController);
163
+ __decorateElement(_init, 1, "query", _query_dec, TrackingController);
130
164
  TrackingController = __decorateElement(_init, 0, "TrackingController", _TrackingController_decorators, TrackingController);
131
165
  __runInitializers(_init, 1, TrackingController);
132
166
  // Annotate the CommonJS export names for ESM import in node:
@@ -0,0 +1,3 @@
1
+ import { CollectionOptions } from '@tachybase/database';
2
+ declare const _default: CollectionOptions;
3
+ export default _default;
@@ -0,0 +1,44 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var history_config_exports = {};
19
+ __export(history_config_exports, {
20
+ default: () => history_config_default
21
+ });
22
+ module.exports = __toCommonJS(history_config_exports);
23
+ var history_config_default = {
24
+ dumpRules: {
25
+ group: "log"
26
+ },
27
+ name: "trackingHistoryOptions",
28
+ createdBy: false,
29
+ updatedBy: false,
30
+ updatedAt: false,
31
+ createdAt: false,
32
+ shared: true,
33
+ model: "CollectionModel",
34
+ fields: [
35
+ {
36
+ type: "string",
37
+ name: "title"
38
+ },
39
+ {
40
+ type: "jsonb",
41
+ name: "historyOptions"
42
+ }
43
+ ]
44
+ };
@@ -22,7 +22,7 @@ __export(afterAction_exports, {
22
22
  module.exports = __toCommonJS(afterAction_exports);
23
23
  var import_filterMatch = require("./filterMatch");
24
24
  async function handleOtherAction(ctx, next, whiteList) {
25
- var _a, _b, _c, _d, _e, _f, _g;
25
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
26
26
  const { actionName, resourceName, params } = ctx.action;
27
27
  const data = ((_a = ctx.response) == null ? void 0 : _a.body) || null;
28
28
  const repo = ctx.db.getRepository("trackingEvents");
@@ -63,10 +63,12 @@ async function handleOtherAction(ctx, next, whiteList) {
63
63
  const currentRecordId = ((_e = ctx.body) == null ? void 0 : _e[collection == null ? void 0 : collection.filterTargetKey]) || null;
64
64
  const currentUserId = ((_g = (_f = ctx.auth) == null ? void 0 : _f.user) == null ? void 0 : _g.id) || null;
65
65
  const currentTime = (/* @__PURE__ */ new Date()).toISOString();
66
+ const currentUserDevice = ((_i = (_h = ctx.req) == null ? void 0 : _h.headers) == null ? void 0 : _i["user-agent"]) || null;
66
67
  const baseValues = {};
67
68
  if (configKeys.meta.includes("userId")) baseValues.userId = currentUserId;
68
69
  if (configKeys.meta.includes("recordId")) baseValues.recordId = currentRecordId;
69
70
  if (configKeys.meta.includes("createdAt")) baseValues.createdAt = currentTime;
71
+ if (configKeys.meta.includes("user-agent")) baseValues.userAgent = currentUserDevice;
70
72
  const nestedValuesMap = findValuesByKeys({ params, data }, configKeys.payload);
71
73
  const finalValues = {
72
74
  meta: baseValues,
@@ -0,0 +1,13 @@
1
+ type FilterConfig = {
2
+ key: string | string[];
3
+ filterValues?: Record<string, any>;
4
+ timeFilter?: {
5
+ after?: string;
6
+ before?: string;
7
+ on?: string;
8
+ today?: true;
9
+ rangeDays?: number;
10
+ };
11
+ };
12
+ export declare function getDataByEventFrequency(data: any[], config: FilterConfig): any;
13
+ export {};
@@ -0,0 +1,88 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var getQueryResult_exports = {};
29
+ __export(getQueryResult_exports, {
30
+ getDataByEventFrequency: () => getDataByEventFrequency
31
+ });
32
+ module.exports = __toCommonJS(getQueryResult_exports);
33
+ var import_dayjs = __toESM(require("dayjs"));
34
+ var import_filterMatch = require("./filterMatch");
35
+ function isSameDay(date1, date2) {
36
+ return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
37
+ }
38
+ function getDataByEventFrequency(data, config) {
39
+ const { key: filterKey, filterValues = {}, timeFilter } = config;
40
+ const matchKey = (itemKey) => {
41
+ if (Array.isArray(filterKey)) return filterKey.includes(itemKey);
42
+ return itemKey === filterKey;
43
+ };
44
+ const matchesTime = (item) => {
45
+ if (!timeFilter) return true;
46
+ const raw = item.createdAt;
47
+ if (!raw) return false;
48
+ const time = new Date(raw);
49
+ if (isNaN(time.getTime())) return false;
50
+ const now = /* @__PURE__ */ new Date();
51
+ if (timeFilter.rangeDays !== void 0) {
52
+ const start = (0, import_dayjs.default)().subtract(timeFilter.rangeDays - 1, "day").startOf("day").toDate();
53
+ const end = (0, import_dayjs.default)().endOf("day").toDate();
54
+ return time >= start && time <= end;
55
+ }
56
+ if (timeFilter.today) {
57
+ return isSameDay(time, /* @__PURE__ */ new Date());
58
+ }
59
+ if (timeFilter.on) {
60
+ const target = new Date(timeFilter.on);
61
+ if (isNaN(target.getTime())) return false;
62
+ return isSameDay(time, target);
63
+ }
64
+ if (timeFilter.after) {
65
+ const after = new Date(timeFilter.after);
66
+ if (isNaN(after.getTime())) return false;
67
+ if (time < after) return false;
68
+ }
69
+ if (timeFilter.before) {
70
+ const before = new Date(timeFilter.before);
71
+ if (isNaN(before.getTime())) return false;
72
+ if (time > before) return false;
73
+ }
74
+ return true;
75
+ };
76
+ const groupMap = [];
77
+ for (const item of data) {
78
+ if (!matchKey(item.key)) continue;
79
+ if (!(0, import_filterMatch.filterMatch)(item.values, filterValues)) continue;
80
+ if (!matchesTime(item)) continue;
81
+ groupMap.push(item);
82
+ }
83
+ return groupMap;
84
+ }
85
+ // Annotate the CommonJS export names for ESM import in node:
86
+ 0 && (module.exports = {
87
+ getDataByEventFrequency
88
+ });
@@ -32,6 +32,7 @@ __export(getStatistics_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(getStatistics_exports);
34
34
  var import_dayjs = __toESM(require("dayjs"));
35
+ var import_filterMatch = require("./filterMatch");
35
36
  function getValueByPath(obj, path) {
36
37
  return path.split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
37
38
  }
@@ -45,9 +46,6 @@ function countDataByEventFrequency(data, config) {
45
46
  if (Array.isArray(filterKey)) return filterKey.includes(itemKey);
46
47
  return itemKey === filterKey;
47
48
  };
48
- const matchesValues = (values) => {
49
- return Object.entries(filterValues).every(([k, v]) => values[k] === v);
50
- };
51
49
  const matchesTime = (item) => {
52
50
  if (!timeFilter) return true;
53
51
  const raw = item.createdAt;
@@ -83,7 +81,7 @@ function countDataByEventFrequency(data, config) {
83
81
  const groupMap = {};
84
82
  for (const item of data) {
85
83
  if (!matchKey(item.key)) continue;
86
- if (!matchesValues(item.values)) continue;
84
+ if (!(0, import_filterMatch.filterMatch)(item.values, filterValues)) continue;
87
85
  if (!matchesTime(item)) continue;
88
86
  const key = dedupBy ? getValueByPath(item.values, dedupBy) : "__no_dedup__";
89
87
  if (!key) continue;
@@ -96,7 +96,7 @@ class ModuleInstrumentationServer extends (_a = import_server.Plugin) {
96
96
  resourceName: "auth",
97
97
  action: "signIn",
98
98
  trackingOptions: {
99
- meta: ["userId", "recordId", "createdAt"],
99
+ meta: ["userId", "recordId", "createdAt", "user-agent"],
100
100
  filter: {
101
101
  $and: [
102
102
  {
@@ -108,7 +108,7 @@ class ModuleInstrumentationServer extends (_a = import_server.Plugin) {
108
108
  }
109
109
  ]
110
110
  },
111
- payload: ["errors", "account"]
111
+ payload: ["errors", "account", "phone"]
112
112
  }
113
113
  }
114
114
  });
@@ -136,7 +136,7 @@ class ModuleInstrumentationServer extends (_a = import_server.Plugin) {
136
136
  this.addServerTrackingListener();
137
137
  }
138
138
  this.app.acl.allow("instrumentation", "create", "public");
139
- this.app.acl.allow("instrumentation", "list", "loggedIn");
139
+ this.app.acl.allow("instrumentation", ["list", "query"], "loggedIn");
140
140
  this.app.acl.registerSnippet({
141
141
  name: `pm.system-services.custom-instrumentation.clientTracking`,
142
142
  actions: ["trackingEvents:*"]
@@ -149,6 +149,14 @@ class ModuleInstrumentationServer extends (_a = import_server.Plugin) {
149
149
  name: `pm.system-services.custom-instrumentation.trackingStatistics`,
150
150
  actions: ["statisticsConfig:*"]
151
151
  });
152
+ this.app.acl.registerSnippet({
153
+ name: `pm.system-services.custom-instrumentation.statisticsDetails`,
154
+ actions: ["instrumentation:*"]
155
+ });
156
+ this.app.acl.registerSnippet({
157
+ name: `pm.system-services.custom-instrumentation.statisticsHistorical`,
158
+ actions: ["trackingHistoryOptions:*"]
159
+ });
152
160
  }
153
161
  }
154
162
  _init = __decoratorStart(_a);
package/package.json CHANGED
@@ -1,26 +1,28 @@
1
1
  {
2
2
  "name": "@tachybase/module-instrumentation",
3
3
  "displayName": "Custom instrumentation",
4
- "version": "1.2.6",
4
+ "version": "1.2.8",
5
5
  "description": "A module for tracking and instrumentation in Tachybase.",
6
6
  "keywords": [
7
7
  "Logging and monitoring"
8
8
  ],
9
9
  "main": "dist/server/index.js",
10
10
  "devDependencies": {
11
+ "@ant-design/icons": "^6.0.0",
11
12
  "@antv/g2": "^5.3.0",
12
13
  "antd": "5.22.5",
13
14
  "dayjs": "1.11.13",
14
15
  "lodash": "^4.17.21"
15
16
  },
16
17
  "peerDependencies": {
17
- "@tachybase/actions": "1.2.6",
18
- "@tachybase/client": "1.2.6",
19
- "@tachybase/database": "1.2.6",
20
- "@tachybase/schema": "1.2.6",
21
- "@tachybase/test": "1.2.6",
22
- "@tachybase/utils": "1.2.6",
23
- "@tachybase/server": "1.2.6"
18
+ "@tachybase/actions": "1.2.8",
19
+ "@tachybase/client": "1.2.8",
20
+ "@tachybase/components": "1.2.8",
21
+ "@tachybase/database": "1.2.8",
22
+ "@tachybase/schema": "1.2.8",
23
+ "@tachybase/test": "1.2.8",
24
+ "@tachybase/utils": "1.2.8",
25
+ "@tachybase/server": "1.2.8"
24
26
  },
25
27
  "description.zh-CN": "Tachybase 应用的追踪和埋点模块",
26
28
  "displayName.zh-CN": "埋点追踪"