@xh/hoist 73.0.0-SNAPSHOT.1745973083869 → 73.0.0-SNAPSHOT.1746025071597
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/admin/AdminUtils.ts +0 -5
- package/admin/App.scss +6 -0
- package/admin/AppModel.ts +5 -17
- package/admin/{tabs/client/clients/ClientsColumns.ts → columns/Clients.ts} +20 -53
- package/admin/columns/Core.ts +34 -35
- package/admin/columns/Tracking.ts +74 -44
- package/admin/columns/index.ts +1 -0
- package/admin/tabs/activity/tracking/ActivityTracking.scss +0 -18
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +205 -296
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +51 -81
- package/admin/tabs/activity/tracking/charts/ChartsModel.ts +218 -0
- package/admin/tabs/activity/tracking/charts/ChartsPanel.ts +76 -0
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +60 -114
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +40 -63
- package/admin/tabs/client/clients/ClientsModel.ts +10 -11
- package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +2 -1
- package/build/types/admin/AdminUtils.d.ts +0 -2
- package/build/types/admin/AppModel.d.ts +1 -4
- package/build/types/admin/{tabs/client/clients/ClientsColumns.d.ts → columns/Clients.d.ts} +3 -7
- package/build/types/admin/columns/Core.d.ts +5 -5
- package/build/types/admin/columns/Tracking.d.ts +7 -4
- package/build/types/admin/columns/index.d.ts +1 -0
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +26 -30
- package/build/types/admin/tabs/activity/tracking/charts/ChartsModel.d.ts +34 -0
- package/build/types/admin/tabs/activity/tracking/charts/ChartsPanel.d.ts +2 -0
- package/build/types/admin/tabs/activity/tracking/detail/ActivityDetailModel.d.ts +1 -13
- package/build/types/cmp/form/FormModel.d.ts +40 -17
- package/build/types/cmp/form/field/SubformsFieldModel.d.ts +18 -20
- package/build/types/core/HoistBase.d.ts +2 -2
- package/build/types/data/cube/CubeField.d.ts +5 -4
- package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -3
- package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -3
- package/cmp/error/ErrorBoundaryModel.ts +1 -1
- package/cmp/form/FormModel.ts +112 -20
- package/cmp/form/field/SubformsFieldModel.ts +22 -28
- package/cmp/grid/impl/GridHScrollbar.ts +2 -1
- package/core/HoistBase.ts +12 -12
- package/data/cube/CubeField.ts +18 -17
- package/package.json +1 -1
- package/svc/TrackService.ts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/admin/tabs/activity/tracking/chart/AggChartModel.ts +0 -218
- package/admin/tabs/activity/tracking/chart/AggChartPanel.ts +0 -61
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditor.ts +0 -147
- package/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.ts +0 -133
- package/build/types/admin/tabs/activity/tracking/chart/AggChartModel.d.ts +0 -33
- package/build/types/admin/tabs/activity/tracking/chart/AggChartPanel.d.ts +0 -2
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditor.d.ts +0 -2
- package/build/types/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel.d.ts +0 -46
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file belongs to Hoist, an application development toolkit
|
|
3
|
-
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
-
*
|
|
5
|
-
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
|
-
*/
|
|
7
|
-
import {ChartModel} from '@xh/hoist/cmp/chart';
|
|
8
|
-
import {HoistModel, lookup, managed, SelectOption} from '@xh/hoist/core';
|
|
9
|
-
import {Cube, StoreRecord} from '@xh/hoist/data';
|
|
10
|
-
import {bindable, computed, makeObservable} from '@xh/hoist/mobx';
|
|
11
|
-
import {LocalDate} from '@xh/hoist/utils/datetime';
|
|
12
|
-
import {pluralize} from '@xh/hoist/utils/js';
|
|
13
|
-
import {isEmpty, last, sortBy} from 'lodash';
|
|
14
|
-
import moment from 'moment';
|
|
15
|
-
import {ActivityTrackingModel} from '../ActivityTrackingModel';
|
|
16
|
-
|
|
17
|
-
export class AggChartModel extends HoistModel {
|
|
18
|
-
@lookup(ActivityTrackingModel)
|
|
19
|
-
activityTrackingModel: ActivityTrackingModel;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Metric to chart on Y axis - one of:
|
|
23
|
-
* - entryCount - count of total track log entries within the primary dim group.
|
|
24
|
-
* - count - count of unique secondary dim values within the primary dim group.
|
|
25
|
-
* - elapsed - avg elapsed time in ms for the primary dim group.
|
|
26
|
-
* - any other numeric, aggregated custom data field metrics, if so configured
|
|
27
|
-
*/
|
|
28
|
-
@bindable metric: string = 'entryCount';
|
|
29
|
-
|
|
30
|
-
@computed
|
|
31
|
-
get metricLabel() {
|
|
32
|
-
return this.selectableMetrics.find(it => it.value === this.metric)?.label ?? this.metric;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@bindable incWeekends: boolean = true;
|
|
36
|
-
|
|
37
|
-
@managed chartModel: ChartModel;
|
|
38
|
-
|
|
39
|
-
get showAsTimeseries(): boolean {
|
|
40
|
-
return this.primaryDim === 'day';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
get selectableMetrics(): SelectOption[] {
|
|
44
|
-
const {activityTrackingModel, secondaryDim, secondaryDimLabel} = this;
|
|
45
|
-
if (!activityTrackingModel) return [];
|
|
46
|
-
|
|
47
|
-
const ret: SelectOption[] = [
|
|
48
|
-
{
|
|
49
|
-
label: 'Entries [count]',
|
|
50
|
-
value: 'entryCount'
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
label: 'Elapsed ms [avg]',
|
|
54
|
-
value: 'elapsed'
|
|
55
|
-
}
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
if (secondaryDim) {
|
|
59
|
-
ret.push({
|
|
60
|
-
label: `Unique ${pluralize(secondaryDimLabel)} [count]`,
|
|
61
|
-
value: 'count'
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const dfMetrics = activityTrackingModel.dataFields.filter(
|
|
66
|
-
it => (it.type === 'int' || it.type === 'number') && it.aggregator
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
dfMetrics.forEach(it => {
|
|
70
|
-
ret.push({
|
|
71
|
-
label: `${it.displayName} [${it.aggregator.toLowerCase()}]`,
|
|
72
|
-
value: it.name
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return sortBy(ret, 'label');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
constructor() {
|
|
80
|
-
super();
|
|
81
|
-
makeObservable(this);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
override onLinked() {
|
|
85
|
-
this.chartModel = this.createChartModel();
|
|
86
|
-
|
|
87
|
-
const {persistWith} = this.activityTrackingModel;
|
|
88
|
-
this.markPersist('metric', {...persistWith, path: 'chartMetric'});
|
|
89
|
-
this.markPersist('incWeekends', {...persistWith, path: 'chartIncWeekends'});
|
|
90
|
-
|
|
91
|
-
this.addReaction({
|
|
92
|
-
track: () => [this.data, this.metric, this.incWeekends],
|
|
93
|
-
run: () => this.loadChart()
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
//-----------------
|
|
98
|
-
// Implementation
|
|
99
|
-
//-----------------
|
|
100
|
-
private get cube(): Cube {
|
|
101
|
-
return this.activityTrackingModel?.cube;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private get dimensions() {
|
|
105
|
-
return this.activityTrackingModel.dimensions;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private get primaryDim(): string {
|
|
109
|
-
return this.dimensions[0];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private get primaryDimLabel(): string {
|
|
113
|
-
return this.getDisplayName(this.primaryDim);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private get secondaryDim(): string {
|
|
117
|
-
const {dimensions} = this;
|
|
118
|
-
return dimensions.length >= 2 ? dimensions[1] : null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private get secondaryDimLabel(): string {
|
|
122
|
-
return this.getDisplayName(this.secondaryDim);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private get data() {
|
|
126
|
-
const roots = this.activityTrackingModel.gridModel.store.allRootRecords;
|
|
127
|
-
return roots.length ? roots[0].children : [];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private createChartModel(): ChartModel {
|
|
131
|
-
return new ChartModel({
|
|
132
|
-
highchartsConfig: {
|
|
133
|
-
chart: {type: 'column', animation: false},
|
|
134
|
-
plotOptions: {
|
|
135
|
-
column: {
|
|
136
|
-
animation: false,
|
|
137
|
-
borderWidth: 0,
|
|
138
|
-
events: {
|
|
139
|
-
click: e => this.selectRow(e)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
legend: {enabled: false},
|
|
144
|
-
title: {text: null},
|
|
145
|
-
xAxis: {type: 'category', title: {}},
|
|
146
|
-
yAxis: [{title: {text: null}, allowDecimals: false}]
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private selectRow(e) {
|
|
152
|
-
const id = `root>>${this.primaryDim}=[${e.point.name}]`;
|
|
153
|
-
this.activityTrackingModel.gridModel.selectAsync(id);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private loadChart() {
|
|
157
|
-
const {primaryDim, chartModel, primaryDimLabel} = this,
|
|
158
|
-
xAxisTitle = ['day', 'month'].includes(primaryDim) ? null : primaryDimLabel,
|
|
159
|
-
series = this.getSeriesData();
|
|
160
|
-
|
|
161
|
-
chartModel.setSeries(series);
|
|
162
|
-
chartModel.updateHighchartsConfig({
|
|
163
|
-
xAxis: {title: {text: xAxisTitle}}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private getSeriesData() {
|
|
168
|
-
const {data, metric, metricLabel, primaryDim, showAsTimeseries, incWeekends} = this,
|
|
169
|
-
sortedData = sortBy(data, aggRow => {
|
|
170
|
-
const {cubeLabel} = aggRow.data;
|
|
171
|
-
switch (primaryDim) {
|
|
172
|
-
case 'month':
|
|
173
|
-
return moment(cubeLabel, 'MMM YYYY').valueOf();
|
|
174
|
-
default:
|
|
175
|
-
return cubeLabel;
|
|
176
|
-
}
|
|
177
|
-
}),
|
|
178
|
-
chartData = [];
|
|
179
|
-
|
|
180
|
-
// Early out if no data.
|
|
181
|
-
if (isEmpty(sortedData)) {
|
|
182
|
-
return [{metric: metricLabel, data: chartData}];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Special handling for timeseries - pad series internally so we can use a category axis
|
|
186
|
-
// with option to skip weekends, while retaining relative spacing between included days.
|
|
187
|
-
if (showAsTimeseries) {
|
|
188
|
-
// Index data we do have by day, for quick retrieval below.
|
|
189
|
-
const byDay: Record<string, StoreRecord> = {};
|
|
190
|
-
sortedData.forEach(it => {
|
|
191
|
-
byDay[it.data.cubeLabel] = it;
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Walk from first to last day, ensuring we have a point or placeholder for each one.
|
|
195
|
-
let dataDay = LocalDate.get(sortedData[0].data.cubeLabel);
|
|
196
|
-
const lastDay = LocalDate.get(last(sortedData).data.cubeLabel);
|
|
197
|
-
while (dataDay <= lastDay) {
|
|
198
|
-
if (incWeekends || dataDay.isWeekday) {
|
|
199
|
-
const xVal = dataDay.toString(),
|
|
200
|
-
yVal = byDay[xVal]?.data[metric] ?? null;
|
|
201
|
-
chartData.push([xVal, Math.round(yVal)]);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
dataDay = dataDay.nextDay();
|
|
205
|
-
}
|
|
206
|
-
} else {
|
|
207
|
-
sortedData.forEach(it => {
|
|
208
|
-
chartData.push([it.data.cubeLabel, Math.round(it.data[metric])]);
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return [{name: metricLabel, data: chartData}];
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private getDisplayName(fieldName: string) {
|
|
216
|
-
return this.activityTrackingModel?.getDisplayName(fieldName) ?? fieldName;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file belongs to Hoist, an application development toolkit
|
|
3
|
-
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
-
*
|
|
5
|
-
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
|
-
*/
|
|
7
|
-
import {chart} from '@xh/hoist/cmp/chart';
|
|
8
|
-
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
9
|
-
import {modalToggleButton} from '@xh/hoist/desktop/cmp/button';
|
|
10
|
-
import {checkbox, select} from '@xh/hoist/desktop/cmp/input';
|
|
11
|
-
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
12
|
-
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
13
|
-
import {Icon} from '@xh/hoist/icon/Icon';
|
|
14
|
-
import {AggChartModel} from './AggChartModel';
|
|
15
|
-
|
|
16
|
-
export const aggChartPanel = hoistCmp.factory({
|
|
17
|
-
model: creates(AggChartModel),
|
|
18
|
-
|
|
19
|
-
render({model, ...props}) {
|
|
20
|
-
return panel({
|
|
21
|
-
collapsedTitle: 'Aggregate Activity Chart',
|
|
22
|
-
collapsedIcon: Icon.chartBar(),
|
|
23
|
-
modelConfig: {
|
|
24
|
-
modalSupport: {width: '90vw', height: '60vh'},
|
|
25
|
-
side: 'bottom',
|
|
26
|
-
defaultSize: 400,
|
|
27
|
-
persistWith: {...model.activityTrackingModel.persistWith, path: 'aggChartPanel'}
|
|
28
|
-
},
|
|
29
|
-
compactHeader: true,
|
|
30
|
-
item: chart({model: model.chartModel}),
|
|
31
|
-
bbar: toolbar({
|
|
32
|
-
items: [
|
|
33
|
-
Icon.chartBar(),
|
|
34
|
-
metricSwitcher(),
|
|
35
|
-
incWeekendsCheckbox(),
|
|
36
|
-
'-',
|
|
37
|
-
modalToggleButton()
|
|
38
|
-
]
|
|
39
|
-
}),
|
|
40
|
-
...props
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const metricSwitcher = hoistCmp.factory<AggChartModel>(({model}) => {
|
|
46
|
-
return select({
|
|
47
|
-
bind: 'metric',
|
|
48
|
-
options: model.selectableMetrics,
|
|
49
|
-
enableFilter: false,
|
|
50
|
-
flex: 1
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const incWeekendsCheckbox = hoistCmp.factory<AggChartModel>(({model}) =>
|
|
55
|
-
checkbox({
|
|
56
|
-
omit: !model.showAsTimeseries,
|
|
57
|
-
bind: 'incWeekends',
|
|
58
|
-
label: 'Weekends',
|
|
59
|
-
style: {marginLeft: '10px'}
|
|
60
|
-
})
|
|
61
|
-
);
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import {DataFieldsEditorModel} from '@xh/hoist/admin/tabs/activity/tracking/datafields/DataFieldsEditorModel';
|
|
2
|
-
import {form, FormModel} from '@xh/hoist/cmp/form';
|
|
3
|
-
import {br, filler, hbox, hspacer, placeholder, span, vspacer} from '@xh/hoist/cmp/layout';
|
|
4
|
-
import {hoistCmp, uses} from '@xh/hoist/core';
|
|
5
|
-
import {FieldType} from '@xh/hoist/data';
|
|
6
|
-
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
7
|
-
import {formField} from '@xh/hoist/desktop/cmp/form';
|
|
8
|
-
import {checkbox, select, textInput} from '@xh/hoist/desktop/cmp/input';
|
|
9
|
-
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
10
|
-
import {Icon} from '@xh/hoist/icon';
|
|
11
|
-
import {popover} from '@xh/hoist/kit/blueprint';
|
|
12
|
-
import {isEmpty} from 'lodash';
|
|
13
|
-
|
|
14
|
-
export const dataFieldsEditor = hoistCmp.factory({
|
|
15
|
-
model: uses(DataFieldsEditorModel),
|
|
16
|
-
|
|
17
|
-
render({model}) {
|
|
18
|
-
const {showEditor, appliedDataFieldCount, hasAppliedDataFields} = model;
|
|
19
|
-
|
|
20
|
-
return popover({
|
|
21
|
-
isOpen: showEditor,
|
|
22
|
-
item: button({
|
|
23
|
-
text: `Extract Data Fields${appliedDataFieldCount ? ' (' + appliedDataFieldCount + ')' : ''}`,
|
|
24
|
-
icon: Icon.json({prefix: hasAppliedDataFields ? 'fas' : 'far'}),
|
|
25
|
-
intent: hasAppliedDataFields ? 'primary' : null,
|
|
26
|
-
outlined: showEditor,
|
|
27
|
-
onClick: () => model.show()
|
|
28
|
-
}),
|
|
29
|
-
content: formPanel(),
|
|
30
|
-
popoverClassName: 'xh-popup xh-popup--framed',
|
|
31
|
-
onClose: () => model.close()
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const formPanel = hoistCmp.factory<DataFieldsEditorModel>(({model}) => {
|
|
37
|
-
const {formModel, dataFields} = model;
|
|
38
|
-
return panel({
|
|
39
|
-
className: 'xh-admin-activity-panel__data-fields-editor',
|
|
40
|
-
item: form({
|
|
41
|
-
model: formModel,
|
|
42
|
-
items: isEmpty(dataFields.value)
|
|
43
|
-
? emptyPlaceholder()
|
|
44
|
-
: [
|
|
45
|
-
...dataFields.value.map((dfModel: FormModel) => {
|
|
46
|
-
return form({
|
|
47
|
-
model: dfModel,
|
|
48
|
-
fieldDefaults: {label: null, commitOnChange: true},
|
|
49
|
-
item: hbox({
|
|
50
|
-
className: 'xh-admin-activity-panel__data-fields-editor__row',
|
|
51
|
-
alignItems: 'flex-start',
|
|
52
|
-
flex: 'none',
|
|
53
|
-
items: [
|
|
54
|
-
formField({
|
|
55
|
-
field: 'path',
|
|
56
|
-
flex: 1,
|
|
57
|
-
item: textInput({placeholder: 'Path (dot-delimited)'})
|
|
58
|
-
}),
|
|
59
|
-
formField({
|
|
60
|
-
field: 'displayName',
|
|
61
|
-
width: 180,
|
|
62
|
-
item: textInput({placeholder: 'Display Name'})
|
|
63
|
-
}),
|
|
64
|
-
formField({
|
|
65
|
-
field: 'type',
|
|
66
|
-
width: 120,
|
|
67
|
-
item: select({
|
|
68
|
-
placeholder: 'Data Type',
|
|
69
|
-
options: Object.values(FieldType).sort()
|
|
70
|
-
})
|
|
71
|
-
}),
|
|
72
|
-
formField({
|
|
73
|
-
field: 'aggregator',
|
|
74
|
-
width: 120,
|
|
75
|
-
item: select({
|
|
76
|
-
placeholder: 'Aggregator',
|
|
77
|
-
// TODO - cascade select with type?
|
|
78
|
-
options: model.aggTokens,
|
|
79
|
-
enableClear: true
|
|
80
|
-
})
|
|
81
|
-
}),
|
|
82
|
-
formField({
|
|
83
|
-
field: 'isDimension',
|
|
84
|
-
marginTop: 8,
|
|
85
|
-
item: checkbox({label: 'dimension'})
|
|
86
|
-
}),
|
|
87
|
-
button({
|
|
88
|
-
icon: Icon.copy(),
|
|
89
|
-
marginTop: 3,
|
|
90
|
-
marginRight: 4,
|
|
91
|
-
onClick: () => model.cloneField(dfModel)
|
|
92
|
-
}),
|
|
93
|
-
button({
|
|
94
|
-
icon: Icon.delete(),
|
|
95
|
-
intent: 'danger',
|
|
96
|
-
marginTop: 3,
|
|
97
|
-
marginRight: 4,
|
|
98
|
-
onClick: () => dataFields.remove(dfModel)
|
|
99
|
-
})
|
|
100
|
-
]
|
|
101
|
-
})
|
|
102
|
-
});
|
|
103
|
-
}),
|
|
104
|
-
hbox(filler(), addButton(), filler()),
|
|
105
|
-
filler({minHeight: 10})
|
|
106
|
-
]
|
|
107
|
-
}),
|
|
108
|
-
bbar: [
|
|
109
|
-
filler(),
|
|
110
|
-
button({
|
|
111
|
-
text: 'Cancel',
|
|
112
|
-
onClick: () => model.close()
|
|
113
|
-
}),
|
|
114
|
-
hspacer(5),
|
|
115
|
-
button({
|
|
116
|
-
text: 'Apply + Reload',
|
|
117
|
-
icon: Icon.check(),
|
|
118
|
-
outlined: true,
|
|
119
|
-
intent: 'success',
|
|
120
|
-
disabled: !formModel.isValid,
|
|
121
|
-
onClick: () => model.applyAndClose()
|
|
122
|
-
})
|
|
123
|
-
]
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const emptyPlaceholder = hoistCmp.factory<DataFieldsEditorModel>(({model}) => {
|
|
128
|
-
return placeholder(
|
|
129
|
-
span(
|
|
130
|
-
'Define fields to extract from the optional data payload on each activity record.',
|
|
131
|
-
br(),
|
|
132
|
-
'Extracted data can then be viewed on both aggregate and detail levels.'
|
|
133
|
-
),
|
|
134
|
-
vspacer(),
|
|
135
|
-
addButton()
|
|
136
|
-
);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const addButton = hoistCmp.factory<DataFieldsEditorModel>(({model}) => {
|
|
140
|
-
return button({
|
|
141
|
-
text: 'Add field...',
|
|
142
|
-
icon: Icon.add(),
|
|
143
|
-
intent: 'primary',
|
|
144
|
-
outlined: true,
|
|
145
|
-
onClick: () => model.addField()
|
|
146
|
-
});
|
|
147
|
-
});
|
|
@@ -1,133 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { ActivityTrackingModel } from '@xh/hoist/admin/tabs/activity/tracking/ActivityTrackingModel';
|
|
2
|
-
import { FormModel, SubformsFieldModel } from '@xh/hoist/cmp/form';
|
|
3
|
-
import { HoistModel } from '@xh/hoist/core';
|
|
4
|
-
import { AggregatorToken, FieldType } from '@xh/hoist/data';
|
|
5
|
-
/**
|
|
6
|
-
* Slimmed down {@link CubeFieldSpec} for persisted specs of fields to be extracted from the `data`
|
|
7
|
-
* block of loaded track statements and promoted to top-level columns in the grids. These are the
|
|
8
|
-
* entities (stored on parent `ActivityTrackingModel`) that are edited by the this component.
|
|
9
|
-
*/
|
|
10
|
-
export interface ActivityTrackingDataFieldSpec {
|
|
11
|
-
/**
|
|
12
|
-
* Path to field data within the `data` block of each track log entry. Can be dot-delimited for
|
|
13
|
-
* nested data (e.g. `timings.preAuth`). See {@link ActivityTrackingModel.processRawTrackLog}.
|
|
14
|
-
*/
|
|
15
|
-
path: string;
|
|
16
|
-
/**
|
|
17
|
-
* Normalized name for the field for use in Cube/Grid - adds `df_` prefix to avoid conflicts
|
|
18
|
-
* and strips out dot-delimiters. See {@link ActivityTrackingModel.setDataFields}.
|
|
19
|
-
*/
|
|
20
|
-
name: string;
|
|
21
|
-
displayName?: string;
|
|
22
|
-
type?: FieldType;
|
|
23
|
-
isDimension?: boolean;
|
|
24
|
-
aggregator?: AggregatorToken;
|
|
25
|
-
}
|
|
26
|
-
export declare class DataFieldsEditorModel extends HoistModel {
|
|
27
|
-
showEditor: boolean;
|
|
28
|
-
formModel: FormModel;
|
|
29
|
-
private parentModel;
|
|
30
|
-
aggTokens: AggregatorToken[];
|
|
31
|
-
get dataFields(): SubformsFieldModel;
|
|
32
|
-
get appliedDataFieldCount(): number;
|
|
33
|
-
get hasAppliedDataFields(): boolean;
|
|
34
|
-
constructor(parentModel: ActivityTrackingModel);
|
|
35
|
-
show(): void;
|
|
36
|
-
applyAndClose(): void;
|
|
37
|
-
close(): void;
|
|
38
|
-
addField(): void;
|
|
39
|
-
cloneField(formModel: FormModel): void;
|
|
40
|
-
private syncFromParent;
|
|
41
|
-
/**
|
|
42
|
-
* Normalize field specs and set onto parent.
|
|
43
|
-
* Note, will de-dupe fields by name w/o any alert to user.
|
|
44
|
-
*/
|
|
45
|
-
private syncToParent;
|
|
46
|
-
}
|