@xh/hoist 73.0.0-SNAPSHOT.1745976013413 → 73.0.0-SNAPSHOT.1746050068813
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 +10 -1
- package/admin/AdminUtils.ts +5 -0
- package/admin/App.scss +6 -0
- package/admin/AppModel.ts +19 -7
- package/admin/{tabs/client/clients/ClientsColumns.ts → columns/Clients.ts} +20 -53
- package/admin/columns/Core.ts +34 -35
- package/admin/columns/Rest.ts +8 -0
- package/admin/columns/Tracking.ts +144 -42
- package/admin/columns/index.ts +1 -0
- package/admin/tabs/activity/tracking/ActivityTracking.scss +18 -0
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +309 -210
- 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 +147 -0
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +133 -0
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +123 -59
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +110 -54
- package/admin/tabs/client/ClientTab.ts +2 -4
- package/admin/tabs/client/clients/ClientsModel.ts +10 -11
- 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/{tabs/client/clients/ClientsColumns.d.ts → columns/Clients.d.ts} +3 -7
- package/build/types/admin/columns/Core.d.ts +5 -5
- package/build/types/admin/columns/Rest.d.ts +1 -0
- package/build/types/admin/columns/Tracking.d.ts +13 -4
- package/build/types/admin/columns/index.d.ts +1 -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/package.json +1 -1
- package/svc/TrackService.ts +2 -0
- 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,154 +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
100
|
|
|
86
|
-
this.
|
|
87
|
-
|
|
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
|
-
] as CubeFieldSpec[]
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
this.filterChooserModel = new FilterChooserModel({
|
|
113
|
-
fieldSpecs: [
|
|
114
|
-
{field: 'category'},
|
|
115
|
-
{field: 'correlationId'},
|
|
116
|
-
{field: 'username', displayName: 'User'},
|
|
117
|
-
{field: 'device'},
|
|
118
|
-
{field: 'browser'},
|
|
119
|
-
{
|
|
120
|
-
field: 'elapsed',
|
|
121
|
-
valueRenderer: v => {
|
|
122
|
-
return fmtNumber(v, {
|
|
123
|
-
label: 'ms',
|
|
124
|
-
formatConfig: {thousandSeparated: false, mantissa: 0}
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
fieldType: 'number'
|
|
128
|
-
},
|
|
129
|
-
{field: 'msg', displayName: 'Message'},
|
|
130
|
-
{field: 'data'},
|
|
131
|
-
{field: 'userAgent'},
|
|
132
|
-
{field: 'url', displayName: 'URL'},
|
|
133
|
-
{field: 'instance'},
|
|
134
|
-
{field: 'severity'},
|
|
135
|
-
{field: 'appVersion'},
|
|
136
|
-
{field: 'appEnvironment', displayName: 'Environment'}
|
|
137
|
-
]
|
|
138
|
-
});
|
|
101
|
+
this.persistWith = {viewManagerModel: this.viewManagerModel};
|
|
102
|
+
this.markPersist('showFilterChooser');
|
|
139
103
|
|
|
140
|
-
this.
|
|
104
|
+
this.formModel = this.createQueryFormModel();
|
|
141
105
|
|
|
142
|
-
this.
|
|
143
|
-
|
|
144
|
-
persistWith: this.persistWith,
|
|
145
|
-
initialValue: this._defaultDims
|
|
146
|
-
});
|
|
106
|
+
this.dataFieldsEditorModel = new DataFieldsEditorModel(this);
|
|
107
|
+
this.markPersist('dataFields');
|
|
147
108
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
...this.persistWith,
|
|
154
|
-
path: 'aggGridModel',
|
|
155
|
-
persistSort: false
|
|
109
|
+
this.addReaction(
|
|
110
|
+
{
|
|
111
|
+
track: () => this.dataFields,
|
|
112
|
+
run: () => this.createAndSetCoreModels(),
|
|
113
|
+
fireImmediately: true
|
|
156
114
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
},
|
|
169
|
-
flex: 1,
|
|
170
|
-
minWidth: 100,
|
|
171
|
-
isTreeColumn: true,
|
|
172
|
-
comparator: this.cubeLabelComparator.bind(this)
|
|
173
|
-
},
|
|
174
|
-
{...Col.username, hidden},
|
|
175
|
-
{...Col.category, hidden},
|
|
176
|
-
{...Col.device, hidden},
|
|
177
|
-
{...Col.browser, hidden},
|
|
178
|
-
{...Col.userAgent, hidden},
|
|
179
|
-
{...Col.impersonating, hidden},
|
|
180
|
-
{...Col.elapsed, headerName: 'Elapsed (avg)', hidden},
|
|
181
|
-
{...Col.dayRange, hidden},
|
|
182
|
-
{...Col.entryCount},
|
|
183
|
-
{field: 'count', hidden},
|
|
184
|
-
{...Col.appEnvironment, hidden},
|
|
185
|
-
{...Col.appVersion, hidden},
|
|
186
|
-
{...Col.url, hidden},
|
|
187
|
-
{...Col.instance, hidden}
|
|
188
|
-
]
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
this.addReaction({
|
|
192
|
-
track: () => this.query,
|
|
193
|
-
run: () => this.loadAsync(),
|
|
194
|
-
debounce: 100
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
this.addReaction({
|
|
198
|
-
track: () => [this.cube.records, this.dimensions],
|
|
199
|
-
run: () => this.loadGridAsync(),
|
|
200
|
-
debounce: 100
|
|
201
|
-
});
|
|
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
|
+
);
|
|
202
126
|
}
|
|
203
127
|
|
|
204
128
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
205
|
-
const {enabled, cube} = this;
|
|
129
|
+
const {enabled, cube, query} = this;
|
|
206
130
|
if (!enabled) return;
|
|
207
131
|
|
|
208
132
|
try {
|
|
209
|
-
const data = await XH.
|
|
133
|
+
const data = await XH.postJson({
|
|
210
134
|
url: 'trackLogAdmin',
|
|
211
|
-
body:
|
|
135
|
+
body: query,
|
|
212
136
|
loadSpec
|
|
213
137
|
});
|
|
214
138
|
|
|
215
|
-
data.forEach(it =>
|
|
216
|
-
it.day = LocalDate.from(it.day);
|
|
217
|
-
it.month = it.day.format(this._monthFormat);
|
|
218
|
-
it.dayRange = {min: it.day, max: it.day};
|
|
219
|
-
});
|
|
139
|
+
data.forEach(it => this.processRawTrackLog(it));
|
|
220
140
|
|
|
221
141
|
await cube.loadDataAsync(data);
|
|
222
142
|
} catch (e) {
|
|
@@ -225,47 +145,23 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
225
145
|
}
|
|
226
146
|
}
|
|
227
147
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
includeRoot: true,
|
|
233
|
-
includeLeaves: true
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
data.forEach(node => this.separateLeafRows(node));
|
|
237
|
-
gridModel.loadData(data);
|
|
238
|
-
await gridModel.preSelectFirstAsync();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Cube emits leaves in "children" collection - rename that collection to "leafRows" so we can
|
|
242
|
-
// carry the leaves with the record, but deliberately not show them in the tree grid. We only
|
|
243
|
-
// want the tree grid to show aggregate records.
|
|
244
|
-
separateLeafRows(node) {
|
|
245
|
-
if (isEmpty(node.children)) return;
|
|
246
|
-
|
|
247
|
-
const childrenAreLeaves = !node.children[0].children;
|
|
248
|
-
if (childrenAreLeaves) {
|
|
249
|
-
node.leafRows = node.children;
|
|
250
|
-
delete node.children;
|
|
251
|
-
} else {
|
|
252
|
-
node.children.forEach(child => this.separateLeafRows(child));
|
|
148
|
+
@action
|
|
149
|
+
setDataFields(dataFields: ActivityTrackingDataFieldSpec[]) {
|
|
150
|
+
if (!isEqual(dataFields, this.dataFields)) {
|
|
151
|
+
this.dataFields = dataFields ?? [];
|
|
253
152
|
}
|
|
254
153
|
}
|
|
255
154
|
|
|
256
155
|
@action
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
formModel.init();
|
|
260
|
-
filterChooserModel.setValue(null);
|
|
261
|
-
groupingChooserModel.setValue(_defaultDims);
|
|
156
|
+
toggleFilterChooser() {
|
|
157
|
+
this.showFilterChooser = !this.showFilterChooser;
|
|
262
158
|
}
|
|
263
159
|
|
|
264
|
-
adjustDates(dir) {
|
|
160
|
+
adjustDates(dir: 'add' | 'subtract') {
|
|
265
161
|
const {startDay, endDay} = this.formModel.fields,
|
|
266
162
|
appDay = LocalDate.currentAppDay(),
|
|
267
|
-
start = startDay.value,
|
|
268
|
-
end = endDay.value,
|
|
163
|
+
start: LocalDate = startDay.value,
|
|
164
|
+
end: LocalDate = endDay.value,
|
|
269
165
|
diff = end.diff(start),
|
|
270
166
|
incr = diff + 1;
|
|
271
167
|
|
|
@@ -294,7 +190,53 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
294
190
|
return startDay === endDay.subtract(value, unit).nextDay();
|
|
295
191
|
}
|
|
296
192
|
|
|
297
|
-
|
|
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}) {
|
|
298
240
|
const rawA = recordA?.raw,
|
|
299
241
|
rawB = recordB?.raw,
|
|
300
242
|
sortValA = this.getComparableValForDim(rawA, rawA?.cubeDimension),
|
|
@@ -303,7 +245,7 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
303
245
|
return defaultComparator(sortValA, sortValB);
|
|
304
246
|
}
|
|
305
247
|
|
|
306
|
-
getComparableValForDim(raw, dim) {
|
|
248
|
+
private getComparableValForDim(raw, dim) {
|
|
307
249
|
const rawVal = raw ? raw[dim] : null;
|
|
308
250
|
if (rawVal == null) return null;
|
|
309
251
|
|
|
@@ -320,40 +262,197 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
320
262
|
}
|
|
321
263
|
}
|
|
322
264
|
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
};
|
|
325
274
|
}
|
|
326
275
|
|
|
327
|
-
|
|
328
|
-
|
|
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();
|
|
285
|
+
}
|
|
286
|
+
|
|
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});
|
|
329
316
|
}
|
|
330
317
|
|
|
331
|
-
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
|
|
332
352
|
try {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
+
});
|
|
343
365
|
});
|
|
344
366
|
} catch (e) {
|
|
345
367
|
XH.handleException(e, {title: 'Error loading lookups for filtering'});
|
|
346
368
|
}
|
|
369
|
+
|
|
370
|
+
return ret;
|
|
347
371
|
}
|
|
348
372
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
+
}
|
|
358
457
|
}
|
|
359
458
|
}
|