@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/admin/AdminUtils.ts +5 -0
  3. package/admin/App.scss +6 -0
  4. package/admin/AppModel.ts +19 -7
  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/Rest.ts +8 -0
  8. package/admin/columns/Tracking.ts +144 -42
  9. package/admin/columns/index.ts +1 -0
  10. package/admin/tabs/activity/tracking/ActivityTracking.scss +18 -0
  11. package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +309 -210
  12. package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +81 -51
  13. package/admin/tabs/activity/tracking/chart/AggChartModel.ts +218 -0
  14. package/admin/tabs/activity/tracking/chart/AggChartPanel.ts +61 -0
  15. package/admin/tabs/activity/tracking/datafields/DataFieldsEditor.ts +147 -0
  16. package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +133 -0
  17. package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +123 -59
  18. package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +110 -54
  19. package/admin/tabs/client/ClientTab.ts +2 -4
  20. package/admin/tabs/client/clients/ClientsModel.ts +10 -11
  21. package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +1 -2
  22. package/admin/tabs/general/GeneralTab.ts +2 -0
  23. package/build/types/admin/AdminUtils.d.ts +2 -0
  24. package/build/types/admin/AppModel.d.ts +4 -1
  25. package/build/types/admin/{tabs/client/clients/ClientsColumns.d.ts → columns/Clients.d.ts} +3 -7
  26. package/build/types/admin/columns/Core.d.ts +5 -5
  27. package/build/types/admin/columns/Rest.d.ts +1 -0
  28. package/build/types/admin/columns/Tracking.d.ts +13 -4
  29. package/build/types/admin/columns/index.d.ts +1 -0
  30. package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +31 -28
  31. package/build/types/admin/tabs/activity/tracking/chart/AggChartModel.d.ts +33 -0
  32. package/build/types/admin/tabs/activity/tracking/chart/AggChartPanel.d.ts +2 -0
  33. package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditor.d.ts +2 -0
  34. package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.d.ts +46 -0
  35. package/build/types/admin/tabs/activity/tracking/detail/ActivityDetailModel.d.ts +14 -1
  36. package/build/types/cmp/form/FormModel.d.ts +19 -30
  37. package/build/types/cmp/form/field/SubformsFieldModel.d.ts +25 -22
  38. package/build/types/core/HoistBase.d.ts +2 -2
  39. package/build/types/data/cube/CubeField.d.ts +4 -5
  40. package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -3
  41. package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -3
  42. package/cmp/error/ErrorBoundaryModel.ts +1 -1
  43. package/cmp/form/FormModel.ts +20 -28
  44. package/cmp/form/field/SubformsFieldModel.ts +28 -22
  45. package/cmp/grid/columns/DatesTimes.ts +1 -2
  46. package/cmp/grid/impl/GridHScrollbar.ts +1 -2
  47. package/core/HoistBase.ts +12 -12
  48. package/data/cube/CubeField.ts +17 -18
  49. package/package.json +1 -1
  50. package/svc/TrackService.ts +2 -0
  51. package/tsconfig.tsbuildinfo +1 -1
  52. package/admin/tabs/activity/tracking/charts/ChartsModel.ts +0 -218
  53. package/admin/tabs/activity/tracking/charts/ChartsPanel.ts +0 -76
  54. package/build/types/admin/tabs/activity/tracking/charts/ChartsModel.d.ts +0 -34
  55. package/build/types/admin/tabs/activity/tracking/charts/ChartsPanel.d.ts +0 -2
  56. /package/admin/tabs/{client → general}/feedback/FeedbackPanel.ts +0 -0
  57. /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
- override persistWith = PERSIST_ACTIVITY;
25
-
26
+ /** FormModel for server-side querying controls. */
26
27
  @managed formModel: FormModel;
27
- @managed groupingChooserModel: GroupingChooserModel;
28
- @managed cube: Cube;
29
- @managed filterChooserModel: FilterChooserModel;
30
- @managed gridModel: GridModel;
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.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
- ] 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.loadLookupsAsync();
104
+ this.formModel = this.createQueryFormModel();
141
105
 
142
- this.groupingChooserModel = new GroupingChooserModel({
143
- dimensions: this.cube.dimensions,
144
- persistWith: this.persistWith,
145
- initialValue: this._defaultDims
146
- });
106
+ this.dataFieldsEditorModel = new DataFieldsEditorModel(this);
107
+ this.markPersist('dataFields');
147
108
 
148
- const hidden = true;
149
- this.gridModel = new GridModel({
150
- treeMode: true,
151
- treeStyle: TreeStyle.HIGHLIGHTS_AND_BORDERS,
152
- persistWith: {
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
- colChooserModel: true,
158
- enableExport: true,
159
- exportOptions: {filename: exportFilename('activity-summary')},
160
- emptyText: 'No activity reported...',
161
- sortBy: ['cubeLabel'],
162
- columns: [
163
- {
164
- field: {
165
- name: 'cubeLabel',
166
- type: 'string',
167
- displayName: 'Tracked Activity'
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.fetchService.postJson({
133
+ const data = await XH.postJson({
210
134
  url: 'trackLogAdmin',
211
- body: this.query,
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
- async loadGridAsync() {
229
- const {cube, gridModel, dimensions} = this,
230
- data = cube.executeQuery({
231
- dimensions,
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
- resetQuery() {
258
- const {formModel, filterChooserModel, groupingChooserModel, _defaultDims} = this;
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
- cubeLabelComparator(valA, valB, sortDir, abs, {recordA, recordB, defaultComparator}) {
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
- private get defaultStartDay() {
324
- return LocalDate.currentAppDay();
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
- private get defaultEndDay() {
328
- return LocalDate.currentAppDay();
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 async loadLookupsAsync() {
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
- const lookups = await XH.fetchJson({url: 'trackLogAdmin/lookups'});
334
- this.filterChooserModel.fieldSpecs.forEach(spec => {
335
- const {field} = spec,
336
- lookup = lookups[field] ? compact(lookups[field]) : null;
337
-
338
- if (!isEmpty(lookup)) {
339
- spec.values = lookup;
340
- spec.enableValues = true;
341
- spec.hasExplicitValues = true;
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
- @computed
350
- private get query() {
351
- const {values} = this.formModel;
352
- return {
353
- startDay: values.startDay,
354
- endDay: values.endDay,
355
- maxRows: values.maxRows,
356
- filters: this.filterChooserModel.value
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
  }