@xh/hoist 73.0.0-SNAPSHOT.1745689122610 → 73.0.0-SNAPSHOT.1745973083869
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/admin/AdminUtils.ts +5 -0
- package/admin/AppModel.ts +17 -5
- package/admin/tabs/activity/tracking/ActivityTracking.scss +18 -0
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +296 -199
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +81 -51
- package/admin/tabs/activity/tracking/chart/AggChartModel.ts +218 -0
- package/admin/tabs/activity/tracking/chart/AggChartPanel.ts +61 -0
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditor.ts +147 -0
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +133 -0
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +114 -59
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +61 -30
- package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +1 -2
- package/build/types/admin/AdminUtils.d.ts +2 -0
- package/build/types/admin/AppModel.d.ts +4 -1
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +30 -26
- package/build/types/admin/tabs/activity/tracking/chart/AggChartModel.d.ts +33 -0
- package/build/types/admin/tabs/activity/tracking/chart/AggChartPanel.d.ts +2 -0
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditor.d.ts +2 -0
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.d.ts +46 -0
- package/build/types/admin/tabs/activity/tracking/detail/ActivityDetailModel.d.ts +13 -1
- package/build/types/cmp/form/FormModel.d.ts +15 -27
- package/build/types/cmp/form/field/SubformsFieldModel.d.ts +20 -18
- package/build/types/core/HoistBase.d.ts +2 -2
- package/build/types/data/cube/CubeField.d.ts +4 -5
- package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -3
- package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -3
- package/cmp/error/ErrorBoundaryModel.ts +1 -1
- package/cmp/form/FormModel.ts +18 -28
- package/cmp/form/field/SubformsFieldModel.ts +28 -22
- package/cmp/grid/impl/GridHScrollbar.ts +1 -2
- package/core/HoistBase.ts +12 -12
- package/data/cube/CubeField.ts +17 -18
- package/package.json +1 -1
- package/promise/Promise.ts +8 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/admin/tabs/activity/tracking/charts/ChartsModel.ts +0 -218
- package/admin/tabs/activity/tracking/charts/ChartsPanel.ts +0 -76
- package/build/types/admin/tabs/activity/tracking/charts/ChartsModel.d.ts +0 -34
- package/build/types/admin/tabs/activity/tracking/charts/ChartsPanel.d.ts +0 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {ActivityTrackingModel} from '@xh/hoist/admin/tabs/activity/tracking/ActivityTrackingModel';
|
|
2
|
+
import {FormModel, SubformsFieldModel} from '@xh/hoist/cmp/form';
|
|
3
|
+
import {HoistModel, managed} from '@xh/hoist/core';
|
|
4
|
+
import {AggregatorToken, FieldType, genDisplayName, required} from '@xh/hoist/data';
|
|
5
|
+
import {action, observable, makeObservable} from '@xh/hoist/mobx';
|
|
6
|
+
import {computed} from '@xh/hoist/mobx';
|
|
7
|
+
import {last, uniqBy} from 'lodash';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Slimmed down {@link CubeFieldSpec} for persisted specs of fields to be extracted from the `data`
|
|
11
|
+
* block of loaded track statements and promoted to top-level columns in the grids. These are the
|
|
12
|
+
* entities (stored on parent `ActivityTrackingModel`) that are edited by the this component.
|
|
13
|
+
*/
|
|
14
|
+
export interface ActivityTrackingDataFieldSpec {
|
|
15
|
+
/**
|
|
16
|
+
* Path to field data within the `data` block of each track log entry. Can be dot-delimited for
|
|
17
|
+
* nested data (e.g. `timings.preAuth`). See {@link ActivityTrackingModel.processRawTrackLog}.
|
|
18
|
+
*/
|
|
19
|
+
path: string;
|
|
20
|
+
/**
|
|
21
|
+
* Normalized name for the field for use in Cube/Grid - adds `df_` prefix to avoid conflicts
|
|
22
|
+
* and strips out dot-delimiters. See {@link ActivityTrackingModel.setDataFields}.
|
|
23
|
+
*/
|
|
24
|
+
name: string;
|
|
25
|
+
displayName?: string;
|
|
26
|
+
type?: FieldType;
|
|
27
|
+
isDimension?: boolean;
|
|
28
|
+
aggregator?: AggregatorToken;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class DataFieldsEditorModel extends HoistModel {
|
|
32
|
+
@observable showEditor = false;
|
|
33
|
+
|
|
34
|
+
@managed formModel: FormModel;
|
|
35
|
+
private parentModel: ActivityTrackingModel;
|
|
36
|
+
|
|
37
|
+
aggTokens: AggregatorToken[] = ['AVG', 'MAX', 'MIN', 'SINGLE', 'SUM', 'UNIQUE'];
|
|
38
|
+
|
|
39
|
+
get dataFields(): SubformsFieldModel {
|
|
40
|
+
return this.formModel.fields.dataFields as SubformsFieldModel;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@computed
|
|
44
|
+
get appliedDataFieldCount(): number {
|
|
45
|
+
return this.parentModel.dataFields.length;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get hasAppliedDataFields(): boolean {
|
|
49
|
+
return this.appliedDataFieldCount > 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
constructor(parentModel: ActivityTrackingModel) {
|
|
53
|
+
super();
|
|
54
|
+
makeObservable(this);
|
|
55
|
+
|
|
56
|
+
this.parentModel = parentModel;
|
|
57
|
+
|
|
58
|
+
this.formModel = new FormModel({
|
|
59
|
+
fields: [
|
|
60
|
+
{
|
|
61
|
+
name: 'dataFields',
|
|
62
|
+
subforms: {
|
|
63
|
+
fields: [
|
|
64
|
+
{name: 'path', rules: [required]},
|
|
65
|
+
{name: 'displayName'},
|
|
66
|
+
{name: 'type', initialValue: 'auto'},
|
|
67
|
+
{name: 'isDimension'},
|
|
68
|
+
{name: 'aggregator'}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.addReaction({
|
|
76
|
+
track: () => this.parentModel.dataFields,
|
|
77
|
+
run: () => this.syncFromParent(),
|
|
78
|
+
fireImmediately: true
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@action
|
|
83
|
+
show() {
|
|
84
|
+
this.syncFromParent();
|
|
85
|
+
this.showEditor = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@action
|
|
89
|
+
applyAndClose() {
|
|
90
|
+
this.syncToParent();
|
|
91
|
+
this.showEditor = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@action
|
|
95
|
+
close() {
|
|
96
|
+
this.showEditor = false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
addField() {
|
|
100
|
+
this.dataFields.add();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
cloneField(formModel: FormModel) {
|
|
104
|
+
const {dataFields} = this,
|
|
105
|
+
srcIdx = dataFields.value.indexOf(formModel);
|
|
106
|
+
|
|
107
|
+
dataFields.add({initialValues: formModel.getData(), index: srcIdx + 1});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private syncFromParent() {
|
|
111
|
+
this.formModel.init({
|
|
112
|
+
dataFields: this.parentModel.dataFields
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Normalize field specs and set onto parent.
|
|
118
|
+
* Note, will de-dupe fields by name w/o any alert to user.
|
|
119
|
+
*/
|
|
120
|
+
private syncToParent() {
|
|
121
|
+
const raw = this.formModel.getData().dataFields,
|
|
122
|
+
specs: ActivityTrackingDataFieldSpec[] = raw.map(it => {
|
|
123
|
+
const {displayName, path, aggregator: agg} = it;
|
|
124
|
+
return {
|
|
125
|
+
...it,
|
|
126
|
+
name: 'df_' + path.replaceAll('.', '') + (agg ? `_${agg}` : ''),
|
|
127
|
+
displayName: displayName || genDisplayName(last(path.split('.')))
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this.parentModel.setDataFields(uniqBy(specs, 'name'));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -9,15 +9,27 @@ import * as Col from '@xh/hoist/admin/columns';
|
|
|
9
9
|
import {FormModel} from '@xh/hoist/cmp/form';
|
|
10
10
|
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
11
11
|
import {HoistModel, lookup, managed} from '@xh/hoist/core';
|
|
12
|
-
import {
|
|
12
|
+
import {StoreRecord} from '@xh/hoist/data';
|
|
13
|
+
import {timestampReplacer} from '@xh/hoist/format';
|
|
14
|
+
import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
15
|
+
import {get} from 'lodash';
|
|
13
16
|
import {ActivityTrackingModel} from '../ActivityTrackingModel';
|
|
14
|
-
import {fmtJson, timestampReplacer} from '@xh/hoist/format';
|
|
15
17
|
|
|
16
18
|
export class ActivityDetailModel extends HoistModel {
|
|
17
19
|
@lookup(ActivityTrackingModel) activityTrackingModel: ActivityTrackingModel;
|
|
18
|
-
|
|
19
|
-
@managed
|
|
20
|
-
@observable
|
|
20
|
+
|
|
21
|
+
@managed @observable.ref gridModel: GridModel;
|
|
22
|
+
@managed @observable.ref formModel: FormModel;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional dot-delimited path(s) to filter the displayed `data` payload down to a particular
|
|
26
|
+
* node or nodes, for easier browsing of records with a large data payload. Multiple paths
|
|
27
|
+
* can be separated with `|`.
|
|
28
|
+
*/
|
|
29
|
+
@bindable formattedDataFilterPath: string;
|
|
30
|
+
|
|
31
|
+
/** Stringified, pretty-printed, optionally path-filtered `data` payload. */
|
|
32
|
+
@observable formattedData: string;
|
|
21
33
|
|
|
22
34
|
@computed
|
|
23
35
|
get hasSelection() {
|
|
@@ -30,48 +42,14 @@ export class ActivityDetailModel extends HoistModel {
|
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
override onLinked() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.gridModel = new GridModel({
|
|
36
|
-
sortBy: 'dateCreated|desc',
|
|
37
|
-
colChooserModel: true,
|
|
38
|
-
enableExport: true,
|
|
39
|
-
filterModel: false,
|
|
40
|
-
exportOptions: {
|
|
41
|
-
columns: 'ALL',
|
|
42
|
-
filename: exportFilename('activity-detail')
|
|
43
|
-
},
|
|
44
|
-
emptyText: 'Select a group on the left to see detailed tracking logs.',
|
|
45
|
-
columns: [
|
|
46
|
-
{...Col.impersonatingFlag},
|
|
47
|
-
{...Col.entryId, hidden},
|
|
48
|
-
{...Col.username},
|
|
49
|
-
{...Col.impersonating, hidden},
|
|
50
|
-
{...Col.category},
|
|
51
|
-
{...Col.msg},
|
|
52
|
-
{...Col.browser},
|
|
53
|
-
{...Col.device},
|
|
54
|
-
{...Col.userAgent, hidden},
|
|
55
|
-
{...Col.appVersion},
|
|
56
|
-
{...Col.appEnvironment, hidden},
|
|
57
|
-
{...Col.data, hidden},
|
|
58
|
-
{...Col.url},
|
|
59
|
-
{...Col.correlationId},
|
|
60
|
-
{...Col.instance, hidden},
|
|
61
|
-
{...Col.severity, hidden},
|
|
62
|
-
{...Col.elapsed},
|
|
63
|
-
{...Col.dateCreatedWithSec, displayName: 'Timestamp'}
|
|
64
|
-
]
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
this.formModel = new FormModel({
|
|
68
|
-
readonly: true,
|
|
69
|
-
fields: this.gridModel
|
|
70
|
-
.getLeafColumns()
|
|
71
|
-
.map(it => ({name: it.field, displayName: it.headerName as string}))
|
|
72
|
-
});
|
|
45
|
+
this.markPersist('formattedDataFilterPath', this.activityTrackingModel.persistWith);
|
|
73
46
|
|
|
74
47
|
this.addReaction(
|
|
48
|
+
{
|
|
49
|
+
track: () => this.activityTrackingModel.dataFields,
|
|
50
|
+
run: () => this.createAndSetCoreModels(),
|
|
51
|
+
fireImmediately: true
|
|
52
|
+
},
|
|
75
53
|
{
|
|
76
54
|
track: () => this.activityTrackingModel.gridModel.selectedRecord,
|
|
77
55
|
run: aggRec => this.showActivityEntriesAsync(aggRec)
|
|
@@ -79,11 +57,18 @@ export class ActivityDetailModel extends HoistModel {
|
|
|
79
57
|
{
|
|
80
58
|
track: () => this.gridModel.selectedRecord,
|
|
81
59
|
run: detailRec => this.showEntryDetail(detailRec)
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
track: () => this.formattedDataFilterPath,
|
|
63
|
+
run: () => this.updateFormattedData()
|
|
82
64
|
}
|
|
83
65
|
);
|
|
84
66
|
}
|
|
85
67
|
|
|
86
|
-
|
|
68
|
+
//------------------
|
|
69
|
+
// Implementation
|
|
70
|
+
//------------------
|
|
71
|
+
private async showActivityEntriesAsync(aggRec: StoreRecord) {
|
|
87
72
|
const {gridModel} = this,
|
|
88
73
|
leaves = this.getAllLeafRows(aggRec);
|
|
89
74
|
|
|
@@ -92,7 +77,7 @@ export class ActivityDetailModel extends HoistModel {
|
|
|
92
77
|
}
|
|
93
78
|
|
|
94
79
|
// Extract all leaf, track-entry-level rows from an aggregate record (at any level).
|
|
95
|
-
private getAllLeafRows(aggRec, ret = []) {
|
|
80
|
+
private getAllLeafRows(aggRec: StoreRecord, ret = []) {
|
|
96
81
|
if (!aggRec) return [];
|
|
97
82
|
|
|
98
83
|
if (aggRec.children.length) {
|
|
@@ -106,22 +91,92 @@ export class ActivityDetailModel extends HoistModel {
|
|
|
106
91
|
return ret;
|
|
107
92
|
}
|
|
108
93
|
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
/** Extract data from a (detail) grid record and flush it into our form for display. */
|
|
95
|
+
@action
|
|
96
|
+
private showEntryDetail(detailRec: StoreRecord) {
|
|
97
|
+
this.formModel.init(detailRec?.data ?? {});
|
|
98
|
+
this.updateFormattedData();
|
|
99
|
+
}
|
|
100
|
+
|
|
111
101
|
@action
|
|
112
|
-
private
|
|
113
|
-
const
|
|
114
|
-
trackData =
|
|
102
|
+
private updateFormattedData() {
|
|
103
|
+
const {gridModel, formattedDataFilterPath} = this,
|
|
104
|
+
trackData = gridModel.selectedRecord?.data.data;
|
|
105
|
+
|
|
106
|
+
if (!trackData) {
|
|
107
|
+
this.formattedData = '';
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
115
110
|
|
|
116
|
-
|
|
111
|
+
let parsed = JSON.parse(trackData),
|
|
112
|
+
toFormat = parsed;
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
if (formattedDataFilterPath) {
|
|
115
|
+
const paths = formattedDataFilterPath.split('|');
|
|
116
|
+
if (paths.length > 1) {
|
|
117
|
+
toFormat = {};
|
|
118
|
+
paths.forEach(path => (toFormat[path.trim()] = get(parsed, path.trim())));
|
|
119
|
+
} else {
|
|
120
|
+
toFormat = get(parsed, formattedDataFilterPath.trim());
|
|
121
|
+
}
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
this.formattedData =
|
|
124
|
+
this.formattedData = JSON.stringify(toFormat, timestampReplacer(), 2);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//------------------------
|
|
128
|
+
// Core data-handling models
|
|
129
|
+
//------------------------
|
|
130
|
+
@action
|
|
131
|
+
private createAndSetCoreModels() {
|
|
132
|
+
this.gridModel = this.createGridModel();
|
|
133
|
+
this.formModel = this.createSingleEntryFormModel();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private createGridModel(): GridModel {
|
|
137
|
+
const hidden = true;
|
|
138
|
+
return new GridModel({
|
|
139
|
+
persistWith: {...this.activityTrackingModel.persistWith, path: 'detailGrid'},
|
|
140
|
+
sortBy: 'dateCreated|desc',
|
|
141
|
+
colChooserModel: true,
|
|
142
|
+
enableExport: true,
|
|
143
|
+
filterModel: false,
|
|
144
|
+
exportOptions: {
|
|
145
|
+
columns: 'ALL',
|
|
146
|
+
filename: exportFilename('activity-detail')
|
|
147
|
+
},
|
|
148
|
+
emptyText: 'Select a group on the left to see detailed tracking logs.',
|
|
149
|
+
columns: [
|
|
150
|
+
{...Col.impersonatingFlag},
|
|
151
|
+
{...Col.entryId, hidden},
|
|
152
|
+
{...Col.username},
|
|
153
|
+
{...Col.impersonating, hidden},
|
|
154
|
+
{...Col.category},
|
|
155
|
+
{...Col.msg},
|
|
156
|
+
{...Col.browser},
|
|
157
|
+
{...Col.device},
|
|
158
|
+
{...Col.userAgent, hidden},
|
|
159
|
+
{...Col.appVersion},
|
|
160
|
+
{...Col.appEnvironment, hidden},
|
|
161
|
+
{...Col.data, hidden},
|
|
162
|
+
{...Col.url},
|
|
163
|
+
{...Col.correlationId},
|
|
164
|
+
{...Col.instance, hidden},
|
|
165
|
+
{...Col.severity, hidden},
|
|
166
|
+
{...Col.elapsed},
|
|
167
|
+
{...Col.dateCreatedWithSec, displayName: 'Timestamp'},
|
|
168
|
+
...this.activityTrackingModel.dataFieldCols
|
|
169
|
+
]
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// TODO - don't base on grid cols
|
|
174
|
+
private createSingleEntryFormModel(): FormModel {
|
|
175
|
+
return new FormModel({
|
|
176
|
+
readonly: true,
|
|
177
|
+
fields: this.gridModel
|
|
178
|
+
.getLeafColumns()
|
|
179
|
+
.map(it => ({name: it.field, displayName: it.headerName as string}))
|
|
180
|
+
});
|
|
126
181
|
}
|
|
127
182
|
}
|
|
@@ -7,16 +7,17 @@
|
|
|
7
7
|
import {correlationId, instance} from '@xh/hoist/admin/columns';
|
|
8
8
|
import {form} from '@xh/hoist/cmp/form';
|
|
9
9
|
import {grid, gridCountLabel} from '@xh/hoist/cmp/grid';
|
|
10
|
-
import {a, div, filler, h3, hframe, placeholder, span} from '@xh/hoist/cmp/layout';
|
|
11
|
-
import {
|
|
10
|
+
import {a, br, div, filler, h3, hframe, placeholder, span} from '@xh/hoist/cmp/layout';
|
|
11
|
+
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
12
12
|
import {colChooserButton, exportButton} from '@xh/hoist/desktop/cmp/button';
|
|
13
13
|
import {formField} from '@xh/hoist/desktop/cmp/form';
|
|
14
14
|
import {gridFindField} from '@xh/hoist/desktop/cmp/grid';
|
|
15
|
-
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
15
|
+
import {jsonInput, textInput} from '@xh/hoist/desktop/cmp/input';
|
|
16
16
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
17
17
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
18
18
|
import {dateTimeSecRenderer, numberRenderer} from '@xh/hoist/format';
|
|
19
19
|
import {Icon} from '@xh/hoist/icon/Icon';
|
|
20
|
+
import {tooltip} from '@xh/hoist/kit/blueprint';
|
|
20
21
|
import {ActivityDetailModel} from './ActivityDetailModel';
|
|
21
22
|
|
|
22
23
|
export const activityDetailView = hoistCmp.factory({
|
|
@@ -24,26 +25,28 @@ export const activityDetailView = hoistCmp.factory({
|
|
|
24
25
|
|
|
25
26
|
render({model, ...props}) {
|
|
26
27
|
return panel({
|
|
27
|
-
title: 'Track Log Entries',
|
|
28
|
-
icon: Icon.list(),
|
|
29
28
|
className: 'xh-admin-activity-detail',
|
|
30
|
-
compactHeader: true,
|
|
31
|
-
items: [grid({flex: 1}), detailRecPanel()],
|
|
32
29
|
tbar: tbar(),
|
|
30
|
+
items: [grid({flex: 1}), detailRecPanel()],
|
|
33
31
|
...props
|
|
34
32
|
});
|
|
35
33
|
}
|
|
36
34
|
});
|
|
37
35
|
|
|
38
|
-
const tbar = hoistCmp.factory(({model}) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
const tbar = hoistCmp.factory<ActivityDetailModel>(({model}) => {
|
|
37
|
+
const {gridModel} = model;
|
|
38
|
+
return toolbar({
|
|
39
|
+
compact: true,
|
|
40
|
+
items: [
|
|
41
|
+
filler(),
|
|
42
|
+
gridCountLabel({unit: 'entry'}),
|
|
43
|
+
'-',
|
|
44
|
+
// TODO - these don't react properly to swapping out grid model
|
|
45
|
+
gridFindField({gridModel}),
|
|
46
|
+
colChooserButton({gridModel}),
|
|
47
|
+
exportButton()
|
|
48
|
+
]
|
|
49
|
+
});
|
|
47
50
|
});
|
|
48
51
|
|
|
49
52
|
// Discrete outer panel to retain sizing across master/detail selection changes.
|
|
@@ -54,7 +57,11 @@ const detailRecPanel = hoistCmp.factory<ActivityDetailModel>(({model}) => {
|
|
|
54
57
|
compactHeader: true,
|
|
55
58
|
modelConfig: {
|
|
56
59
|
side: 'bottom',
|
|
57
|
-
defaultSize: 400
|
|
60
|
+
defaultSize: 400,
|
|
61
|
+
persistWith: {
|
|
62
|
+
...model.activityTrackingModel.persistWith,
|
|
63
|
+
path: 'singleActivityDetailPanel'
|
|
64
|
+
}
|
|
58
65
|
},
|
|
59
66
|
item: detailRecForm()
|
|
60
67
|
});
|
|
@@ -125,23 +132,47 @@ const detailRecForm = hoistCmp.factory<ActivityDetailModel>(({model}) => {
|
|
|
125
132
|
formField({field: 'userAgent'})
|
|
126
133
|
]
|
|
127
134
|
}),
|
|
128
|
-
|
|
129
|
-
flex: 1,
|
|
130
|
-
className: 'xh-border-left',
|
|
131
|
-
items: [h3(Icon.json(), 'Additional Data'), additionalDataJsonInput()]
|
|
132
|
-
})
|
|
135
|
+
additionalDataPanel()
|
|
133
136
|
)
|
|
134
137
|
})
|
|
135
|
-
: placeholder('Select an activity tracking record to view details.');
|
|
138
|
+
: placeholder(Icon.detail(), 'Select an activity tracking record to view details.');
|
|
136
139
|
});
|
|
137
140
|
|
|
138
|
-
const
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
const additionalDataPanel = hoistCmp.factory<ActivityDetailModel>(({model}) => {
|
|
142
|
+
return panel({
|
|
143
|
+
flex: 1,
|
|
144
|
+
className: 'xh-border-left',
|
|
145
|
+
items: [
|
|
146
|
+
h3(Icon.json(), 'Additional Data'),
|
|
147
|
+
jsonInput({
|
|
148
|
+
readonly: true,
|
|
149
|
+
width: '100%',
|
|
150
|
+
height: '100%',
|
|
151
|
+
showCopyButton: true,
|
|
152
|
+
value: model.formattedData
|
|
153
|
+
}),
|
|
154
|
+
toolbar({
|
|
155
|
+
compact: true,
|
|
156
|
+
items: [
|
|
157
|
+
textInput({
|
|
158
|
+
placeholder: 'Path filter(s)...',
|
|
159
|
+
leftIcon: Icon.filter(),
|
|
160
|
+
commitOnChange: true,
|
|
161
|
+
enableClear: true,
|
|
162
|
+
flex: 1,
|
|
163
|
+
bind: 'formattedDataFilterPath'
|
|
164
|
+
}),
|
|
165
|
+
tooltip({
|
|
166
|
+
item: Icon.questionCircle({className: 'xh-margin-right'}),
|
|
167
|
+
content: span(
|
|
168
|
+
'Specify one or more dot-delimited paths to filter the JSON data displayed above.',
|
|
169
|
+
br(),
|
|
170
|
+
'Separate multiple paths that you wish to include with a | character.'
|
|
171
|
+
)
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
})
|
|
175
|
+
]
|
|
145
176
|
});
|
|
146
177
|
});
|
|
147
178
|
|
|
@@ -13,9 +13,8 @@ import {LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
|
13
13
|
import {lengthIs, required} from '@xh/hoist/data';
|
|
14
14
|
import {fmtTime, numberRenderer} from '@xh/hoist/format';
|
|
15
15
|
import {Icon} from '@xh/hoist/icon';
|
|
16
|
-
import {bindable, makeObservable} from '@xh/hoist/mobx';
|
|
16
|
+
import {bindable, makeObservable, observable, runInAction} from '@xh/hoist/mobx';
|
|
17
17
|
import {forOwn, orderBy, sortBy} from 'lodash';
|
|
18
|
-
import {observable, runInAction} from 'mobx';
|
|
19
18
|
|
|
20
19
|
export interface PastInstance {
|
|
21
20
|
name: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AppModel } from '@xh/hoist/admin/AppModel';
|
|
1
2
|
/**
|
|
2
3
|
* Generate a standardized filename for an Admin module grid export, without datestamp.
|
|
3
4
|
*/
|
|
@@ -7,3 +8,4 @@ export declare function exportFilename(moduleName: string): string;
|
|
|
7
8
|
* Returned as a closure to ensure current date is evaluated at export time.
|
|
8
9
|
*/
|
|
9
10
|
export declare function exportFilenameWithDate(moduleName: string): () => string;
|
|
11
|
+
export declare function getAppModel<T extends AppModel>(): T;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { TabConfig, TabContainerModel } from '@xh/hoist/cmp/tab';
|
|
2
|
+
import { ViewManagerModel } from '@xh/hoist/cmp/viewmanager';
|
|
2
3
|
import { HoistAppModel } from '@xh/hoist/core';
|
|
3
4
|
import { Route } from 'router5';
|
|
4
5
|
export declare class AppModel extends HoistAppModel {
|
|
5
|
-
static instance: AppModel;
|
|
6
6
|
tabModel: TabContainerModel;
|
|
7
|
+
viewManagerModels: Record<string, ViewManagerModel>;
|
|
7
8
|
static get readonly(): boolean;
|
|
8
9
|
constructor();
|
|
10
|
+
initAsync(): Promise<void>;
|
|
9
11
|
getRoutes(): Route[];
|
|
10
12
|
getAppMenuButtonExtraItems(): any[];
|
|
11
13
|
getTabRoutes(): Route[];
|
|
@@ -13,4 +15,5 @@ export declare class AppModel extends HoistAppModel {
|
|
|
13
15
|
/** Open the primary business-facing application, typically 'app'. */
|
|
14
16
|
openPrimaryApp(): void;
|
|
15
17
|
getPrimaryAppCode(): string;
|
|
18
|
+
initViewManagerModelsAsync(): Promise<void>;
|
|
16
19
|
}
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
+
import { ActivityTrackingDataFieldSpec, DataFieldsEditorModel } from '@xh/hoist/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel';
|
|
1
2
|
import { FilterChooserModel } from '@xh/hoist/cmp/filter';
|
|
2
3
|
import { FormModel } from '@xh/hoist/cmp/form';
|
|
3
|
-
import { GridModel } from '@xh/hoist/cmp/grid';
|
|
4
|
+
import { ColumnSpec, GridModel } from '@xh/hoist/cmp/grid';
|
|
4
5
|
import { GroupingChooserModel } from '@xh/hoist/cmp/grouping';
|
|
5
|
-
import { HoistModel, LoadSpec } from '@xh/hoist/core';
|
|
6
|
+
import { HoistModel, LoadSpec, PlainObject } from '@xh/hoist/core';
|
|
6
7
|
import { Cube } from '@xh/hoist/data';
|
|
7
8
|
import { LocalDate } from '@xh/hoist/utils/datetime';
|
|
8
|
-
export declare const PERSIST_ACTIVITY: {
|
|
9
|
-
localStorageKey: string;
|
|
10
|
-
};
|
|
11
9
|
export declare class ActivityTrackingModel extends HoistModel {
|
|
12
|
-
|
|
13
|
-
localStorageKey: string;
|
|
14
|
-
};
|
|
10
|
+
/** FormModel for server-side querying controls. */
|
|
15
11
|
formModel: FormModel;
|
|
12
|
+
/** Models for data-handling components - can be rebuilt due to change in dataFields. */
|
|
16
13
|
groupingChooserModel: GroupingChooserModel;
|
|
17
14
|
cube: Cube;
|
|
18
15
|
filterChooserModel: FilterChooserModel;
|
|
19
16
|
gridModel: GridModel;
|
|
20
|
-
|
|
21
|
-
get dimensions(): string[];
|
|
17
|
+
dataFieldsEditorModel: DataFieldsEditorModel;
|
|
22
18
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Formerly summarized server-side filters, but was misleading w/new filtering.
|
|
19
|
+
* Optional spec for fields to be extracted from additional `data` returned by track entries
|
|
20
|
+
* and promoted to top-level columns in the grids. Supports dot-delimited paths as names.
|
|
26
21
|
*/
|
|
27
|
-
|
|
22
|
+
dataFields: ActivityTrackingDataFieldSpec[];
|
|
23
|
+
showFilterChooser: boolean;
|
|
24
|
+
get enabled(): boolean;
|
|
25
|
+
get dimensions(): string[];
|
|
28
26
|
get endDay(): LocalDate;
|
|
27
|
+
get hasFilter(): boolean;
|
|
29
28
|
get maxRowOptions(): {
|
|
30
29
|
value: number;
|
|
31
30
|
label: string;
|
|
@@ -33,24 +32,29 @@ export declare class ActivityTrackingModel extends HoistModel {
|
|
|
33
32
|
get maxRows(): number;
|
|
34
33
|
/** True if data loaded from the server has been topped by maxRows. */
|
|
35
34
|
get maxRowsReached(): boolean;
|
|
35
|
+
get dataFieldCols(): ColumnSpec[];
|
|
36
|
+
get viewManagerModel(): import("../../../../cmp/viewmanager").ViewManagerModel<PlainObject>;
|
|
36
37
|
private _monthFormat;
|
|
37
|
-
private _defaultDims;
|
|
38
38
|
constructor();
|
|
39
39
|
doLoadAsync(loadSpec: LoadSpec): Promise<void>;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
adjustDates(dir: any): void;
|
|
40
|
+
setDataFields(dataFields: ActivityTrackingDataFieldSpec[]): void;
|
|
41
|
+
toggleFilterChooser(): void;
|
|
42
|
+
adjustDates(dir: 'add' | 'subtract'): void;
|
|
44
43
|
adjustStartDate(value: any, unit: any): void;
|
|
45
44
|
isInterval(value: any, unit: any): boolean;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
getComparableValForDim(raw: any, dim: any): any;
|
|
45
|
+
getDisplayName(fieldName: string): string;
|
|
46
|
+
private loadGridAsync;
|
|
47
|
+
private separateLeafRows;
|
|
48
|
+
private cubeLabelComparator;
|
|
49
|
+
private getComparableValForDim;
|
|
52
50
|
private get defaultStartDay();
|
|
53
51
|
private get defaultEndDay();
|
|
54
|
-
private loadLookupsAsync;
|
|
55
52
|
private get query();
|
|
53
|
+
private createAndSetCoreModels;
|
|
54
|
+
private createCube;
|
|
55
|
+
private createFilterChooserModel;
|
|
56
|
+
private createGroupingChooserModel;
|
|
57
|
+
private createGridModel;
|
|
58
|
+
private processRawTrackLog;
|
|
59
|
+
private getDfRenderer;
|
|
56
60
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ChartModel } from '@xh/hoist/cmp/chart';
|
|
2
|
+
import { HoistModel, SelectOption } from '@xh/hoist/core';
|
|
3
|
+
import { ActivityTrackingModel } from '../ActivityTrackingModel';
|
|
4
|
+
export declare class AggChartModel extends HoistModel {
|
|
5
|
+
activityTrackingModel: ActivityTrackingModel;
|
|
6
|
+
/**
|
|
7
|
+
* Metric to chart on Y axis - one of:
|
|
8
|
+
* - entryCount - count of total track log entries within the primary dim group.
|
|
9
|
+
* - count - count of unique secondary dim values within the primary dim group.
|
|
10
|
+
* - elapsed - avg elapsed time in ms for the primary dim group.
|
|
11
|
+
* - any other numeric, aggregated custom data field metrics, if so configured
|
|
12
|
+
*/
|
|
13
|
+
metric: string;
|
|
14
|
+
get metricLabel(): string;
|
|
15
|
+
incWeekends: boolean;
|
|
16
|
+
chartModel: ChartModel;
|
|
17
|
+
get showAsTimeseries(): boolean;
|
|
18
|
+
get selectableMetrics(): SelectOption[];
|
|
19
|
+
constructor();
|
|
20
|
+
onLinked(): void;
|
|
21
|
+
private get cube();
|
|
22
|
+
private get dimensions();
|
|
23
|
+
private get primaryDim();
|
|
24
|
+
private get primaryDimLabel();
|
|
25
|
+
private get secondaryDim();
|
|
26
|
+
private get secondaryDimLabel();
|
|
27
|
+
private get data();
|
|
28
|
+
private createChartModel;
|
|
29
|
+
private selectRow;
|
|
30
|
+
private loadChart;
|
|
31
|
+
private getSeriesData;
|
|
32
|
+
private getDisplayName;
|
|
33
|
+
}
|