@xh/hoist 73.0.0-SNAPSHOT.1745973083869 → 73.0.0-SNAPSHOT.1745976013413
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 +2 -0
- package/admin/AdminUtils.ts +0 -5
- package/admin/AppModel.ts +5 -17
- package/admin/tabs/activity/tracking/ActivityTracking.scss +0 -18
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +199 -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 +59 -114
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +30 -61
- 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/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/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
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,8 @@ Requires `hoist-core >= 30.0` with new APIs to support the consolidated Admin Co
|
|
|
10
10
|
|
|
11
11
|
* Added a new "Clients" Admin Console tab- a consolidated view of all websocket-connected clients
|
|
12
12
|
across all instances in the cluster.
|
|
13
|
+
* Updated `FormModel` to support `persistWith` for storing and recalling its values, including
|
|
14
|
+
developer options to persist all or a provided subset of fields.
|
|
13
15
|
|
|
14
16
|
### 🐞 Bug Fixes
|
|
15
17
|
|
package/admin/AdminUtils.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {AppModel} from '@xh/hoist/admin/AppModel';
|
|
8
7
|
import {XH} from '@xh/hoist/core';
|
|
9
8
|
import {LocalDate} from '@xh/hoist/utils/datetime';
|
|
10
9
|
|
|
@@ -22,7 +21,3 @@ export function exportFilename(moduleName: string): string {
|
|
|
22
21
|
export function exportFilenameWithDate(moduleName: string): () => string {
|
|
23
22
|
return () => `${XH.appCode}-${moduleName}-${LocalDate.today()}`;
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
export function getAppModel<T extends AppModel>() {
|
|
27
|
-
return XH.appModel as T;
|
|
28
|
-
}
|
package/admin/AppModel.ts
CHANGED
|
@@ -7,21 +7,21 @@
|
|
|
7
7
|
import {clusterTab} from '@xh/hoist/admin/tabs/cluster/ClusterTab';
|
|
8
8
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
9
9
|
import {TabConfig, TabContainerModel} from '@xh/hoist/cmp/tab';
|
|
10
|
-
import {
|
|
11
|
-
import {HoistAppModel, XH} from '@xh/hoist/core';
|
|
10
|
+
import {HoistAppModel, managed, XH} from '@xh/hoist/core';
|
|
12
11
|
import {Icon} from '@xh/hoist/icon';
|
|
13
12
|
import {without} from 'lodash';
|
|
14
13
|
import {Route} from 'router5';
|
|
15
14
|
import {activityTrackingPanel} from './tabs/activity/tracking/ActivityTrackingPanel';
|
|
16
|
-
import {clientTab} from './tabs/client/ClientTab';
|
|
17
15
|
import {generalTab} from './tabs/general/GeneralTab';
|
|
18
16
|
import {monitorTab} from './tabs/monitor/MonitorTab';
|
|
19
17
|
import {userDataTab} from './tabs/userData/UserDataTab';
|
|
18
|
+
import {clientTab} from './tabs/client/ClientTab';
|
|
20
19
|
|
|
21
20
|
export class AppModel extends HoistAppModel {
|
|
22
|
-
|
|
21
|
+
static instance: AppModel;
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
@managed
|
|
24
|
+
tabModel: TabContainerModel;
|
|
25
25
|
|
|
26
26
|
static get readonly() {
|
|
27
27
|
return !XH.getUser().isHoistAdmin;
|
|
@@ -40,11 +40,6 @@ export class AppModel extends HoistAppModel {
|
|
|
40
40
|
GridModel.DEFAULT_AUTOSIZE_MODE = 'managed';
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
override async initAsync() {
|
|
44
|
-
await this.initViewManagerModelsAsync();
|
|
45
|
-
await super.initAsync();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
43
|
override getRoutes(): Route[] {
|
|
49
44
|
return [
|
|
50
45
|
{
|
|
@@ -166,11 +161,4 @@ export class AppModel extends HoistAppModel {
|
|
|
166
161
|
const appCodes = without(XH.clientApps, XH.clientAppCode, 'mobile');
|
|
167
162
|
return appCodes.find(it => it === 'app') ?? appCodes[0];
|
|
168
163
|
}
|
|
169
|
-
|
|
170
|
-
async initViewManagerModelsAsync() {
|
|
171
|
-
this.viewManagerModels.activityTracking = await ViewManagerModel.createAsync({
|
|
172
|
-
type: 'xhAdminActivityTrackingView',
|
|
173
|
-
typeDisplayName: 'View'
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
164
|
}
|
|
@@ -31,24 +31,6 @@
|
|
|
31
31
|
margin-right: var(--xh-pad-half-px);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
&__data-fields-editor {
|
|
36
|
-
width: 50vw;
|
|
37
|
-
min-width: 700px;
|
|
38
|
-
max-width: 1000px;
|
|
39
|
-
min-height: 200px;
|
|
40
|
-
|
|
41
|
-
&__row {
|
|
42
|
-
align-items: flex-start;
|
|
43
|
-
flex: none;
|
|
44
|
-
margin-left: 5px;
|
|
45
|
-
margin-right: 5px;
|
|
46
|
-
|
|
47
|
-
&:first-child {
|
|
48
|
-
margin-top: 5px;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
34
|
}
|
|
53
35
|
|
|
54
36
|
.xh-admin-activity-detail {
|
|
@@ -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,140 @@ 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
|
+
] as CubeFieldSpec[]
|
|
110
|
+
});
|
|
115
111
|
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
+
});
|
|
139
|
+
|
|
140
|
+
this.loadLookupsAsync();
|
|
141
|
+
|
|
142
|
+
this.groupingChooserModel = new GroupingChooserModel({
|
|
143
|
+
dimensions: this.cube.dimensions,
|
|
144
|
+
persistWith: this.persistWith,
|
|
145
|
+
initialValue: this._defaultDims
|
|
146
|
+
});
|
|
147
|
+
|
|
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
|
|
126
156
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
});
|
|
133
202
|
}
|
|
134
203
|
|
|
135
204
|
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
136
|
-
const {enabled, cube
|
|
205
|
+
const {enabled, cube} = this;
|
|
137
206
|
if (!enabled) return;
|
|
138
207
|
|
|
139
208
|
try {
|
|
140
|
-
const data = await XH.postJson({
|
|
209
|
+
const data = await XH.fetchService.postJson({
|
|
141
210
|
url: 'trackLogAdmin',
|
|
142
|
-
body: query,
|
|
211
|
+
body: this.query,
|
|
143
212
|
loadSpec
|
|
144
213
|
});
|
|
145
214
|
|
|
146
|
-
data.forEach(it =>
|
|
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
|
+
});
|
|
147
220
|
|
|
148
221
|
await cube.loadDataAsync(data);
|
|
149
222
|
} catch (e) {
|
|
@@ -152,23 +225,47 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
152
225
|
}
|
|
153
226
|
}
|
|
154
227
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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));
|
|
159
253
|
}
|
|
160
254
|
}
|
|
161
255
|
|
|
162
256
|
@action
|
|
163
|
-
|
|
164
|
-
|
|
257
|
+
resetQuery() {
|
|
258
|
+
const {formModel, filterChooserModel, groupingChooserModel, _defaultDims} = this;
|
|
259
|
+
formModel.init();
|
|
260
|
+
filterChooserModel.setValue(null);
|
|
261
|
+
groupingChooserModel.setValue(_defaultDims);
|
|
165
262
|
}
|
|
166
263
|
|
|
167
|
-
adjustDates(dir
|
|
264
|
+
adjustDates(dir) {
|
|
168
265
|
const {startDay, endDay} = this.formModel.fields,
|
|
169
266
|
appDay = LocalDate.currentAppDay(),
|
|
170
|
-
start
|
|
171
|
-
end
|
|
267
|
+
start = startDay.value,
|
|
268
|
+
end = endDay.value,
|
|
172
269
|
diff = end.diff(start),
|
|
173
270
|
incr = diff + 1;
|
|
174
271
|
|
|
@@ -197,42 +294,7 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
197
294
|
return startDay === endDay.subtract(value, unit).nextDay();
|
|
198
295
|
}
|
|
199
296
|
|
|
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}) {
|
|
297
|
+
cubeLabelComparator(valA, valB, sortDir, abs, {recordA, recordB, defaultComparator}) {
|
|
236
298
|
const rawA = recordA?.raw,
|
|
237
299
|
rawB = recordB?.raw,
|
|
238
300
|
sortValA = this.getComparableValForDim(rawA, rawA?.cubeDimension),
|
|
@@ -241,7 +303,7 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
241
303
|
return defaultComparator(sortValA, sortValB);
|
|
242
304
|
}
|
|
243
305
|
|
|
244
|
-
|
|
306
|
+
getComparableValForDim(raw, dim) {
|
|
245
307
|
const rawVal = raw ? raw[dim] : null;
|
|
246
308
|
if (rawVal == null) return null;
|
|
247
309
|
|
|
@@ -266,6 +328,24 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
266
328
|
return LocalDate.currentAppDay();
|
|
267
329
|
}
|
|
268
330
|
|
|
331
|
+
private async loadLookupsAsync() {
|
|
332
|
+
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
|
+
}
|
|
343
|
+
});
|
|
344
|
+
} catch (e) {
|
|
345
|
+
XH.handleException(e, {title: 'Error loading lookups for filtering'});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
269
349
|
@computed
|
|
270
350
|
private get query() {
|
|
271
351
|
const {values} = this.formModel;
|
|
@@ -276,181 +356,4 @@ export class ActivityTrackingModel extends HoistModel {
|
|
|
276
356
|
filters: this.filterChooserModel.value
|
|
277
357
|
};
|
|
278
358
|
}
|
|
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
359
|
}
|