@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/admin/AdminUtils.ts +0 -5
  3. package/admin/App.scss +6 -0
  4. package/admin/AppModel.ts +5 -17
  5. package/admin/{tabs/client/clients/ClientsColumns.ts → columns/Clients.ts} +20 -53
  6. package/admin/columns/Core.ts +34 -35
  7. package/admin/columns/Tracking.ts +74 -44
  8. package/admin/columns/index.ts +1 -0
  9. package/admin/tabs/activity/tracking/ActivityTracking.scss +0 -18
  10. package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +205 -296
  11. package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +51 -81
  12. package/admin/tabs/activity/tracking/charts/ChartsModel.ts +218 -0
  13. package/admin/tabs/activity/tracking/charts/ChartsPanel.ts +76 -0
  14. package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +60 -114
  15. package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +40 -63
  16. package/admin/tabs/client/clients/ClientsModel.ts +10 -11
  17. package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +2 -1
  18. package/build/types/admin/AdminUtils.d.ts +0 -2
  19. package/build/types/admin/AppModel.d.ts +1 -4
  20. package/build/types/admin/{tabs/client/clients/ClientsColumns.d.ts → columns/Clients.d.ts} +3 -7
  21. package/build/types/admin/columns/Core.d.ts +5 -5
  22. package/build/types/admin/columns/Tracking.d.ts +7 -4
  23. package/build/types/admin/columns/index.d.ts +1 -0
  24. package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +26 -30
  25. package/build/types/admin/tabs/activity/tracking/charts/ChartsModel.d.ts +34 -0
  26. package/build/types/admin/tabs/activity/tracking/charts/ChartsPanel.d.ts +2 -0
  27. package/build/types/admin/tabs/activity/tracking/detail/ActivityDetailModel.d.ts +1 -13
  28. package/build/types/cmp/form/FormModel.d.ts +40 -17
  29. package/build/types/cmp/form/field/SubformsFieldModel.d.ts +18 -20
  30. package/build/types/core/HoistBase.d.ts +2 -2
  31. package/build/types/data/cube/CubeField.d.ts +5 -4
  32. package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -3
  33. package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -3
  34. package/cmp/error/ErrorBoundaryModel.ts +1 -1
  35. package/cmp/form/FormModel.ts +112 -20
  36. package/cmp/form/field/SubformsFieldModel.ts +22 -28
  37. package/cmp/grid/impl/GridHScrollbar.ts +2 -1
  38. package/core/HoistBase.ts +12 -12
  39. package/data/cube/CubeField.ts +18 -17
  40. package/package.json +1 -1
  41. package/svc/TrackService.ts +2 -0
  42. package/tsconfig.tsbuildinfo +1 -1
  43. package/admin/tabs/activity/tracking/chart/AggChartModel.ts +0 -218
  44. package/admin/tabs/activity/tracking/chart/AggChartPanel.ts +0 -61
  45. package/admin/tabs/activity/tracking/datafields/DataFieldsEditor.ts +0 -147
  46. package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +0 -133
  47. package/build/types/admin/tabs/activity/tracking/chart/AggChartModel.d.ts +0 -33
  48. package/build/types/admin/tabs/activity/tracking/chart/AggChartPanel.d.ts +0 -2
  49. package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditor.d.ts +0 -2
  50. 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, getAppModel} from '@xh/hoist/admin/AdminUtils';
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 {ColumnRenderer, ColumnSpec, GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
11
+ import {GridModel, TreeStyle} from '@xh/hoist/cmp/grid';
16
12
  import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
17
- import {HoistModel, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
13
+ import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
18
14
  import {Cube, CubeFieldSpec, FieldSpec} from '@xh/hoist/data';
19
- import {dateRenderer, dateTimeSecRenderer, fmtNumber, numberRenderer} from '@xh/hoist/format';
20
- import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
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, get, isEmpty, isEqual, round} from 'lodash';
18
+ import {compact, isEmpty, round} from 'lodash';
23
19
  import moment from 'moment';
24
20
 
25
- export class ActivityTrackingModel extends HoistModel {
26
- /** FormModel for server-side querying controls. */
27
- @managed formModel: FormModel;
21
+ export const PERSIST_ACTIVITY = {localStorageKey: 'xhAdminActivityState'};
28
22
 
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[] = [];
23
+ export class ActivityTrackingModel extends HoistModel {
24
+ override persistWith = PERSIST_ACTIVITY;
41
25
 
42
- @observable showFilterChooser: boolean = false;
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
- get endDay(): LocalDate {
53
- return this.formModel.values.endDay;
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
- @computed
57
- get hasFilter(): boolean {
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.dataFieldsEditorModel = new DataFieldsEditorModel(this);
114
- this.markPersist('dataFields');
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.addReaction(
117
- {
118
- track: () => this.dataFields,
119
- run: () => this.createAndSetCoreModels(),
120
- fireImmediately: true
121
- },
122
- {
123
- track: () => this.query,
124
- run: () => this.loadAsync(),
125
- debounce: 100
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
- track: () => [this.cube.records, this.dimensions],
129
- run: () => this.loadGridAsync(),
130
- debounce: 100
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, query} = this;
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 => this.processRawTrackLog(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
- @action
156
- setDataFields(dataFields: ActivityTrackingDataFieldSpec[]) {
157
- if (!isEqual(dataFields, this.dataFields)) {
158
- this.dataFields = dataFields ?? [];
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
- toggleFilterChooser() {
164
- this.showFilterChooser = !this.showFilterChooser;
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: 'add' | 'subtract') {
270
+ adjustDates(dir) {
168
271
  const {startDay, endDay} = this.formModel.fields,
169
272
  appDay = LocalDate.currentAppDay(),
170
- start: LocalDate = startDay.value,
171
- end: LocalDate = endDay.value,
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
- getDisplayName(fieldName: string) {
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
- private getComparableValForDim(raw, dim) {
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
  }