@xh/hoist 73.0.0-SNAPSHOT.1746025071597 → 73.0.0-SNAPSHOT.1746050507413
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/CHANGELOG.md +9 -3
- package/admin/AdminUtils.ts +5 -0
- package/admin/AppModel.ts +19 -7
- package/admin/columns/Rest.ts +8 -0
- package/admin/columns/Tracking.ts +72 -0
- package/admin/tabs/activity/tracking/ActivityTracking.scss +19 -0
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +309 -216
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +81 -51
- package/admin/tabs/activity/tracking/chart/AggChartModel.ts +218 -0
- package/admin/tabs/activity/tracking/chart/AggChartPanel.ts +61 -0
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditor.ts +148 -0
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +133 -0
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +123 -60
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +106 -58
- package/admin/tabs/client/ClientTab.ts +2 -4
- package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +1 -2
- package/admin/tabs/general/GeneralTab.ts +2 -0
- package/build/types/admin/AdminUtils.d.ts +2 -0
- package/build/types/admin/AppModel.d.ts +4 -1
- package/build/types/admin/columns/Rest.d.ts +1 -0
- package/build/types/admin/columns/Tracking.d.ts +6 -0
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +31 -28
- package/build/types/admin/tabs/activity/tracking/chart/AggChartModel.d.ts +33 -0
- package/build/types/admin/tabs/activity/tracking/chart/AggChartPanel.d.ts +2 -0
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditor.d.ts +2 -0
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.d.ts +46 -0
- package/build/types/admin/tabs/activity/tracking/detail/ActivityDetailModel.d.ts +14 -1
- package/build/types/cmp/form/FormModel.d.ts +19 -30
- package/build/types/cmp/form/field/SubformsFieldModel.d.ts +25 -22
- package/build/types/core/HoistBase.d.ts +2 -2
- package/build/types/data/cube/CubeField.d.ts +4 -5
- package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -3
- package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -3
- package/cmp/error/ErrorBoundaryModel.ts +1 -1
- package/cmp/form/FormModel.ts +20 -28
- package/cmp/form/field/SubformsFieldModel.ts +28 -22
- package/cmp/grid/columns/DatesTimes.ts +1 -2
- package/cmp/grid/impl/GridHScrollbar.ts +1 -2
- package/core/HoistBase.ts +12 -12
- package/data/cube/CubeField.ts +17 -18
- package/desktop/cmp/button/DashCanvasAddViewButton.ts +1 -0
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/admin/tabs/activity/tracking/charts/ChartsModel.ts +0 -218
- package/admin/tabs/activity/tracking/charts/ChartsPanel.ts +0 -76
- package/build/types/admin/tabs/activity/tracking/charts/ChartsModel.d.ts +0 -34
- package/build/types/admin/tabs/activity/tracking/charts/ChartsPanel.d.ts +0 -2
- /package/admin/tabs/{client → general}/feedback/FeedbackPanel.ts +0 -0
- /package/build/types/admin/tabs/{client → general}/feedback/FeedbackPanel.d.ts +0 -0
|
@@ -4,30 +4,42 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {exportFilename} from '@xh/hoist/admin/AdminUtils';
|
|
7
|
+
import {exportFilename, getAppModel} from '@xh/hoist/admin/AdminUtils';
|
|
8
8
|
import * as Col from '@xh/hoist/admin/columns';
|
|
9
|
+
import {
|
|
10
|
+
ActivityTrackingDataFieldSpec,
|
|
11
|
+
DataFieldsEditorModel
|
|
12
|
+
} from '@xh/hoist/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel';
|
|
9
13
|
import {FilterChooserModel} from '@xh/hoist/cmp/filter';
|
|
10
14
|
import {FormModel} from '@xh/hoist/cmp/form';
|
|
11
|
-
import {GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
|
|
15
|
+
import {ColumnRenderer, ColumnSpec, GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
|
|
12
16
|
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
|
|
13
|
-
import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
17
|
+
import {HoistModel, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
|
|
14
18
|
import {Cube, CubeFieldSpec, FieldSpec} from '@xh/hoist/data';
|
|
15
|
-
import {fmtNumber} from '@xh/hoist/format';
|
|
16
|
-
import {action, computed, makeObservable} from '@xh/hoist/mobx';
|
|
19
|
+
import {dateRenderer, dateTimeSecRenderer, fmtNumber, numberRenderer} from '@xh/hoist/format';
|
|
20
|
+
import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
17
21
|
import {LocalDate} from '@xh/hoist/utils/datetime';
|
|
18
|
-
import {compact, isEmpty, round} from 'lodash';
|
|
22
|
+
import {compact, get, isEmpty, isEqual, round} from 'lodash';
|
|
19
23
|
import moment from 'moment';
|
|
20
24
|
|
|
21
|
-
export const PERSIST_ACTIVITY = {localStorageKey: 'xhAdminActivityState'};
|
|
22
|
-
|
|
23
25
|
export class ActivityTrackingModel extends HoistModel {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
/** FormModel for server-side querying controls. */
|
|
26
27
|
@managed formModel: FormModel;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@managed
|
|
30
|
-
@managed
|
|
28
|
+
|
|
29
|
+
/** Models for data-handling components - can be rebuilt due to change in dataFields. */
|
|
30
|
+
@managed @observable.ref groupingChooserModel: GroupingChooserModel;
|
|
31
|
+
@managed @observable.ref cube: Cube;
|
|
32
|
+
@managed @observable.ref filterChooserModel: FilterChooserModel;
|
|
33
|
+
@managed @observable.ref gridModel: GridModel;
|
|
34
|
+
@managed dataFieldsEditorModel: DataFieldsEditorModel;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Optional spec for fields to be extracted from additional `data` returned by track entries
|
|
38
|
+
* and promoted to top-level columns in the grids. Supports dot-delimited paths as names.
|
|
39
|
+
*/
|
|
40
|
+
@observable.ref dataFields: ActivityTrackingDataFieldSpec[] = [];
|
|
41
|
+
|
|
42
|
+
@observable showFilterChooser: boolean = false;
|
|
31
43
|
|
|
32
44
|
get enabled(): boolean {
|
|
33
45
|
return XH.trackService.enabled;
|
|
@@ -37,19 +49,15 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
37
49
|
return this.groupingChooserModel.value;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
/**
|
|
41
|
-
* Summary of currently active query / filters.
|
|
42
|
-
* TODO - include new local filters if feasible, or drop this altogether.
|
|
43
|
-
* Formerly summarized server-side filters, but was misleading w/new filtering.
|
|
44
|
-
*/
|
|
45
|
-
get queryDisplayString(): string {
|
|
46
|
-
return `${XH.appName} Activity`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
52
|
get endDay(): LocalDate {
|
|
50
53
|
return this.formModel.values.endDay;
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
@computed
|
|
57
|
+
get hasFilter(): boolean {
|
|
58
|
+
return !!this.filterChooserModel.value;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
get maxRowOptions() {
|
|
54
62
|
return (
|
|
55
63
|
XH.trackService.conf.maxRows?.options?.map(rowCount => ({
|
|
@@ -69,160 +77,66 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
69
77
|
return this.maxRows === this.cube.store.allCount;
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
// TODO - process two collections - one for agg grid with _agg fields left as-is, another for
|
|
81
|
+
// detail grid and filter that replaces (potentially multiple) agg fields with a single
|
|
82
|
+
// underlying field.
|
|
83
|
+
get dataFieldCols(): ColumnSpec[] {
|
|
84
|
+
return this.dataFields.map(df => ({
|
|
85
|
+
field: df,
|
|
86
|
+
renderer: this.getDfRenderer(df),
|
|
87
|
+
appData: {showInAggGrid: !!df.aggregator}
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get viewManagerModel() {
|
|
92
|
+
return getAppModel().viewManagerModels.activityTracking;
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
private _monthFormat = 'MMM YYYY';
|
|
73
|
-
private _defaultDims = ['username'];
|
|
74
96
|
|
|
75
97
|
constructor() {
|
|
76
98
|
super();
|
|
77
99
|
makeObservable(this);
|
|
78
|
-
this.formModel = new FormModel({
|
|
79
|
-
fields: [
|
|
80
|
-
{name: 'startDay', initialValue: () => this.defaultStartDay},
|
|
81
|
-
{name: 'endDay', initialValue: () => this.defaultEndDay},
|
|
82
|
-
{name: 'maxRows', initialValue: XH.trackService.conf.maxRows?.default}
|
|
83
|
-
]
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
this.cube = new Cube({
|
|
87
|
-
fields: [
|
|
88
|
-
Col.browser.field,
|
|
89
|
-
Col.category.field,
|
|
90
|
-
Col.severity.field,
|
|
91
|
-
Col.correlationId.field,
|
|
92
|
-
Col.data.field,
|
|
93
|
-
{...(Col.dateCreated.field as FieldSpec), displayName: 'Timestamp'},
|
|
94
|
-
Col.day.field,
|
|
95
|
-
Col.dayRange.field,
|
|
96
|
-
Col.device.field,
|
|
97
|
-
Col.elapsed.field,
|
|
98
|
-
Col.entryCount.field,
|
|
99
|
-
Col.impersonating.field,
|
|
100
|
-
Col.msg.field,
|
|
101
|
-
Col.userAgent.field,
|
|
102
|
-
Col.username.field,
|
|
103
|
-
{name: 'count', type: 'int', aggregator: 'CHILD_COUNT'},
|
|
104
|
-
{name: 'month', type: 'string', isDimension: true, aggregator: 'UNIQUE'},
|
|
105
|
-
Col.url.field,
|
|
106
|
-
Col.instance.field,
|
|
107
|
-
Col.appVersion.field,
|
|
108
|
-
Col.appEnvironment.field,
|
|
109
|
-
Col.loadId.field,
|
|
110
|
-
Col.tabId.field
|
|
111
|
-
] as CubeFieldSpec[]
|
|
112
|
-
});
|
|
113
100
|
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
{field: 'category'},
|
|
117
|
-
{field: 'correlationId'},
|
|
118
|
-
{field: 'username', displayName: 'User'},
|
|
119
|
-
{field: 'device'},
|
|
120
|
-
{field: 'browser'},
|
|
121
|
-
{
|
|
122
|
-
field: 'elapsed',
|
|
123
|
-
valueRenderer: v => {
|
|
124
|
-
return fmtNumber(v, {
|
|
125
|
-
label: 'ms',
|
|
126
|
-
formatConfig: {thousandSeparated: false, mantissa: 0}
|
|
127
|
-
});
|
|
128
|
-
},
|
|
129
|
-
fieldType: 'number'
|
|
130
|
-
},
|
|
131
|
-
{field: 'msg', displayName: 'Message'},
|
|
132
|
-
{field: 'data'},
|
|
133
|
-
{field: 'userAgent'},
|
|
134
|
-
{field: 'url', displayName: 'URL'},
|
|
135
|
-
{field: 'instance'},
|
|
136
|
-
{field: 'severity'},
|
|
137
|
-
{field: 'appVersion'},
|
|
138
|
-
{field: 'loadId'},
|
|
139
|
-
{field: 'tabId'},
|
|
140
|
-
{field: 'appEnvironment', displayName: 'Environment'}
|
|
141
|
-
]
|
|
142
|
-
});
|
|
101
|
+
this.persistWith = {viewManagerModel: this.viewManagerModel};
|
|
102
|
+
this.markPersist('showFilterChooser');
|
|
143
103
|
|
|
144
|
-
this.
|
|
104
|
+
this.formModel = this.createQueryFormModel();
|
|
145
105
|
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
persistWith: this.persistWith,
|
|
149
|
-
initialValue: this._defaultDims
|
|
150
|
-
});
|
|
106
|
+
this.dataFieldsEditorModel = new DataFieldsEditorModel(this);
|
|
107
|
+
this.markPersist('dataFields');
|
|
151
108
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
...this.persistWith,
|
|
158
|
-
path: 'aggGridModel',
|
|
159
|
-
persistSort: false
|
|
109
|
+
this.addReaction(
|
|
110
|
+
{
|
|
111
|
+
track: () => this.dataFields,
|
|
112
|
+
run: () => this.createAndSetCoreModels(),
|
|
113
|
+
fireImmediately: true
|
|
160
114
|
},
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
},
|
|
173
|
-
flex: 1,
|
|
174
|
-
minWidth: 100,
|
|
175
|
-
isTreeColumn: true,
|
|
176
|
-
comparator: this.cubeLabelComparator.bind(this)
|
|
177
|
-
},
|
|
178
|
-
{...Col.username, hidden},
|
|
179
|
-
{...Col.category, hidden},
|
|
180
|
-
{...Col.device, hidden},
|
|
181
|
-
{...Col.browser, hidden},
|
|
182
|
-
{...Col.userAgent, hidden},
|
|
183
|
-
{...Col.impersonating, hidden},
|
|
184
|
-
{...Col.elapsed, headerName: 'Elapsed (avg)', hidden},
|
|
185
|
-
{...Col.dayRange, hidden},
|
|
186
|
-
{...Col.entryCount},
|
|
187
|
-
{field: 'count', hidden},
|
|
188
|
-
{...Col.appEnvironment, hidden},
|
|
189
|
-
{...Col.appVersion, hidden},
|
|
190
|
-
{...Col.loadId, hidden},
|
|
191
|
-
{...Col.tabId, hidden},
|
|
192
|
-
{...Col.url, hidden},
|
|
193
|
-
{...Col.instance, hidden}
|
|
194
|
-
]
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
this.addReaction({
|
|
198
|
-
track: () => this.query,
|
|
199
|
-
run: () => this.loadAsync(),
|
|
200
|
-
debounce: 100
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
this.addReaction({
|
|
204
|
-
track: () => [this.cube.records, this.dimensions],
|
|
205
|
-
run: () => this.loadGridAsync(),
|
|
206
|
-
debounce: 100
|
|
207
|
-
});
|
|
115
|
+
{
|
|
116
|
+
track: () => this.query,
|
|
117
|
+
run: () => this.loadAsync(),
|
|
118
|
+
debounce: 100
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
track: () => [this.cube.records, this.dimensions],
|
|
122
|
+
run: () => this.loadGridAsync(),
|
|
123
|
+
debounce: 100
|
|
124
|
+
}
|
|
125
|
+
);
|
|
208
126
|
}
|
|
209
127
|
|
|
210
128
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
211
|
-
const {enabled, cube} = this;
|
|
129
|
+
const {enabled, cube, query} = this;
|
|
212
130
|
if (!enabled) return;
|
|
213
131
|
|
|
214
132
|
try {
|
|
215
|
-
const data = await XH.
|
|
133
|
+
const data = await XH.postJson({
|
|
216
134
|
url: 'trackLogAdmin',
|
|
217
|
-
body:
|
|
135
|
+
body: query,
|
|
218
136
|
loadSpec
|
|
219
137
|
});
|
|
220
138
|
|
|
221
|
-
data.forEach(it =>
|
|
222
|
-
it.day = LocalDate.from(it.day);
|
|
223
|
-
it.month = it.day.format(this._monthFormat);
|
|
224
|
-
it.dayRange = {min: it.day, max: it.day};
|
|
225
|
-
});
|
|
139
|
+
data.forEach(it => this.processRawTrackLog(it));
|
|
226
140
|
|
|
227
141
|
await cube.loadDataAsync(data);
|
|
228
142
|
} catch (e) {
|
|
@@ -231,47 +145,23 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
231
145
|
}
|
|
232
146
|
}
|
|
233
147
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
includeRoot: true,
|
|
239
|
-
includeLeaves: true
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
data.forEach(node => this.separateLeafRows(node));
|
|
243
|
-
gridModel.loadData(data);
|
|
244
|
-
await gridModel.preSelectFirstAsync();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Cube emits leaves in "children" collection - rename that collection to "leafRows" so we can
|
|
248
|
-
// carry the leaves with the record, but deliberately not show them in the tree grid. We only
|
|
249
|
-
// want the tree grid to show aggregate records.
|
|
250
|
-
separateLeafRows(node) {
|
|
251
|
-
if (isEmpty(node.children)) return;
|
|
252
|
-
|
|
253
|
-
const childrenAreLeaves = !node.children[0].children;
|
|
254
|
-
if (childrenAreLeaves) {
|
|
255
|
-
node.leafRows = node.children;
|
|
256
|
-
delete node.children;
|
|
257
|
-
} else {
|
|
258
|
-
node.children.forEach(child => this.separateLeafRows(child));
|
|
148
|
+
@action
|
|
149
|
+
setDataFields(dataFields: ActivityTrackingDataFieldSpec[]) {
|
|
150
|
+
if (!isEqual(dataFields, this.dataFields)) {
|
|
151
|
+
this.dataFields = dataFields ?? [];
|
|
259
152
|
}
|
|
260
153
|
}
|
|
261
154
|
|
|
262
155
|
@action
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
formModel.init();
|
|
266
|
-
filterChooserModel.setValue(null);
|
|
267
|
-
groupingChooserModel.setValue(_defaultDims);
|
|
156
|
+
toggleFilterChooser() {
|
|
157
|
+
this.showFilterChooser = !this.showFilterChooser;
|
|
268
158
|
}
|
|
269
159
|
|
|
270
|
-
adjustDates(dir) {
|
|
160
|
+
adjustDates(dir: 'add' | 'subtract') {
|
|
271
161
|
const {startDay, endDay} = this.formModel.fields,
|
|
272
162
|
appDay = LocalDate.currentAppDay(),
|
|
273
|
-
start = startDay.value,
|
|
274
|
-
end = endDay.value,
|
|
163
|
+
start: LocalDate = startDay.value,
|
|
164
|
+
end: LocalDate = endDay.value,
|
|
275
165
|
diff = end.diff(start),
|
|
276
166
|
incr = diff + 1;
|
|
277
167
|
|
|
@@ -300,7 +190,53 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
300
190
|
return startDay === endDay.subtract(value, unit).nextDay();
|
|
301
191
|
}
|
|
302
192
|
|
|
303
|
-
|
|
193
|
+
getDisplayName(fieldName: string) {
|
|
194
|
+
return fieldName ? (this.cube.store.getField(fieldName)?.displayName ?? fieldName) : null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//------------------
|
|
198
|
+
// Implementation
|
|
199
|
+
//------------------
|
|
200
|
+
private createQueryFormModel(): FormModel {
|
|
201
|
+
return new FormModel({
|
|
202
|
+
persistWith: {...this.persistWith, path: 'queryFormValues', includeFields: ['maxRows']},
|
|
203
|
+
fields: [
|
|
204
|
+
{name: 'startDay', initialValue: () => LocalDate.currentAppDay()},
|
|
205
|
+
{name: 'endDay', initialValue: () => LocalDate.currentAppDay()},
|
|
206
|
+
{name: 'maxRows', initialValue: XH.trackService.conf.maxRows?.default}
|
|
207
|
+
]
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async loadGridAsync() {
|
|
212
|
+
const {cube, gridModel, dimensions} = this,
|
|
213
|
+
data = cube.executeQuery({
|
|
214
|
+
dimensions,
|
|
215
|
+
includeRoot: true,
|
|
216
|
+
includeLeaves: true
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
data.forEach(node => this.separateLeafRows(node));
|
|
220
|
+
gridModel.loadData(data);
|
|
221
|
+
await gridModel.preSelectFirstAsync();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Cube emits leaves in "children" collection - rename that collection to "leafRows" so we can
|
|
225
|
+
// carry the leaves with the record, but deliberately not show them in the tree grid. We only
|
|
226
|
+
// want the tree grid to show aggregate records.
|
|
227
|
+
private separateLeafRows(node) {
|
|
228
|
+
if (isEmpty(node.children)) return;
|
|
229
|
+
|
|
230
|
+
const childrenAreLeaves = !node.children[0].children;
|
|
231
|
+
if (childrenAreLeaves) {
|
|
232
|
+
node.leafRows = node.children;
|
|
233
|
+
delete node.children;
|
|
234
|
+
} else {
|
|
235
|
+
node.children.forEach(child => this.separateLeafRows(child));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private cubeLabelComparator(valA, valB, sortDir, abs, {recordA, recordB, defaultComparator}) {
|
|
304
240
|
const rawA = recordA?.raw,
|
|
305
241
|
rawB = recordB?.raw,
|
|
306
242
|
sortValA = this.getComparableValForDim(rawA, rawA?.cubeDimension),
|
|
@@ -309,7 +245,7 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
309
245
|
return defaultComparator(sortValA, sortValB);
|
|
310
246
|
}
|
|
311
247
|
|
|
312
|
-
getComparableValForDim(raw, dim) {
|
|
248
|
+
private getComparableValForDim(raw, dim) {
|
|
313
249
|
const rawVal = raw ? raw[dim] : null;
|
|
314
250
|
if (rawVal == null) return null;
|
|
315
251
|
|
|
@@ -326,40 +262,197 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
326
262
|
}
|
|
327
263
|
}
|
|
328
264
|
|
|
329
|
-
|
|
330
|
-
|
|
265
|
+
@computed
|
|
266
|
+
private get query() {
|
|
267
|
+
const {values} = this.formModel;
|
|
268
|
+
return {
|
|
269
|
+
startDay: values.startDay,
|
|
270
|
+
endDay: values.endDay,
|
|
271
|
+
maxRows: values.maxRows,
|
|
272
|
+
filters: this.filterChooserModel.value
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
//------------------------
|
|
277
|
+
// Impl - core data models
|
|
278
|
+
//------------------------
|
|
279
|
+
@action
|
|
280
|
+
private createAndSetCoreModels() {
|
|
281
|
+
this.cube = this.createCube();
|
|
282
|
+
this.filterChooserModel = this.createFilterChooserModel();
|
|
283
|
+
this.groupingChooserModel = this.createGroupingChooserModel();
|
|
284
|
+
this.gridModel = this.createGridModel();
|
|
331
285
|
}
|
|
332
286
|
|
|
333
|
-
private
|
|
334
|
-
|
|
287
|
+
private createCube(): Cube {
|
|
288
|
+
const fields = [
|
|
289
|
+
Col.appEnvironment.field,
|
|
290
|
+
Col.appVersion.field,
|
|
291
|
+
Col.browser.field,
|
|
292
|
+
Col.category.field,
|
|
293
|
+
Col.correlationId.field,
|
|
294
|
+
{name: 'count', type: 'int', aggregator: 'CHILD_COUNT'},
|
|
295
|
+
Col.data.field,
|
|
296
|
+
{...(Col.dateCreated.field as FieldSpec), displayName: 'Timestamp'},
|
|
297
|
+
Col.day.field,
|
|
298
|
+
Col.dayRange.field,
|
|
299
|
+
Col.device.field,
|
|
300
|
+
Col.elapsed.field,
|
|
301
|
+
Col.entryCount.field,
|
|
302
|
+
Col.impersonating.field,
|
|
303
|
+
Col.instance.field,
|
|
304
|
+
Col.loadId.field,
|
|
305
|
+
Col.msg.field,
|
|
306
|
+
{name: 'month', type: 'string', isDimension: true, aggregator: 'UNIQUE'},
|
|
307
|
+
Col.severity.field,
|
|
308
|
+
Col.tabId.field,
|
|
309
|
+
Col.userAgent.field,
|
|
310
|
+
Col.username.field,
|
|
311
|
+
Col.url.field,
|
|
312
|
+
...this.dataFields
|
|
313
|
+
] as CubeFieldSpec[];
|
|
314
|
+
|
|
315
|
+
return new Cube({fields});
|
|
335
316
|
}
|
|
336
317
|
|
|
337
|
-
private
|
|
318
|
+
private createFilterChooserModel(): FilterChooserModel {
|
|
319
|
+
// TODO - data fields?
|
|
320
|
+
const ret = new FilterChooserModel({
|
|
321
|
+
persistWith: {...this.persistWith, persistFavorites: false},
|
|
322
|
+
fieldSpecs: [
|
|
323
|
+
{field: 'appEnvironment', displayName: 'Environment'},
|
|
324
|
+
{field: 'appVersion'},
|
|
325
|
+
{field: 'browser'},
|
|
326
|
+
{field: 'category'},
|
|
327
|
+
{field: 'correlationId'},
|
|
328
|
+
{field: 'data'},
|
|
329
|
+
{field: 'device'},
|
|
330
|
+
{
|
|
331
|
+
field: 'elapsed',
|
|
332
|
+
valueRenderer: v => {
|
|
333
|
+
return fmtNumber(v, {
|
|
334
|
+
label: 'ms',
|
|
335
|
+
formatConfig: {thousandSeparated: false, mantissa: 0}
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
fieldType: 'number'
|
|
339
|
+
},
|
|
340
|
+
{field: 'instance'},
|
|
341
|
+
{field: 'loadId'},
|
|
342
|
+
{field: 'msg', displayName: 'Message'},
|
|
343
|
+
{field: 'severity'},
|
|
344
|
+
{field: 'tabId'},
|
|
345
|
+
{field: 'userAgent'},
|
|
346
|
+
{field: 'username', displayName: 'User'},
|
|
347
|
+
{field: 'url', displayName: 'URL'}
|
|
348
|
+
]
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Load lookups - not awaited
|
|
338
352
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
353
|
+
XH.fetchJson({url: 'trackLogAdmin/lookups'}).then(lookups => {
|
|
354
|
+
if (ret !== this.filterChooserModel) return;
|
|
355
|
+
ret.fieldSpecs.forEach(spec => {
|
|
356
|
+
const {field} = spec,
|
|
357
|
+
lookup = lookups[field] ? compact(lookups[field]) : null;
|
|
358
|
+
|
|
359
|
+
if (!isEmpty(lookup)) {
|
|
360
|
+
spec.values = lookup;
|
|
361
|
+
spec.enableValues = true;
|
|
362
|
+
spec.hasExplicitValues = true;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
349
365
|
});
|
|
350
366
|
} catch (e) {
|
|
351
367
|
XH.handleException(e, {title: 'Error loading lookups for filtering'});
|
|
352
368
|
}
|
|
369
|
+
|
|
370
|
+
return ret;
|
|
353
371
|
}
|
|
354
372
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
373
|
+
private createGroupingChooserModel(): GroupingChooserModel {
|
|
374
|
+
return new GroupingChooserModel({
|
|
375
|
+
persistWith: {...this.persistWith, persistFavorites: false},
|
|
376
|
+
dimensions: this.cube.dimensions,
|
|
377
|
+
initialValue: ['username', 'category']
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private createGridModel(): GridModel {
|
|
382
|
+
const hidden = true;
|
|
383
|
+
return new GridModel({
|
|
384
|
+
persistWith: {...this.persistWith, path: 'aggGrid'},
|
|
385
|
+
enableExport: true,
|
|
386
|
+
colChooserModel: true,
|
|
387
|
+
treeMode: true,
|
|
388
|
+
treeStyle: TreeStyle.HIGHLIGHTS_AND_BORDERS,
|
|
389
|
+
autosizeOptions: {mode: 'managed'},
|
|
390
|
+
exportOptions: {filename: exportFilename('activity-summary')},
|
|
391
|
+
emptyText: 'No activity reported...',
|
|
392
|
+
sortBy: ['cubeLabel'],
|
|
393
|
+
columns: [
|
|
394
|
+
{
|
|
395
|
+
field: {
|
|
396
|
+
name: 'cubeLabel',
|
|
397
|
+
type: 'string',
|
|
398
|
+
displayName: 'Group'
|
|
399
|
+
},
|
|
400
|
+
minWidth: 100,
|
|
401
|
+
isTreeColumn: true,
|
|
402
|
+
comparator: this.cubeLabelComparator.bind(this)
|
|
403
|
+
},
|
|
404
|
+
{...Col.username, hidden},
|
|
405
|
+
{...Col.category, hidden},
|
|
406
|
+
{...Col.device, hidden},
|
|
407
|
+
{...Col.browser, hidden},
|
|
408
|
+
{...Col.userAgent, hidden},
|
|
409
|
+
{...Col.impersonating, hidden},
|
|
410
|
+
{...Col.elapsed, headerName: 'Elapsed (avg)', hidden},
|
|
411
|
+
{...Col.dayRange, hidden},
|
|
412
|
+
{...Col.entryCount},
|
|
413
|
+
{field: 'count', hidden},
|
|
414
|
+
{...Col.appEnvironment, hidden},
|
|
415
|
+
{...Col.appVersion, hidden},
|
|
416
|
+
{...Col.loadId, hidden},
|
|
417
|
+
{...Col.tabId, hidden},
|
|
418
|
+
{...Col.url, hidden},
|
|
419
|
+
{...Col.instance, hidden},
|
|
420
|
+
...this.dataFieldCols.map(it => ({...it, hidden: !it.appData.showInAggGrid}))
|
|
421
|
+
]
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
//------------------------------
|
|
426
|
+
// Impl - data fields processing
|
|
427
|
+
//------------------------------
|
|
428
|
+
private processRawTrackLog(raw: PlainObject) {
|
|
429
|
+
try {
|
|
430
|
+
raw.day = LocalDate.from(raw.day);
|
|
431
|
+
raw.month = raw.day.format(this._monthFormat);
|
|
432
|
+
raw.dayRange = {min: raw.day, max: raw.day};
|
|
433
|
+
|
|
434
|
+
const data = JSON.parse(raw.data);
|
|
435
|
+
if (isEmpty(data)) return;
|
|
436
|
+
|
|
437
|
+
this.dataFields.forEach(df => {
|
|
438
|
+
const path = df.path;
|
|
439
|
+
raw[df.name] = get(data, path);
|
|
440
|
+
});
|
|
441
|
+
} catch (e) {
|
|
442
|
+
this.logError(`Error processing raw track log`, e);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private getDfRenderer(df: ActivityTrackingDataFieldSpec): ColumnRenderer {
|
|
447
|
+
switch (df.type) {
|
|
448
|
+
case 'number':
|
|
449
|
+
return numberRenderer();
|
|
450
|
+
case 'date':
|
|
451
|
+
return dateTimeSecRenderer();
|
|
452
|
+
case 'localDate':
|
|
453
|
+
return dateRenderer();
|
|
454
|
+
default:
|
|
455
|
+
return v => v ?? '-';
|
|
456
|
+
}
|
|
364
457
|
}
|
|
365
458
|
}
|