@xh/hoist 75.0.0-SNAPSHOT.1754335298677 → 75.0.0-SNAPSHOT.1754604038542
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 -0
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +4 -36
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +0 -2
- package/build/types/data/cube/BucketSpec.d.ts +3 -0
- package/build/types/data/cube/Cube.d.ts +42 -41
- package/build/types/data/cube/Query.d.ts +57 -23
- package/build/types/data/cube/View.d.ts +21 -21
- package/build/types/data/cube/ViewRowData.d.ts +40 -0
- package/build/types/data/cube/row/AggregateRow.d.ts +4 -1
- package/build/types/data/cube/row/BaseRow.d.ts +7 -4
- package/build/types/data/cube/row/BucketRow.d.ts +5 -1
- package/build/types/data/cube/row/LeafRow.d.ts +6 -5
- package/build/types/data/index.d.ts +1 -0
- package/data/cube/BucketSpec.ts +3 -0
- package/data/cube/Cube.ts +48 -47
- package/data/cube/Query.ts +72 -24
- package/data/cube/View.ts +30 -26
- package/data/cube/ViewRowData.ts +66 -0
- package/data/cube/row/AggregateRow.ts +5 -2
- package/data/cube/row/BaseRow.ts +30 -20
- package/data/cube/row/BucketRow.ts +5 -1
- package/data/cube/row/LeafRow.ts +8 -6
- package/data/index.ts +1 -0
- package/package.json +1 -1
- package/svc/IdentityService.ts +10 -12
- package/tsconfig.tsbuildinfo +1 -1
package/data/cube/Cube.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {HoistBase, managed, PlainObject, Some} from '@xh/hoist/core';
|
|
|
9
9
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
10
10
|
import {forEachAsync} from '@xh/hoist/utils/async';
|
|
11
11
|
import {CubeField, CubeFieldSpec} from './CubeField';
|
|
12
|
+
import {ViewRowData} from './ViewRowData';
|
|
12
13
|
import {Query, QueryConfig} from './Query';
|
|
13
14
|
import {View} from './View';
|
|
14
15
|
import {Store, StoreRecordIdSpec, StoreTransaction} from '../Store';
|
|
@@ -56,6 +57,35 @@ export interface CubeConfig {
|
|
|
56
57
|
omitFn?: OmitFn;
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Function to be called for each node to aggregate to determine if it should be "locked",
|
|
62
|
+
* preventing drilldown into its children. If true returned for a node, no drilldown will be
|
|
63
|
+
* allowed, and the row will be marked with a boolean "locked" property.
|
|
64
|
+
*/
|
|
65
|
+
export type LockFn = (row: AggregateRow | BucketRow) => boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Function to be called for each node during row generation to determine if it should be
|
|
69
|
+
* skipped in tree output. Useful for removing aggregates that are degenerate due to context.
|
|
70
|
+
* Note that skipping in this way has no effect on aggregations -- all children of this node are
|
|
71
|
+
* simply promoted to their parent node.
|
|
72
|
+
*/
|
|
73
|
+
export type OmitFn = (row: AggregateRow | BucketRow) => boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Function to be called for rows making up an aggregated dimension to determine if the children of
|
|
77
|
+
* that dimension should be dynamically bucketed into additional sub-groupings.
|
|
78
|
+
*
|
|
79
|
+
* An example use case would be a grouped collection of portfolio positions, where any closed
|
|
80
|
+
* positions are identified as such by this function and bucketed into a "Closed" sub-grouping,
|
|
81
|
+
* without having to add something like an "openClosed" dimension that would apply to all
|
|
82
|
+
* aggregations and create an unwanted "Open" grouping.
|
|
83
|
+
*
|
|
84
|
+
* @param rows - the rows being checked for bucketing
|
|
85
|
+
* @returns BucketSpec for configuring dynamic sub-aggregations, or null to perform no bucketing.
|
|
86
|
+
*/
|
|
87
|
+
export type BucketSpecFn = (rows: BaseRow[]) => BucketSpec;
|
|
88
|
+
|
|
59
89
|
/**
|
|
60
90
|
* A data store that supports grouping, aggregating, and filtering data on multiple dimensions.
|
|
61
91
|
*
|
|
@@ -94,7 +124,7 @@ export class Cube extends HoistBase {
|
|
|
94
124
|
this.store = new Store({
|
|
95
125
|
fields: this.parseFields(fields, fieldDefaults),
|
|
96
126
|
idSpec,
|
|
97
|
-
processRawData: processRawData
|
|
127
|
+
processRawData: processRawData,
|
|
98
128
|
freezeData: false,
|
|
99
129
|
idEncodesTreePath: true
|
|
100
130
|
});
|
|
@@ -143,9 +173,9 @@ export class Cube extends HoistBase {
|
|
|
143
173
|
* @param query - Config for query defining the shape of the view.
|
|
144
174
|
* @returns data containing the results of the query as a hierarchical set of rows.
|
|
145
175
|
*/
|
|
146
|
-
executeQuery(query: QueryConfig):
|
|
147
|
-
const q = new Query({...query, cube: this})
|
|
148
|
-
|
|
176
|
+
executeQuery(query: QueryConfig): ViewRowData[] {
|
|
177
|
+
const q = new Query({...query, cube: this}),
|
|
178
|
+
view = new View({query: q}),
|
|
149
179
|
rows = view.result.rows;
|
|
150
180
|
|
|
151
181
|
view.destroy();
|
|
@@ -153,19 +183,19 @@ export class Cube extends HoistBase {
|
|
|
153
183
|
}
|
|
154
184
|
|
|
155
185
|
/**
|
|
156
|
-
* Create a View on
|
|
186
|
+
* Create a dynamic {@link View} of the cube data based on a query. Unlike the static snapshot
|
|
187
|
+
* returned by {@link Cube.executeQuery}, a View created with this method can be configured
|
|
188
|
+
* with `connect:true` to automatically update as the underlying data in the Cube changes.
|
|
157
189
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
190
|
+
* Provide one or more `stores` to automatically populate them with the aggregated data returned
|
|
191
|
+
* by the query, or read the returned {@link View.result} directly.
|
|
160
192
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
193
|
+
* When the returned View is no longer needed, call {@link View.destroy} (or save a reference
|
|
194
|
+
* via an `@managed` model property) to avoid unnecessary processing.
|
|
163
195
|
*
|
|
164
196
|
* @param query - query to be used to construct this view.
|
|
165
|
-
* @param stores - Stores to be loaded/reloaded with
|
|
166
|
-
*
|
|
167
|
-
* @param connect - true to update View automatically when data in
|
|
168
|
-
* the underlying cube is changed. Default false.
|
|
197
|
+
* @param stores - Stores to be automatically loaded/reloaded with View results.
|
|
198
|
+
* @param connect - true to update View automatically when data in the underlying Cube changes.
|
|
169
199
|
*/
|
|
170
200
|
createView({
|
|
171
201
|
query,
|
|
@@ -183,14 +213,12 @@ export class Cube extends HoistBase {
|
|
|
183
213
|
});
|
|
184
214
|
}
|
|
185
215
|
|
|
186
|
-
/**
|
|
187
|
-
* True if the provided view is connected to this Cube for live updates.
|
|
188
|
-
*/
|
|
216
|
+
/** True if the provided view is connected to this Cube for live updates. */
|
|
189
217
|
viewIsConnected(view: View): boolean {
|
|
190
218
|
return this._connectedViews.has(view);
|
|
191
219
|
}
|
|
192
220
|
|
|
193
|
-
/**
|
|
221
|
+
/** Cease pushing further updates to this Cube's data into a previously connected View. */
|
|
194
222
|
disconnectView(view: View) {
|
|
195
223
|
this._connectedViews.delete(view);
|
|
196
224
|
}
|
|
@@ -200,12 +228,10 @@ export class Cube extends HoistBase {
|
|
|
200
228
|
//-------------------
|
|
201
229
|
/**
|
|
202
230
|
* Populate this cube with a new dataset.
|
|
231
|
+
* This method largely delegates to {@link Store.loadData} - see that method for more info.
|
|
203
232
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* Note that this method will update its views asynchronously, in order to avoid locking
|
|
208
|
-
* up the browser when attached to multiple expensive views.
|
|
233
|
+
* Note that this method will update its views asynchronously in order to avoid locking up the
|
|
234
|
+
* browser when attached to multiple expensive views.
|
|
209
235
|
*
|
|
210
236
|
* @param rawData - flat array of lowest/leaf level data rows.
|
|
211
237
|
* @param info - optional metadata to associate with this cube/dataset.
|
|
@@ -303,28 +329,3 @@ export class Cube extends HoistBase {
|
|
|
303
329
|
super.destroy();
|
|
304
330
|
}
|
|
305
331
|
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Function to be called for each node to aggregate to determine if it should be "locked",
|
|
309
|
-
* preventing drilldown into its children. If true returned for a node, no drilldown will be
|
|
310
|
-
* allowed, and the row will be marked with a boolean "locked" property.
|
|
311
|
-
*/
|
|
312
|
-
export type LockFn = (row: AggregateRow | BucketRow) => boolean;
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Function to be called for each node during row generation to determine if it should be
|
|
316
|
-
* skipped in tree output. Useful for removing aggregates that are degenerate due to context.
|
|
317
|
-
* Note that skipping in this way has no effect on aggregations -- all children of this node are
|
|
318
|
-
* simply promoted to their parent node.
|
|
319
|
-
*/
|
|
320
|
-
export type OmitFn = (row: AggregateRow | BucketRow) => boolean;
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Function to be called for each dimension to determine if children of said dimension should be
|
|
324
|
-
* bucketed into additional dynamic dimensions.
|
|
325
|
-
*
|
|
326
|
-
* @param rows - the rows being checked for bucketing
|
|
327
|
-
* @returns a BucketSpec for configuring the bucket to place child rows into, or null to perform
|
|
328
|
-
* no bucketing
|
|
329
|
-
*/
|
|
330
|
-
export type BucketSpecFn = (rows: BaseRow[]) => BucketSpec;
|
package/data/cube/Query.ts
CHANGED
|
@@ -5,53 +5,90 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
import {
|
|
9
|
+
BucketSpecFn,
|
|
10
|
+
Filter,
|
|
11
|
+
FilterLike,
|
|
12
|
+
FilterTestFn,
|
|
13
|
+
LockFn,
|
|
14
|
+
OmitFn,
|
|
15
|
+
parseFilter,
|
|
16
|
+
StoreRecord
|
|
17
|
+
} from '@xh/hoist/data';
|
|
13
18
|
import {throwIf} from '@xh/hoist/utils/js';
|
|
19
|
+
import {find, isEqual} from 'lodash';
|
|
20
|
+
import {Cube} from './Cube';
|
|
21
|
+
import {CubeField} from './CubeField';
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
|
-
* Queries determine what data is extracted
|
|
17
|
-
*
|
|
18
|
-
* Note that if no dimensions are provided, 'includeRoot' or 'includeLeaves' should be true -
|
|
19
|
-
* otherwise no data will be returned!
|
|
24
|
+
* Queries determine what data is extracted, grouped, and aggregated from a {@link Cube}.
|
|
20
25
|
*/
|
|
21
26
|
export interface QueryConfig {
|
|
22
27
|
/**
|
|
23
|
-
*
|
|
24
|
-
* itself on the query config (automatically)
|
|
28
|
+
* The Cube to query. Required, but note that the preferred {@link Cube.executeQuery} API will
|
|
29
|
+
* install a reference to itself on the query config (automatically).
|
|
25
30
|
*/
|
|
26
31
|
cube?: Cube;
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
|
-
* Fields or field names. If unspecified will include all available fields
|
|
30
|
-
*
|
|
34
|
+
* Fields or field names. If unspecified will include all available {@link Cube.fields}.
|
|
35
|
+
* Specify a subset to optimize aggregation performance.
|
|
31
36
|
*/
|
|
32
37
|
fields?: string[] | CubeField[];
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
|
-
* Fields or field names
|
|
36
|
-
*
|
|
40
|
+
* Fields or field names on which data should be grouped and aggregated. These are the ordered
|
|
41
|
+
* grouping levels in the resulting hierarchy - e.g. ['Country', 'State', 'City'].
|
|
42
|
+
*
|
|
43
|
+
* Any fields provided here must also be included in the `fields` array, if specified.
|
|
44
|
+
*
|
|
45
|
+
* If not provided or empty, the resulting data will not be grouped. Specify 'includeRoot' or
|
|
46
|
+
* 'includeLeaves' in that case, otherwise no data will be returned.
|
|
37
47
|
*/
|
|
38
48
|
dimensions?: string[] | CubeField[];
|
|
39
49
|
|
|
40
50
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
51
|
+
* Filters to apply to leaf data, or configs to create. Note that leaf data will be filtered
|
|
52
|
+
* and then aggregated - i.e. the filters provided here will filter in/out the lowest level
|
|
53
|
+
* facts and _won't_ operate directly on any aggregates.
|
|
54
|
+
*
|
|
55
|
+
* Arrays will be combined into a single 'AND' CompoundFilter.
|
|
43
56
|
*/
|
|
44
57
|
filter?: FilterLike;
|
|
45
58
|
|
|
46
59
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
60
|
+
* True to include a synthetic root node in the return with grand totals (aggregations across
|
|
61
|
+
* all data returned by the query). Pairs well with {@link StoreConfig.loadRootAsSummary} and
|
|
62
|
+
* {@link GridConfig.showSummary} to display a docked grand total row for grids rendering
|
|
63
|
+
* Cube results.
|
|
49
64
|
*/
|
|
50
65
|
includeRoot?: boolean;
|
|
51
66
|
|
|
52
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* True to include leaf nodes (the "flat" facts originally loaded into the Cube) as the
|
|
69
|
+
* {@link ViewRowData.children} of the lowest level of aggregated `dimensions`.
|
|
70
|
+
*
|
|
71
|
+
* False (the default) to only return aggregate rows based on requested `dimensions`.
|
|
72
|
+
*
|
|
73
|
+
* Useful when you wish to e.g. load Cube results into a tree grid and allow users to expand
|
|
74
|
+
* aggregated groups all the way out to see the source data. See also `provideLeaves`, which
|
|
75
|
+
* will provide access to these nodes without exposing as `children`.
|
|
76
|
+
*/
|
|
53
77
|
includeLeaves?: boolean;
|
|
54
78
|
|
|
79
|
+
/**
|
|
80
|
+
* True to provide access to leaf nodes via the {@link ViewRowData.cubeLeaves} getter on the
|
|
81
|
+
* lowest level of aggregated `dimensions`. This will allow programmatic access to the leaves
|
|
82
|
+
* used to produce a given aggregation, without exposing them as `children` in a way that would
|
|
83
|
+
* cause them to be rendered in a tree grid.
|
|
84
|
+
*
|
|
85
|
+
* Useful when e.g. a full leaf-level drill-down is not desired, but the app still needs
|
|
86
|
+
* access to those leaves to display in a separate view or for further processing.
|
|
87
|
+
*
|
|
88
|
+
* See also the more common `includeLeaves`.
|
|
89
|
+
*/
|
|
90
|
+
provideLeaves?: boolean;
|
|
91
|
+
|
|
55
92
|
/**
|
|
56
93
|
* True (default) to recursively omit single-child parents in the hierarchy.
|
|
57
94
|
* Apps can implement further omit logic using `omitFn`.
|
|
@@ -60,14 +97,21 @@ export interface QueryConfig {
|
|
|
60
97
|
|
|
61
98
|
/**
|
|
62
99
|
* Optional function to be called for each aggregate node to determine if it should be "locked",
|
|
63
|
-
* preventing drill-down into its children.
|
|
100
|
+
* preventing drill-down into its children.
|
|
101
|
+
*
|
|
102
|
+
* Defaults to {@link Cube.lockFn}.
|
|
64
103
|
*/
|
|
65
104
|
lockFn?: LockFn;
|
|
66
105
|
|
|
67
106
|
/**
|
|
68
107
|
* Optional function to be called for each dimension during row generation to determine if the
|
|
69
108
|
* children of that dimension should be bucketed into additional dynamic dimensions.
|
|
70
|
-
*
|
|
109
|
+
*
|
|
110
|
+
* This can be used to break selected aggregations into sub-groups dynamically, without having
|
|
111
|
+
* to define another dimension in the Cube and have it apply to all aggregations. See the
|
|
112
|
+
* {@link BucketSpec} interface for additional information.
|
|
113
|
+
*
|
|
114
|
+
* Defaults to {@link Cube.bucketSpecFn}.
|
|
71
115
|
*/
|
|
72
116
|
bucketSpecFn?: BucketSpecFn;
|
|
73
117
|
|
|
@@ -85,6 +129,7 @@ export class Query {
|
|
|
85
129
|
readonly filter: Filter;
|
|
86
130
|
readonly includeRoot: boolean;
|
|
87
131
|
readonly includeLeaves: boolean;
|
|
132
|
+
readonly provideLeaves: boolean;
|
|
88
133
|
readonly omitRedundantNodes: boolean;
|
|
89
134
|
readonly cube: Cube;
|
|
90
135
|
readonly lockFn: LockFn;
|
|
@@ -100,6 +145,7 @@ export class Query {
|
|
|
100
145
|
filter = null,
|
|
101
146
|
includeRoot = false,
|
|
102
147
|
includeLeaves = false,
|
|
148
|
+
provideLeaves = false,
|
|
103
149
|
omitRedundantNodes = true,
|
|
104
150
|
lockFn = cube.lockFn,
|
|
105
151
|
bucketSpecFn = cube.bucketSpecFn,
|
|
@@ -110,6 +156,7 @@ export class Query {
|
|
|
110
156
|
this.dimensions = this.parseDimensions(dimensions);
|
|
111
157
|
this.includeRoot = includeRoot;
|
|
112
158
|
this.includeLeaves = includeLeaves;
|
|
159
|
+
this.provideLeaves = provideLeaves;
|
|
113
160
|
this.omitRedundantNodes = omitRedundantNodes;
|
|
114
161
|
this.filter = parseFilter(filter);
|
|
115
162
|
this.lockFn = lockFn;
|
|
@@ -126,6 +173,7 @@ export class Query {
|
|
|
126
173
|
filter: this.filter,
|
|
127
174
|
includeRoot: this.includeRoot,
|
|
128
175
|
includeLeaves: this.includeLeaves,
|
|
176
|
+
provideLeaves: this.provideLeaves,
|
|
129
177
|
omitRedundantNodes: this.omitRedundantNodes,
|
|
130
178
|
lockFn: this.lockFn,
|
|
131
179
|
bucketSpecFn: this.bucketSpecFn,
|
|
@@ -153,8 +201,7 @@ export class Query {
|
|
|
153
201
|
}
|
|
154
202
|
|
|
155
203
|
/**
|
|
156
|
-
* True if the provided other Query is equivalent to this instance,
|
|
157
|
-
* not considering the filter.
|
|
204
|
+
* True if the provided other Query is equivalent to this instance, not considering the filter.
|
|
158
205
|
*/
|
|
159
206
|
equalsExcludingFilter(other: Query): boolean {
|
|
160
207
|
return (
|
|
@@ -163,6 +210,7 @@ export class Query {
|
|
|
163
210
|
this.cube === other.cube &&
|
|
164
211
|
this.includeRoot === other.includeRoot &&
|
|
165
212
|
this.includeLeaves === other.includeLeaves &&
|
|
213
|
+
this.provideLeaves === other.provideLeaves &&
|
|
166
214
|
this.omitRedundantNodes === other.omitRedundantNodes &&
|
|
167
215
|
this.bucketSpecFn == other.bucketSpecFn &&
|
|
168
216
|
this.omitFn == other.omitFn &&
|
package/data/cube/View.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
StoreRecord,
|
|
18
18
|
StoreRecordId
|
|
19
19
|
} from '@xh/hoist/data';
|
|
20
|
+
import {ViewRowData} from '@xh/hoist/data/cube/ViewRowData';
|
|
20
21
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
21
22
|
import {shallowEqualArrays} from '@xh/hoist/utils/impl';
|
|
22
23
|
import {logWithDebug, throwIf} from '@xh/hoist/utils/js';
|
|
@@ -32,19 +33,24 @@ export interface ViewConfig {
|
|
|
32
33
|
query: Query;
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* Store(s) to be automatically (re)loaded with data from this view.
|
|
36
|
-
*
|
|
36
|
+
* Store(s) to be automatically (re)loaded with data from this view.
|
|
37
|
+
* Optional - read {@link View.result} directly to use without a Store.
|
|
37
38
|
*/
|
|
38
39
|
stores?: Store[] | Store;
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
* True to reactively update the View's
|
|
42
|
-
* underlying Cube
|
|
43
|
-
* a snapshot without further updates
|
|
42
|
+
* True to reactively update the View's {@link View.result} and any connected store(s) when data
|
|
43
|
+
* in the underlying Cube changes. False (default) to have this view run its query once to
|
|
44
|
+
* capture a snapshot without further (automatic) updates.
|
|
44
45
|
*/
|
|
45
46
|
connect?: boolean;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
export interface ViewResult {
|
|
50
|
+
rows: ViewRowData[];
|
|
51
|
+
leafMap: Map<StoreRecordId, LeafRow>;
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
export interface DimensionValue {
|
|
49
55
|
/** Dimension field. */
|
|
50
56
|
field: CubeField;
|
|
@@ -62,34 +68,34 @@ export class View extends HoistBase {
|
|
|
62
68
|
return true;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
/** Query defining this View. Update via
|
|
71
|
+
/** Query defining this View. Update via {@link updateQuery}. */
|
|
66
72
|
@observable.ref
|
|
67
73
|
query: Query = null;
|
|
68
74
|
|
|
69
75
|
/**
|
|
70
|
-
* Results of this view, an observable object with a `rows` property
|
|
71
|
-
*
|
|
76
|
+
* Results of this view, an observable object with a `rows` property containing an array of
|
|
77
|
+
* hierarchical {@link ViewRowData} objects.
|
|
72
78
|
*/
|
|
73
79
|
@observable.ref
|
|
74
|
-
result:
|
|
80
|
+
result: ViewResult = null;
|
|
75
81
|
|
|
76
82
|
/** Stores to which results of this view should be (re)loaded. */
|
|
77
83
|
stores: Store[] = null;
|
|
78
84
|
|
|
79
|
-
/** Cube
|
|
85
|
+
/** The source {@link Cube.info} as of the last time this View was updated. */
|
|
80
86
|
@observable.ref
|
|
81
87
|
info: PlainObject = null;
|
|
82
88
|
|
|
83
|
-
/**
|
|
89
|
+
/** Timestamp (ms) of the last time this view's data was changed. */
|
|
84
90
|
@observable
|
|
85
91
|
lastUpdated: number;
|
|
86
92
|
|
|
87
93
|
// Implementation
|
|
88
|
-
private
|
|
89
|
-
private _rowCache: Map<string, BaseRow> = null;
|
|
94
|
+
private _rowDatas: ViewRowData[] = null;
|
|
90
95
|
private _leafMap: Map<StoreRecordId, LeafRow> = null;
|
|
91
96
|
private _recordMap: Map<StoreRecordId, StoreRecord> = null;
|
|
92
97
|
_aggContext: AggregationContext = null;
|
|
98
|
+
_rowCache: Map<string, BaseRow> = null;
|
|
93
99
|
|
|
94
100
|
/** @internal - applications should use {@link Cube.createView} */
|
|
95
101
|
constructor(config: ViewConfig) {
|
|
@@ -144,8 +150,7 @@ export class View extends HoistBase {
|
|
|
144
150
|
* Change the query in some way, re-computing the data in this View to reflect the new query.
|
|
145
151
|
*
|
|
146
152
|
* @param overrides - changes to be applied to the query. May include any arguments to the query
|
|
147
|
-
* constructor except `cube`, which cannot be changed
|
|
148
|
-
* via the initial query.
|
|
153
|
+
* constructor except `cube`, which cannot be changed once set via the initial query.
|
|
149
154
|
*/
|
|
150
155
|
@action
|
|
151
156
|
updateQuery(overrides: Partial<QueryConfig>) {
|
|
@@ -164,9 +169,7 @@ export class View extends HoistBase {
|
|
|
164
169
|
this.fullUpdate();
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
/**
|
|
168
|
-
* Gathers all unique values for each dimension field in the query
|
|
169
|
-
*/
|
|
172
|
+
/** Gather all unique values for each dimension field in the query. */
|
|
170
173
|
getDimensionValues(): DimensionValue[] {
|
|
171
174
|
const {_leafMap} = this,
|
|
172
175
|
fields = this.query.fields.filter(it => it.isDimension);
|
|
@@ -229,6 +232,7 @@ export class View extends HoistBase {
|
|
|
229
232
|
this.updateResults();
|
|
230
233
|
}
|
|
231
234
|
|
|
235
|
+
@logWithDebug
|
|
232
236
|
private dataOnlyUpdate(updates: StoreRecord[]) {
|
|
233
237
|
const {_leafMap, _recordMap, stores} = this,
|
|
234
238
|
updatedRowDatas = new Set<PlainObject>();
|
|
@@ -252,17 +256,17 @@ export class View extends HoistBase {
|
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
private loadStores() {
|
|
255
|
-
const {_leafMap,
|
|
256
|
-
if (!_leafMap || !
|
|
259
|
+
const {_leafMap, _rowDatas} = this;
|
|
260
|
+
if (!_leafMap || !_rowDatas) return;
|
|
257
261
|
|
|
258
262
|
// Skip degenerate root in stores/grids, but preserve in object api.
|
|
259
|
-
const storeRows = _leafMap.size !== 0 ?
|
|
263
|
+
const storeRows = _leafMap.size !== 0 ? _rowDatas : [];
|
|
260
264
|
this.stores.forEach(s => s.loadData(storeRows));
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
private updateResults() {
|
|
264
|
-
const {_leafMap,
|
|
265
|
-
this.result = {rows:
|
|
268
|
+
const {_leafMap, _rowDatas} = this;
|
|
269
|
+
this.result = {rows: _rowDatas, leafMap: _leafMap};
|
|
266
270
|
this.info = this.cube.info;
|
|
267
271
|
this.lastUpdated = Date.now();
|
|
268
272
|
}
|
|
@@ -274,7 +278,7 @@ export class View extends HoistBase {
|
|
|
274
278
|
rootId = 'root';
|
|
275
279
|
|
|
276
280
|
const records = this._aggContext.filteredRecords;
|
|
277
|
-
const leafMap = new Map();
|
|
281
|
+
const leafMap: Map<StoreRecordId, LeafRow> = new Map();
|
|
278
282
|
let newRows = this.groupAndInsertRecords(records, dimensions, rootId, {}, leafMap);
|
|
279
283
|
newRows = this.bucketRows(newRows, rootId, {});
|
|
280
284
|
|
|
@@ -292,10 +296,10 @@ export class View extends HoistBase {
|
|
|
292
296
|
|
|
293
297
|
this._leafMap = leafMap;
|
|
294
298
|
|
|
295
|
-
// This is the magic.
|
|
299
|
+
// This is the magic. We only actually reveal to API the network of *data* nodes.
|
|
296
300
|
// This hides all the meta information, as well as unwanted leaves and skipped rows.
|
|
297
301
|
// Underlying network still there and updates will flow up through it via the leaves.
|
|
298
|
-
this.
|
|
302
|
+
this._rowDatas = newRows.flatMap(it => it.getVisibleDatas());
|
|
299
303
|
}
|
|
300
304
|
|
|
301
305
|
private groupAndInsertRecords(
|
|
@@ -0,0 +1,66 @@
|
|
|
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 {PlainObject, Some} from '@xh/hoist/core';
|
|
8
|
+
import {flatMap} from 'lodash';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Grouped node data, as returned by {@link Cube.executeQuery} or exposed via {@link View.result}.
|
|
12
|
+
* Designed for direct consumption by hierarchical stores and their associated tree grids.
|
|
13
|
+
*/
|
|
14
|
+
export class ViewRowData implements PlainObject {
|
|
15
|
+
constructor(id: string) {
|
|
16
|
+
this.id = id;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Unique id. */
|
|
20
|
+
id: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Label of the row. The dimension value or, for leaf rows. the underlying cubeId.
|
|
24
|
+
* Suitable for display, although apps will typically wish to customize leaf row rendering.
|
|
25
|
+
*/
|
|
26
|
+
cubeLabel: string;
|
|
27
|
+
|
|
28
|
+
/** Dimension on which this row was computed, or null for leaf rows. */
|
|
29
|
+
cubeDimension: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Buckets this row appears in
|
|
33
|
+
*/
|
|
34
|
+
cubeBuckets: Record<string, any>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Visible children of this row.
|
|
38
|
+
*
|
|
39
|
+
* Note that this may not be the same as the simple aggregation children of this row. In
|
|
40
|
+
* particular, this property is subject to the semantics of row locking, redundant row omission,
|
|
41
|
+
* and bucketing as defined by the Query.
|
|
42
|
+
*/
|
|
43
|
+
children: ViewRowData[];
|
|
44
|
+
|
|
45
|
+
/** True for leaf rows loaded into the cube (i.e. not a grouped aggregation). */
|
|
46
|
+
get isCubeLeaf(): boolean {
|
|
47
|
+
return this.cubeDimension == null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* All visible (i.e. non-locked) cube leaves associated with this row.
|
|
52
|
+
*
|
|
53
|
+
* For this to be populated, either {@link Query.includeLeaves} or {@link Query.provideLeaves}
|
|
54
|
+
* must have been set on the underlying Query.
|
|
55
|
+
*/
|
|
56
|
+
get cubeLeaves(): Some<ViewRowData> {
|
|
57
|
+
if (this.isCubeLeaf) return this;
|
|
58
|
+
return this._cubeLeafChildren ?? flatMap(this.children, 'cubeLeaves');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//------------------
|
|
62
|
+
// Implementation
|
|
63
|
+
//-----------------
|
|
64
|
+
/** @internal */
|
|
65
|
+
_cubeLeafChildren: ViewRowData[];
|
|
66
|
+
}
|
|
@@ -11,14 +11,17 @@ import {View} from '../View';
|
|
|
11
11
|
import {BaseRow} from './BaseRow';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Row within a dataset produced by a Cube / View representing aggregated data on a dimension.
|
|
15
|
+
*
|
|
16
|
+
* This is an internal data structure - {@link ViewRowData} is the public row-level data API.
|
|
15
17
|
*/
|
|
16
18
|
export class AggregateRow extends BaseRow {
|
|
17
19
|
override get isAggregate() {
|
|
18
20
|
return true;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
/** The dimension for which this row is aggregating data. Null for a top-level summary row. */
|
|
24
|
+
readonly dim: CubeField = null;
|
|
22
25
|
readonly dimName: string = null;
|
|
23
26
|
|
|
24
27
|
constructor(
|