@xh/hoist 73.0.0-SNAPSHOT.1745973083869 → 73.0.0-SNAPSHOT.1746025071597
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 +7 -1
- package/admin/AdminUtils.ts +0 -5
- package/admin/App.scss +6 -0
- package/admin/AppModel.ts +5 -17
- package/admin/{tabs/client/clients/ClientsColumns.ts → columns/Clients.ts} +20 -53
- package/admin/columns/Core.ts +34 -35
- package/admin/columns/Tracking.ts +74 -44
- package/admin/columns/index.ts +1 -0
- package/admin/tabs/activity/tracking/ActivityTracking.scss +0 -18
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +205 -296
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +51 -81
- package/admin/tabs/activity/tracking/charts/ChartsModel.ts +218 -0
- package/admin/tabs/activity/tracking/charts/ChartsPanel.ts +76 -0
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +60 -114
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +40 -63
- package/admin/tabs/client/clients/ClientsModel.ts +10 -11
- package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +2 -1
- package/build/types/admin/AdminUtils.d.ts +0 -2
- package/build/types/admin/AppModel.d.ts +1 -4
- 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/Tracking.d.ts +7 -4
- package/build/types/admin/columns/index.d.ts +1 -0
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +26 -30
- package/build/types/admin/tabs/activity/tracking/charts/ChartsModel.d.ts +34 -0
- package/build/types/admin/tabs/activity/tracking/charts/ChartsPanel.d.ts +2 -0
- package/build/types/admin/tabs/activity/tracking/detail/ActivityDetailModel.d.ts +1 -13
- package/build/types/cmp/form/FormModel.d.ts +40 -17
- package/build/types/cmp/form/field/SubformsFieldModel.d.ts +18 -20
- package/build/types/core/HoistBase.d.ts +2 -2
- package/build/types/data/cube/CubeField.d.ts +5 -4
- 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 +112 -20
- package/cmp/form/field/SubformsFieldModel.ts +22 -28
- package/cmp/grid/impl/GridHScrollbar.ts +2 -1
- package/core/HoistBase.ts +12 -12
- package/data/cube/CubeField.ts +18 -17
- package/package.json +1 -1
- package/svc/TrackService.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/admin/tabs/activity/tracking/chart/AggChartModel.ts +0 -218
- package/admin/tabs/activity/tracking/chart/AggChartPanel.ts +0 -61
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditor.ts +0 -147
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +0 -133
- package/build/types/admin/tabs/activity/tracking/chart/AggChartModel.d.ts +0 -33
- package/build/types/admin/tabs/activity/tracking/chart/AggChartPanel.d.ts +0 -2
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditor.d.ts +0 -2
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.d.ts +0 -46
|
@@ -4,42 +4,30 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {exportFilename
|
|
7
|
+
import {exportFilename} 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';
|
|
13
9
|
import {FilterChooserModel} from '@xh/hoist/cmp/filter';
|
|
14
10
|
import {FormModel} from '@xh/hoist/cmp/form';
|
|
15
|
-
import {
|
|
11
|
+
import {GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
|
|
16
12
|
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
|
|
17
|
-
import {HoistModel, LoadSpec, managed,
|
|
13
|
+
import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
18
14
|
import {Cube, CubeFieldSpec, FieldSpec} from '@xh/hoist/data';
|
|
19
|
-
import {
|
|
20
|
-
import {action, computed, makeObservable
|
|
15
|
+
import {fmtNumber} from '@xh/hoist/format';
|
|
16
|
+
import {action, computed, makeObservable} from '@xh/hoist/mobx';
|
|
21
17
|
import {LocalDate} from '@xh/hoist/utils/datetime';
|
|
22
|
-
import {compact,
|
|
18
|
+
import {compact, isEmpty, round} from 'lodash';
|
|
23
19
|
import moment from 'moment';
|
|
24
20
|
|
|
25
|
-
export
|
|
26
|
-
/** FormModel for server-side querying controls. */
|
|
27
|
-
@managed formModel: FormModel;
|
|
21
|
+
export const PERSIST_ACTIVITY = {localStorageKey: 'xhAdminActivityState'};
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
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[] = [];
|
|
23
|
+
export class ActivityTrackingModel extends HoistModel {
|
|
24
|
+
override persistWith = PERSIST_ACTIVITY;
|
|
41
25
|
|
|
42
|
-
@
|
|
26
|
+
@managed formModel: FormModel;
|
|
27
|
+
@managed groupingChooserModel: GroupingChooserModel;
|
|
28
|
+
@managed cube: Cube;
|
|
29
|
+
@managed filterChooserModel: FilterChooserModel;
|
|
30
|
+
@managed gridModel: GridModel;
|
|
43
31
|
|
|
44
32
|
get enabled(): boolean {
|
|
45
33
|
return XH.trackService.enabled;
|
|
@@ -49,13 +37,17 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
49
37
|
return this.groupingChooserModel.value;
|
|
50
38
|
}
|
|
51
39
|
|
|
52
|
-
|
|
53
|
-
|
|
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`;
|
|
54
47
|
}
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return !!this.filterChooserModel.value;
|
|
49
|
+
get endDay(): LocalDate {
|
|
50
|
+
return this.formModel.values.endDay;
|
|
59
51
|
}
|
|
60
52
|
|
|
61
53
|
get maxRowOptions() {
|
|
@@ -77,31 +69,12 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
77
69
|
return this.maxRows === this.cube.store.allCount;
|
|
78
70
|
}
|
|
79
71
|
|
|
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
|
-
|
|
95
72
|
private _monthFormat = 'MMM YYYY';
|
|
73
|
+
private _defaultDims = ['username'];
|
|
96
74
|
|
|
97
75
|
constructor() {
|
|
98
76
|
super();
|
|
99
77
|
makeObservable(this);
|
|
100
|
-
|
|
101
|
-
this.persistWith = {viewManagerModel: this.viewManagerModel};
|
|
102
|
-
this.markPersist('showFilterChooser');
|
|
103
|
-
|
|
104
|
-
// TODO - persist maxRows via FM persistence (to be merged shortly)
|
|
105
78
|
this.formModel = new FormModel({
|
|
106
79
|
fields: [
|
|
107
80
|
{name: 'startDay', initialValue: () => this.defaultStartDay},
|
|
@@ -110,40 +83,146 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
110
83
|
]
|
|
111
84
|
});
|
|
112
85
|
|
|
113
|
-
this.
|
|
114
|
-
|
|
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
|
+
});
|
|
115
113
|
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
this.filterChooserModel = new FilterChooserModel({
|
|
115
|
+
fieldSpecs: [
|
|
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
|
+
});
|
|
143
|
+
|
|
144
|
+
this.loadLookupsAsync();
|
|
145
|
+
|
|
146
|
+
this.groupingChooserModel = new GroupingChooserModel({
|
|
147
|
+
dimensions: this.cube.dimensions,
|
|
148
|
+
persistWith: this.persistWith,
|
|
149
|
+
initialValue: this._defaultDims
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const hidden = true;
|
|
153
|
+
this.gridModel = new GridModel({
|
|
154
|
+
treeMode: true,
|
|
155
|
+
treeStyle: TreeStyle.HIGHLIGHTS_AND_BORDERS,
|
|
156
|
+
persistWith: {
|
|
157
|
+
...this.persistWith,
|
|
158
|
+
path: 'aggGridModel',
|
|
159
|
+
persistSort: false
|
|
126
160
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
161
|
+
colChooserModel: true,
|
|
162
|
+
enableExport: true,
|
|
163
|
+
exportOptions: {filename: exportFilename('activity-summary')},
|
|
164
|
+
emptyText: 'No activity reported...',
|
|
165
|
+
sortBy: ['cubeLabel'],
|
|
166
|
+
columns: [
|
|
167
|
+
{
|
|
168
|
+
field: {
|
|
169
|
+
name: 'cubeLabel',
|
|
170
|
+
type: 'string',
|
|
171
|
+
displayName: 'Tracked Activity'
|
|
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
|
+
});
|
|
133
208
|
}
|
|
134
209
|
|
|
135
210
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
136
|
-
const {enabled, cube
|
|
211
|
+
const {enabled, cube} = this;
|
|
137
212
|
if (!enabled) return;
|
|
138
213
|
|
|
139
214
|
try {
|
|
140
|
-
const data = await XH.postJson({
|
|
215
|
+
const data = await XH.fetchService.postJson({
|
|
141
216
|
url: 'trackLogAdmin',
|
|
142
|
-
body: query,
|
|
217
|
+
body: this.query,
|
|
143
218
|
loadSpec
|
|
144
219
|
});
|
|
145
220
|
|
|
146
|
-
data.forEach(it =>
|
|
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
|
+
});
|
|
147
226
|
|
|
148
227
|
await cube.loadDataAsync(data);
|
|
149
228
|
} catch (e) {
|
|
@@ -152,23 +231,47 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
152
231
|
}
|
|
153
232
|
}
|
|
154
233
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
234
|
+
async loadGridAsync() {
|
|
235
|
+
const {cube, gridModel, dimensions} = this,
|
|
236
|
+
data = cube.executeQuery({
|
|
237
|
+
dimensions,
|
|
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));
|
|
159
259
|
}
|
|
160
260
|
}
|
|
161
261
|
|
|
162
262
|
@action
|
|
163
|
-
|
|
164
|
-
|
|
263
|
+
resetQuery() {
|
|
264
|
+
const {formModel, filterChooserModel, groupingChooserModel, _defaultDims} = this;
|
|
265
|
+
formModel.init();
|
|
266
|
+
filterChooserModel.setValue(null);
|
|
267
|
+
groupingChooserModel.setValue(_defaultDims);
|
|
165
268
|
}
|
|
166
269
|
|
|
167
|
-
adjustDates(dir
|
|
270
|
+
adjustDates(dir) {
|
|
168
271
|
const {startDay, endDay} = this.formModel.fields,
|
|
169
272
|
appDay = LocalDate.currentAppDay(),
|
|
170
|
-
start
|
|
171
|
-
end
|
|
273
|
+
start = startDay.value,
|
|
274
|
+
end = endDay.value,
|
|
172
275
|
diff = end.diff(start),
|
|
173
276
|
incr = diff + 1;
|
|
174
277
|
|
|
@@ -197,42 +300,7 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
197
300
|
return startDay === endDay.subtract(value, unit).nextDay();
|
|
198
301
|
}
|
|
199
302
|
|
|
200
|
-
|
|
201
|
-
return fieldName ? (this.cube.store.getField(fieldName)?.displayName ?? fieldName) : null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
//------------------
|
|
205
|
-
// Implementation
|
|
206
|
-
//------------------
|
|
207
|
-
private async loadGridAsync() {
|
|
208
|
-
const {cube, gridModel, dimensions} = this,
|
|
209
|
-
data = cube.executeQuery({
|
|
210
|
-
dimensions,
|
|
211
|
-
includeRoot: true,
|
|
212
|
-
includeLeaves: true
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
data.forEach(node => this.separateLeafRows(node));
|
|
216
|
-
gridModel.loadData(data);
|
|
217
|
-
await gridModel.preSelectFirstAsync();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Cube emits leaves in "children" collection - rename that collection to "leafRows" so we can
|
|
221
|
-
// carry the leaves with the record, but deliberately not show them in the tree grid. We only
|
|
222
|
-
// want the tree grid to show aggregate records.
|
|
223
|
-
private separateLeafRows(node) {
|
|
224
|
-
if (isEmpty(node.children)) return;
|
|
225
|
-
|
|
226
|
-
const childrenAreLeaves = !node.children[0].children;
|
|
227
|
-
if (childrenAreLeaves) {
|
|
228
|
-
node.leafRows = node.children;
|
|
229
|
-
delete node.children;
|
|
230
|
-
} else {
|
|
231
|
-
node.children.forEach(child => this.separateLeafRows(child));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
private cubeLabelComparator(valA, valB, sortDir, abs, {recordA, recordB, defaultComparator}) {
|
|
303
|
+
cubeLabelComparator(valA, valB, sortDir, abs, {recordA, recordB, defaultComparator}) {
|
|
236
304
|
const rawA = recordA?.raw,
|
|
237
305
|
rawB = recordB?.raw,
|
|
238
306
|
sortValA = this.getComparableValForDim(rawA, rawA?.cubeDimension),
|
|
@@ -241,7 +309,7 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
241
309
|
return defaultComparator(sortValA, sortValB);
|
|
242
310
|
}
|
|
243
311
|
|
|
244
|
-
|
|
312
|
+
getComparableValForDim(raw, dim) {
|
|
245
313
|
const rawVal = raw ? raw[dim] : null;
|
|
246
314
|
if (rawVal == null) return null;
|
|
247
315
|
|
|
@@ -266,6 +334,24 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
266
334
|
return LocalDate.currentAppDay();
|
|
267
335
|
}
|
|
268
336
|
|
|
337
|
+
private async loadLookupsAsync() {
|
|
338
|
+
try {
|
|
339
|
+
const lookups = await XH.fetchJson({url: 'trackLogAdmin/lookups'});
|
|
340
|
+
this.filterChooserModel.fieldSpecs.forEach(spec => {
|
|
341
|
+
const {field} = spec,
|
|
342
|
+
lookup = lookups[field] ? compact(lookups[field]) : null;
|
|
343
|
+
|
|
344
|
+
if (!isEmpty(lookup)) {
|
|
345
|
+
spec.values = lookup;
|
|
346
|
+
spec.enableValues = true;
|
|
347
|
+
spec.hasExplicitValues = true;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
} catch (e) {
|
|
351
|
+
XH.handleException(e, {title: 'Error loading lookups for filtering'});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
269
355
|
@computed
|
|
270
356
|
private get query() {
|
|
271
357
|
const {values} = this.formModel;
|
|
@@ -276,181 +362,4 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
276
362
|
filters: this.filterChooserModel.value
|
|
277
363
|
};
|
|
278
364
|
}
|
|
279
|
-
|
|
280
|
-
//------------------------
|
|
281
|
-
// Impl - core data models
|
|
282
|
-
//------------------------
|
|
283
|
-
@action
|
|
284
|
-
private createAndSetCoreModels() {
|
|
285
|
-
this.cube = this.createCube();
|
|
286
|
-
this.filterChooserModel = this.createFilterChooserModel();
|
|
287
|
-
this.groupingChooserModel = this.createGroupingChooserModel();
|
|
288
|
-
this.gridModel = this.createGridModel();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
private createCube(): Cube {
|
|
292
|
-
const fields = [
|
|
293
|
-
Col.browser.field,
|
|
294
|
-
Col.category.field,
|
|
295
|
-
Col.severity.field,
|
|
296
|
-
Col.correlationId.field,
|
|
297
|
-
Col.data.field,
|
|
298
|
-
{...(Col.dateCreated.field as FieldSpec), displayName: 'Timestamp'},
|
|
299
|
-
Col.day.field,
|
|
300
|
-
Col.dayRange.field,
|
|
301
|
-
Col.device.field,
|
|
302
|
-
Col.elapsed.field,
|
|
303
|
-
Col.entryCount.field,
|
|
304
|
-
Col.impersonating.field,
|
|
305
|
-
Col.msg.field,
|
|
306
|
-
Col.userAgent.field,
|
|
307
|
-
Col.username.field,
|
|
308
|
-
{name: 'count', type: 'int', aggregator: 'CHILD_COUNT'},
|
|
309
|
-
{name: 'month', type: 'string', isDimension: true, aggregator: 'UNIQUE'},
|
|
310
|
-
Col.url.field,
|
|
311
|
-
Col.instance.field,
|
|
312
|
-
Col.appVersion.field,
|
|
313
|
-
Col.appEnvironment.field,
|
|
314
|
-
...this.dataFields
|
|
315
|
-
] as CubeFieldSpec[];
|
|
316
|
-
|
|
317
|
-
return new Cube({fields});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
private createFilterChooserModel(): FilterChooserModel {
|
|
321
|
-
// TODO - data fields?
|
|
322
|
-
const ret = new FilterChooserModel({
|
|
323
|
-
persistWith: {...this.persistWith, persistFavorites: false},
|
|
324
|
-
fieldSpecs: [
|
|
325
|
-
{field: 'category'},
|
|
326
|
-
{field: 'correlationId'},
|
|
327
|
-
{field: 'username', displayName: 'User'},
|
|
328
|
-
{field: 'device'},
|
|
329
|
-
{field: 'browser'},
|
|
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: 'msg', displayName: 'Message'},
|
|
341
|
-
{field: 'data'},
|
|
342
|
-
{field: 'userAgent'},
|
|
343
|
-
{field: 'url', displayName: 'URL'},
|
|
344
|
-
{field: 'instance'},
|
|
345
|
-
{field: 'severity'},
|
|
346
|
-
{field: 'appVersion'},
|
|
347
|
-
{field: 'appEnvironment', displayName: 'Environment'}
|
|
348
|
-
]
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// Load lookups - not awaited
|
|
352
|
-
try {
|
|
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
|
-
});
|
|
365
|
-
});
|
|
366
|
-
} catch (e) {
|
|
367
|
-
XH.handleException(e, {title: 'Error loading lookups for filtering'});
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return ret;
|
|
371
|
-
}
|
|
372
|
-
|
|
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.url, hidden},
|
|
417
|
-
{...Col.instance, hidden},
|
|
418
|
-
...this.dataFieldCols.map(it => ({...it, hidden: !it.appData.showInAggGrid}))
|
|
419
|
-
]
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
//------------------------------
|
|
424
|
-
// Impl - data fields processing
|
|
425
|
-
//------------------------------
|
|
426
|
-
private processRawTrackLog(raw: PlainObject) {
|
|
427
|
-
try {
|
|
428
|
-
raw.day = LocalDate.from(raw.day);
|
|
429
|
-
raw.month = raw.day.format(this._monthFormat);
|
|
430
|
-
raw.dayRange = {min: raw.day, max: raw.day};
|
|
431
|
-
|
|
432
|
-
const data = JSON.parse(raw.data);
|
|
433
|
-
if (isEmpty(data)) return;
|
|
434
|
-
|
|
435
|
-
this.dataFields.forEach(df => {
|
|
436
|
-
const path = df.path;
|
|
437
|
-
raw[df.name] = get(data, path);
|
|
438
|
-
});
|
|
439
|
-
} catch (e) {
|
|
440
|
-
this.logError(`Error processing raw track log`, e);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
private getDfRenderer(df: ActivityTrackingDataFieldSpec): ColumnRenderer {
|
|
445
|
-
switch (df.type) {
|
|
446
|
-
case 'number':
|
|
447
|
-
return numberRenderer();
|
|
448
|
-
case 'date':
|
|
449
|
-
return dateTimeSecRenderer();
|
|
450
|
-
case 'localDate':
|
|
451
|
-
return dateRenderer();
|
|
452
|
-
default:
|
|
453
|
-
return v => v ?? '-';
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
365
|
}
|