@xh/hoist 78.0.0-SNAPSHOT.1763606305479 → 78.0.0-SNAPSHOT.1763737348746
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 +19 -4
- package/build/types/cmp/grid/GridModel.d.ts +3 -2
- package/build/types/data/cube/BucketSpec.d.ts +28 -8
- package/build/types/data/cube/Cube.d.ts +3 -3
- package/build/types/data/cube/Query.d.ts +1 -3
- package/build/types/data/cube/View.d.ts +4 -3
- package/build/types/data/cube/row/BucketRow.d.ts +1 -1
- package/cmp/grid/GridModel.ts +21 -15
- package/cmp/grid/impl/InitPersist.ts +1 -3
- package/data/cube/BucketSpec.ts +33 -15
- package/data/cube/Cube.ts +3 -3
- package/data/cube/Query.ts +4 -6
- package/data/cube/View.ts +26 -13
- package/data/cube/row/BucketRow.ts +1 -1
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,20 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
## 78.0.0-SNAPSHOT - unreleased
|
|
4
4
|
|
|
5
|
+
### 💥 Breaking Changes
|
|
6
|
+
|
|
7
|
+
* `GridModel.setColumnState` no longer patches existing column state, but instead replaces it
|
|
8
|
+
wholesale. Applications that were relying on the prior patching behavior will need to
|
|
9
|
+
call `GridModel.applyColumnStateChanges` instead.
|
|
10
|
+
* `GridModel.cleanColumnState` is now private (not expected to impact applications).
|
|
11
|
+
|
|
5
12
|
### 🎁 New Features
|
|
6
13
|
|
|
7
|
-
*
|
|
14
|
+
* Added new `FieldFilter` operators `not begins` and `not ends`.
|
|
15
|
+
* Added new optional `BucketSpec.dependentFields` config to the Cube API, allowing apps to ensure
|
|
16
|
+
proper re-bucketing of rows during data-only updates where those updates could affect bucketing
|
|
17
|
+
determinations made by the spec.
|
|
8
18
|
|
|
9
19
|
### 🐞 Bug Fixes
|
|
10
20
|
|
|
11
21
|
* Fixed `GridModel` not appending children to the parents correctly when loaded data uses a
|
|
12
22
|
numerical ID.
|
|
23
|
+
* Fixed issue where newly added columns appearing in the Displayed Columns section of the column
|
|
24
|
+
chooser after loading grid state that was persisted before the columns were added to the grid.
|
|
25
|
+
* Removed a minor Cube `Query` annoyance - `dimensions` are now automatically added to the `fields`
|
|
26
|
+
list and do not need to be manually repeated there.
|
|
13
27
|
|
|
14
28
|
### ⚙️ Technical
|
|
15
29
|
|
|
16
|
-
* `
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
* Updated the Cube API's `BucketSpecFn` to return either a concrete `BucketSpec` class instance (as
|
|
31
|
+
before) or a plain object conforming to the new `BucketSpecConfig` interface.
|
|
32
|
+
* Enhanced `FetchService` to recognize variants on the `application/json` content-type when
|
|
33
|
+
processing failed responses and decoding exceptions - e.g. `application/problem+json`.
|
|
19
34
|
|
|
20
35
|
## 77.1.1 - 2025-11-12
|
|
21
36
|
|
|
@@ -464,7 +464,7 @@ export declare class GridModel extends HoistModel {
|
|
|
464
464
|
/** Clear the underlying store, removing all rows. */
|
|
465
465
|
clear(): void;
|
|
466
466
|
setColumns(colConfigs: Array<ColumnSpec | ColumnGroupSpec>): void;
|
|
467
|
-
setColumnState(colState:
|
|
467
|
+
setColumnState(colState: ColumnState[]): void;
|
|
468
468
|
showColChooser(): void;
|
|
469
469
|
noteAgColumnStateChanged(agColState: AgColumnState[]): void;
|
|
470
470
|
setExpandState(expandState: any): void;
|
|
@@ -583,7 +583,7 @@ export declare class GridModel extends HoistModel {
|
|
|
583
583
|
private validateStoreConfig;
|
|
584
584
|
private validateColConfigs;
|
|
585
585
|
private validateColumns;
|
|
586
|
-
cleanColumnState
|
|
586
|
+
private cleanColumnState;
|
|
587
587
|
private enhanceColConfigsFromStore;
|
|
588
588
|
private enhanceStoreConfigFromColumns;
|
|
589
589
|
private leafColsByFieldName;
|
|
@@ -597,4 +597,5 @@ export declare class GridModel extends HoistModel {
|
|
|
597
597
|
private readonly RIGHT_BORDER_CLASS;
|
|
598
598
|
private enhanceConfigWithGroupBorders;
|
|
599
599
|
private createGroupBorderFn;
|
|
600
|
+
private getDefaultStateForColumn;
|
|
600
601
|
}
|
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
import { BaseRow } from './row/BaseRow';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Spec to define a bucketing level within the hierarchy of data returned by a Query, as identified
|
|
4
|
+
* by a set of rows passed to a {@link BucketSpecFn} configured on that Query (or defaulted from
|
|
5
|
+
* the Cube). If this object is returned for a candidate set of rows, each row is evaluated by the
|
|
6
|
+
* spec's `bucketFn` to determine if it should yield a value for the bucket, causing the row to be
|
|
7
|
+
* nested underneath a new {@link BucketRow} created to hold all rows with that value.
|
|
4
8
|
*/
|
|
9
|
+
export interface BucketSpecConfig {
|
|
10
|
+
/** Name for the bucketing level configured by this spec - equivalent to a dimension name. */
|
|
11
|
+
name: string;
|
|
12
|
+
/**
|
|
13
|
+
* Function returning the bucketed value (if any) into which the given row should be placed -
|
|
14
|
+
* equivalent to a dimension value. Return null/undefined to exclude the row from bucketing.
|
|
15
|
+
*/
|
|
16
|
+
bucketFn: (row: BaseRow) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Function returning bucket row label from the bucket value string returned by bucketFn.
|
|
19
|
+
* Defaults to using the value directly.
|
|
20
|
+
*/
|
|
21
|
+
labelFn?: (bucket: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Fields on which the `bucketFn` depends, to ensure rows are re-bucketed if dependent field
|
|
24
|
+
* values change. If not provided or does not cover all fields potentially accessed by
|
|
25
|
+
* `bucketFn`, an incremental "data only" update that should have changed a row's bucket can
|
|
26
|
+
* fail to do so.
|
|
27
|
+
*/
|
|
28
|
+
dependentFields?: string[];
|
|
29
|
+
}
|
|
5
30
|
export declare class BucketSpec {
|
|
6
31
|
name: string;
|
|
7
32
|
bucketFn: (row: BaseRow) => string;
|
|
8
33
|
labelFn: (bucket: string) => string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @param bucketFn - function to determine which (if any) bucket the given row should
|
|
12
|
-
* be placed into
|
|
13
|
-
* @param labelFn - function to generate the bucket row label from name returned by bucketFn
|
|
14
|
-
**/
|
|
15
|
-
constructor(name: string, bucketFn: (row: BaseRow) => string, labelFn?: (bucket: string) => string);
|
|
34
|
+
dependentFields: string[];
|
|
35
|
+
constructor(config: BucketSpecConfig);
|
|
16
36
|
}
|
|
@@ -8,7 +8,7 @@ import { StoreRecord } from '../StoreRecord';
|
|
|
8
8
|
import { AggregateRow } from './row/AggregateRow';
|
|
9
9
|
import { BucketRow } from './row/BucketRow';
|
|
10
10
|
import { BaseRow } from './row/BaseRow';
|
|
11
|
-
import { BucketSpec } from './BucketSpec';
|
|
11
|
+
import { BucketSpec, BucketSpecConfig } from './BucketSpec';
|
|
12
12
|
export interface CubeConfig {
|
|
13
13
|
fields: CubeField[] | CubeFieldSpec[];
|
|
14
14
|
/** Default configs applied to all `CubeField`s constructed internally by this Cube. */
|
|
@@ -60,9 +60,9 @@ export type OmitFn = (row: AggregateRow | BucketRow) => boolean;
|
|
|
60
60
|
* aggregations and create an unwanted "Open" grouping.
|
|
61
61
|
*
|
|
62
62
|
* @param rows - the rows being checked for bucketing
|
|
63
|
-
* @returns
|
|
63
|
+
* @returns {@link BucketSpecConfig} for dynamic sub-aggregations, or null to perform no bucketing.
|
|
64
64
|
*/
|
|
65
|
-
export type BucketSpecFn = (rows: BaseRow[]) => BucketSpec;
|
|
65
|
+
export type BucketSpecFn = (rows: BaseRow[]) => BucketSpecConfig | BucketSpec;
|
|
66
66
|
/**
|
|
67
67
|
* A data store that supports grouping, aggregating, and filtering data on multiple dimensions.
|
|
68
68
|
*
|
|
@@ -19,8 +19,6 @@ export interface QueryConfig {
|
|
|
19
19
|
* Fields or field names on which data should be grouped and aggregated. These are the ordered
|
|
20
20
|
* grouping levels in the resulting hierarchy - e.g. ['Country', 'State', 'City'].
|
|
21
21
|
*
|
|
22
|
-
* Any fields provided here must also be included in the `fields` array, if specified.
|
|
23
|
-
*
|
|
24
22
|
* If not provided or empty, the resulting data will not be grouped. Specify 'includeRoot' or
|
|
25
23
|
* 'includeLeaves' in that case, otherwise no data will be returned.
|
|
26
24
|
*/
|
|
@@ -81,7 +79,7 @@ export interface QueryConfig {
|
|
|
81
79
|
*
|
|
82
80
|
* This can be used to break selected aggregations into sub-groups dynamically, without having
|
|
83
81
|
* to define another dimension in the Cube and have it apply to all aggregations. See the
|
|
84
|
-
* {@link BucketSpecFn} type and {@link
|
|
82
|
+
* {@link BucketSpecFn} type and {@link BucketSpecConfig} interface for additional information.
|
|
85
83
|
*
|
|
86
84
|
* Defaults to {@link Cube.bucketSpecFn}.
|
|
87
85
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HoistBase, PlainObject, Some } from '@xh/hoist/core';
|
|
2
|
-
import { Cube, CubeField, Filter, FilterLike, Query, QueryConfig, Store, StoreRecordId } from '@xh/hoist/data';
|
|
2
|
+
import { Cube, CubeField, Filter, FilterLike, Query, QueryConfig, Store, StoreChangeLog, StoreRecordId } from '@xh/hoist/data';
|
|
3
3
|
import { ViewRowData } from '@xh/hoist/data/cube/ViewRowData';
|
|
4
4
|
import { AggregationContext } from './aggregate/AggregationContext';
|
|
5
5
|
import { BaseRow } from './row/BaseRow';
|
|
@@ -51,6 +51,7 @@ export declare class View extends HoistBase {
|
|
|
51
51
|
private _rowDatas;
|
|
52
52
|
private _leafMap;
|
|
53
53
|
private _recordMap;
|
|
54
|
+
private _bucketDependentFields;
|
|
54
55
|
_aggContext: AggregationContext;
|
|
55
56
|
_rowCache: Map<string, BaseRow>;
|
|
56
57
|
/** @internal - applications should use {@link Cube.createView} */
|
|
@@ -79,7 +80,7 @@ export declare class View extends HoistBase {
|
|
|
79
80
|
/** Update the filter on the current Query.*/
|
|
80
81
|
setFilter(filter: FilterLike): void;
|
|
81
82
|
noteCubeLoaded(): void;
|
|
82
|
-
noteCubeUpdated(changeLog:
|
|
83
|
+
noteCubeUpdated(changeLog: StoreChangeLog): void;
|
|
83
84
|
private fullUpdate;
|
|
84
85
|
private dataOnlyUpdate;
|
|
85
86
|
private loadStores;
|
|
@@ -88,7 +89,7 @@ export declare class View extends HoistBase {
|
|
|
88
89
|
private groupAndInsertRecords;
|
|
89
90
|
private bucketRows;
|
|
90
91
|
private getSimpleUpdates;
|
|
91
|
-
private
|
|
92
|
+
private hasDimOrBucketUpdates;
|
|
92
93
|
private cachedRow;
|
|
93
94
|
private filterRecords;
|
|
94
95
|
private createAggregationContext;
|
|
@@ -5,7 +5,7 @@ import { View } from '../View';
|
|
|
5
5
|
/**
|
|
6
6
|
* Row within a dataset produced by a Cube / View representing aggregated data on a dimension that
|
|
7
7
|
* has been further grouped into a dynamic child "bucket" - a subset of the dimension-level
|
|
8
|
-
* {@link AggregateRow} produced as per a specified {@link
|
|
8
|
+
* {@link AggregateRow} produced as per a specified {@link BucketSpecFn}.
|
|
9
9
|
*
|
|
10
10
|
* This is an internal data structure - {@link ViewRowData} is the public row-level data API.
|
|
11
11
|
*/
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
pull,
|
|
99
99
|
take
|
|
100
100
|
} from 'lodash';
|
|
101
|
+
import {computed} from 'mobx';
|
|
101
102
|
import {createRef, ReactNode, RefObject} from 'react';
|
|
102
103
|
import {GridAutosizeOptions} from './GridAutosizeOptions';
|
|
103
104
|
import {GridContextMenuSpec} from './GridContextMenu';
|
|
@@ -442,6 +443,7 @@ export class GridModel extends HoistModel {
|
|
|
442
443
|
@observable.ref groupBy: string[] = null;
|
|
443
444
|
@observable expandLevel: number = 0;
|
|
444
445
|
|
|
446
|
+
@computed.struct
|
|
445
447
|
get persistableColumnState(): ColumnState[] {
|
|
446
448
|
return this.cleanColumnState(this.columnState);
|
|
447
449
|
}
|
|
@@ -1147,19 +1149,11 @@ export class GridModel extends HoistModel {
|
|
|
1147
1149
|
this.validateColumns(columns);
|
|
1148
1150
|
|
|
1149
1151
|
this.columns = columns;
|
|
1150
|
-
this.columnState = this.getLeafColumns().map(it => (
|
|
1151
|
-
...pick(it, ['colId', 'width', 'hidden', 'pinned']),
|
|
1152
|
-
// If not in managed auto-size mode, treat in-code column widths as manuallySized so
|
|
1153
|
-
// widths are not omitted from persistableColumnState. This is important because
|
|
1154
|
-
// PersistanceProvider.getPersistableState() expects a complete snapshot of initial
|
|
1155
|
-
// state in order to detect changes and restore initial state correctly.
|
|
1156
|
-
// See https://github.com/xh/hoist-react/issues/4102.
|
|
1157
|
-
manuallySized: !!(it.width && this.autosizeOptions.mode !== 'managed')
|
|
1158
|
-
}));
|
|
1152
|
+
this.columnState = this.getLeafColumns().map(it => this.getDefaultStateForColumn(it));
|
|
1159
1153
|
}
|
|
1160
1154
|
|
|
1161
|
-
setColumnState(colState:
|
|
1162
|
-
this.
|
|
1155
|
+
setColumnState(colState: ColumnState[]) {
|
|
1156
|
+
this.columnState = this.cleanColumnState(colState);
|
|
1163
1157
|
}
|
|
1164
1158
|
|
|
1165
1159
|
showColChooser() {
|
|
@@ -1706,7 +1700,7 @@ export class GridModel extends HoistModel {
|
|
|
1706
1700
|
);
|
|
1707
1701
|
}
|
|
1708
1702
|
|
|
1709
|
-
cleanColumnState(columnState) {
|
|
1703
|
+
private cleanColumnState(columnState) {
|
|
1710
1704
|
const gridCols = this.getLeafColumns();
|
|
1711
1705
|
|
|
1712
1706
|
// REMOVE any state columns that are no longer found in the grid. These were likely saved
|
|
@@ -1715,9 +1709,9 @@ export class GridModel extends HoistModel {
|
|
|
1715
1709
|
|
|
1716
1710
|
// ADD any grid columns that are not found in state. These are newly added to the code.
|
|
1717
1711
|
// Insert these columns in position based on the index at which they are defined.
|
|
1718
|
-
gridCols.forEach((
|
|
1719
|
-
if (!find(ret, {colId})) {
|
|
1720
|
-
ret.splice(idx, 0,
|
|
1712
|
+
gridCols.forEach((col, idx) => {
|
|
1713
|
+
if (!find(ret, {colId: col.colId})) {
|
|
1714
|
+
ret.splice(idx, 0, this.getDefaultStateForColumn(col));
|
|
1721
1715
|
}
|
|
1722
1716
|
});
|
|
1723
1717
|
|
|
@@ -1958,4 +1952,16 @@ export class GridModel extends HoistModel {
|
|
|
1958
1952
|
}
|
|
1959
1953
|
};
|
|
1960
1954
|
}
|
|
1955
|
+
|
|
1956
|
+
private getDefaultStateForColumn(column: Column): ColumnState {
|
|
1957
|
+
return {
|
|
1958
|
+
...pick(column, ['colId', 'width', 'hidden', 'pinned']),
|
|
1959
|
+
// If not in managed auto-size mode, treat in-code column widths as manuallySized so
|
|
1960
|
+
// widths are not omitted from persistableColumnState. This is important because
|
|
1961
|
+
// PersistanceProvider.getPersistableState() expects a complete snapshot of initial
|
|
1962
|
+
// state in order to detect changes and restore initial state correctly.
|
|
1963
|
+
// See https://github.com/xh/hoist-react/issues/4102.
|
|
1964
|
+
manuallySized: !!(column.width && this.autosizeOptions.mode !== 'managed')
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1961
1967
|
}
|
|
@@ -39,9 +39,7 @@ export function initPersist(
|
|
|
39
39
|
new PersistableColumnState(gridModel.persistableColumnState),
|
|
40
40
|
setPersistableState: ({value}) =>
|
|
41
41
|
runInAction(() => {
|
|
42
|
-
|
|
43
|
-
// provided state with the current state, which is not what we want here.
|
|
44
|
-
gridModel.columnState = gridModel.cleanColumnState(value);
|
|
42
|
+
gridModel.setColumnState(value);
|
|
45
43
|
if (gridModel.autosizeOptions.mode === 'managed') {
|
|
46
44
|
const columns = gridModel.columnState
|
|
47
45
|
.filter(it => !it.manuallySized)
|
package/data/cube/BucketSpec.ts
CHANGED
|
@@ -8,26 +8,44 @@
|
|
|
8
8
|
import {BaseRow} from './row/BaseRow';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Spec to define a bucketing level within the hierarchy of data returned by a Query, as identified
|
|
12
|
+
* by a set of rows passed to a {@link BucketSpecFn} configured on that Query (or defaulted from
|
|
13
|
+
* the Cube). If this object is returned for a candidate set of rows, each row is evaluated by the
|
|
14
|
+
* spec's `bucketFn` to determine if it should yield a value for the bucket, causing the row to be
|
|
15
|
+
* nested underneath a new {@link BucketRow} created to hold all rows with that value.
|
|
12
16
|
*/
|
|
17
|
+
export interface BucketSpecConfig {
|
|
18
|
+
/** Name for the bucketing level configured by this spec - equivalent to a dimension name. */
|
|
19
|
+
name: string;
|
|
20
|
+
/**
|
|
21
|
+
* Function returning the bucketed value (if any) into which the given row should be placed -
|
|
22
|
+
* equivalent to a dimension value. Return null/undefined to exclude the row from bucketing.
|
|
23
|
+
*/
|
|
24
|
+
bucketFn: (row: BaseRow) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Function returning bucket row label from the bucket value string returned by bucketFn.
|
|
27
|
+
* Defaults to using the value directly.
|
|
28
|
+
*/
|
|
29
|
+
labelFn?: (bucket: string) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Fields on which the `bucketFn` depends, to ensure rows are re-bucketed if dependent field
|
|
32
|
+
* values change. If not provided or does not cover all fields potentially accessed by
|
|
33
|
+
* `bucketFn`, an incremental "data only" update that should have changed a row's bucket can
|
|
34
|
+
* fail to do so.
|
|
35
|
+
*/
|
|
36
|
+
dependentFields?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
export class BucketSpec {
|
|
14
40
|
name: string;
|
|
15
41
|
bucketFn: (row: BaseRow) => string;
|
|
16
42
|
labelFn: (bucket: string) => string;
|
|
43
|
+
dependentFields: string[];
|
|
17
44
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
**/
|
|
24
|
-
constructor(
|
|
25
|
-
name: string,
|
|
26
|
-
bucketFn: (row: BaseRow) => string,
|
|
27
|
-
labelFn?: (bucket: string) => string
|
|
28
|
-
) {
|
|
29
|
-
this.name = name;
|
|
30
|
-
this.bucketFn = bucketFn;
|
|
31
|
-
this.labelFn = labelFn ?? (b => b);
|
|
45
|
+
constructor(config: BucketSpecConfig) {
|
|
46
|
+
this.name = config.name;
|
|
47
|
+
this.bucketFn = config.bucketFn;
|
|
48
|
+
this.labelFn = config.labelFn ?? (b => b);
|
|
49
|
+
this.dependentFields = config.dependentFields ?? [];
|
|
32
50
|
}
|
|
33
51
|
}
|
package/data/cube/Cube.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {StoreRecord} from '../StoreRecord';
|
|
|
17
17
|
import {AggregateRow} from './row/AggregateRow';
|
|
18
18
|
import {BucketRow} from './row/BucketRow';
|
|
19
19
|
import {BaseRow} from './row/BaseRow';
|
|
20
|
-
import {BucketSpec} from './BucketSpec';
|
|
20
|
+
import {BucketSpec, BucketSpecConfig} from './BucketSpec';
|
|
21
21
|
import {defaultsDeep, isEmpty} from 'lodash';
|
|
22
22
|
|
|
23
23
|
export interface CubeConfig {
|
|
@@ -82,9 +82,9 @@ export type OmitFn = (row: AggregateRow | BucketRow) => boolean;
|
|
|
82
82
|
* aggregations and create an unwanted "Open" grouping.
|
|
83
83
|
*
|
|
84
84
|
* @param rows - the rows being checked for bucketing
|
|
85
|
-
* @returns
|
|
85
|
+
* @returns {@link BucketSpecConfig} for dynamic sub-aggregations, or null to perform no bucketing.
|
|
86
86
|
*/
|
|
87
|
-
export type BucketSpecFn = (rows: BaseRow[]) => BucketSpec;
|
|
87
|
+
export type BucketSpecFn = (rows: BaseRow[]) => BucketSpecConfig | BucketSpec;
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* A data store that supports grouping, aggregating, and filtering data on multiple dimensions.
|
package/data/cube/Query.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
StoreRecord
|
|
17
17
|
} from '@xh/hoist/data';
|
|
18
18
|
import {throwIf} from '@xh/hoist/utils/js';
|
|
19
|
-
import {find, isEqual} from 'lodash';
|
|
19
|
+
import {find, isEqual, uniq} from 'lodash';
|
|
20
20
|
import {Cube} from './Cube';
|
|
21
21
|
import {CubeField} from './CubeField';
|
|
22
22
|
|
|
@@ -40,8 +40,6 @@ export interface QueryConfig {
|
|
|
40
40
|
* Fields or field names on which data should be grouped and aggregated. These are the ordered
|
|
41
41
|
* grouping levels in the resulting hierarchy - e.g. ['Country', 'State', 'City'].
|
|
42
42
|
*
|
|
43
|
-
* Any fields provided here must also be included in the `fields` array, if specified.
|
|
44
|
-
*
|
|
45
43
|
* If not provided or empty, the resulting data will not be grouped. Specify 'includeRoot' or
|
|
46
44
|
* 'includeLeaves' in that case, otherwise no data will be returned.
|
|
47
45
|
*/
|
|
@@ -109,7 +107,7 @@ export interface QueryConfig {
|
|
|
109
107
|
*
|
|
110
108
|
* This can be used to break selected aggregations into sub-groups dynamically, without having
|
|
111
109
|
* to define another dimension in the Cube and have it apply to all aggregations. See the
|
|
112
|
-
* {@link BucketSpecFn} type and {@link
|
|
110
|
+
* {@link BucketSpecFn} type and {@link BucketSpecConfig} interface for additional information.
|
|
113
111
|
*
|
|
114
112
|
* Defaults to {@link Cube.bucketSpecFn}.
|
|
115
113
|
*/
|
|
@@ -153,8 +151,8 @@ export class Query {
|
|
|
153
151
|
omitFn = cube.omitFn
|
|
154
152
|
}: QueryConfig) {
|
|
155
153
|
this.cube = cube;
|
|
156
|
-
this.fields = this.parseFields(fields);
|
|
157
154
|
this.dimensions = this.parseDimensions(dimensions);
|
|
155
|
+
this.fields = uniq([...this.parseFields(fields), ...(this.dimensions ?? [])]);
|
|
158
156
|
this.includeRoot = includeRoot;
|
|
159
157
|
this.includeLeaves = includeLeaves;
|
|
160
158
|
this.provideLeaves = provideLeaves;
|
|
@@ -234,7 +232,7 @@ export class Query {
|
|
|
234
232
|
private parseDimensions(raw: CubeField[] | string[]): CubeField[] {
|
|
235
233
|
if (!raw) return null;
|
|
236
234
|
if (raw[0] instanceof CubeField) return raw as CubeField[];
|
|
237
|
-
const {fields} = this;
|
|
235
|
+
const {fields} = this.cube;
|
|
238
236
|
return raw.map(name => {
|
|
239
237
|
const field = find(fields, {name});
|
|
240
238
|
throwIf(
|
package/data/cube/View.ts
CHANGED
|
@@ -14,14 +14,16 @@ import {
|
|
|
14
14
|
Query,
|
|
15
15
|
QueryConfig,
|
|
16
16
|
Store,
|
|
17
|
+
StoreChangeLog,
|
|
17
18
|
StoreRecord,
|
|
18
19
|
StoreRecordId
|
|
19
20
|
} from '@xh/hoist/data';
|
|
21
|
+
import {BucketSpec} from '@xh/hoist/data/cube/BucketSpec';
|
|
20
22
|
import {ViewRowData} from '@xh/hoist/data/cube/ViewRowData';
|
|
21
23
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
22
24
|
import {shallowEqualArrays} from '@xh/hoist/utils/impl';
|
|
23
25
|
import {logWithDebug, throwIf} from '@xh/hoist/utils/js';
|
|
24
|
-
import {castArray, find, forEach, groupBy, isEmpty, isNil, map} from 'lodash';
|
|
26
|
+
import {castArray, find, forEach, groupBy, isEmpty, isNil, map, uniq} from 'lodash';
|
|
25
27
|
import {AggregationContext} from './aggregate/AggregationContext';
|
|
26
28
|
import {AggregateRow} from './row/AggregateRow';
|
|
27
29
|
import {BaseRow} from './row/BaseRow';
|
|
@@ -94,6 +96,7 @@ export class View extends HoistBase {
|
|
|
94
96
|
private _rowDatas: ViewRowData[] = null;
|
|
95
97
|
private _leafMap: Map<StoreRecordId, LeafRow> = null;
|
|
96
98
|
private _recordMap: Map<StoreRecordId, StoreRecord> = null;
|
|
99
|
+
private _bucketDependentFields = new Set<string>();
|
|
97
100
|
_aggContext: AggregationContext = null;
|
|
98
101
|
_rowCache: Map<string, BaseRow> = null;
|
|
99
102
|
|
|
@@ -207,7 +210,7 @@ export class View extends HoistBase {
|
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
@action
|
|
210
|
-
noteCubeUpdated(changeLog:
|
|
213
|
+
noteCubeUpdated(changeLog: StoreChangeLog) {
|
|
211
214
|
const simpleUpdates = this.getSimpleUpdates(changeLog);
|
|
212
215
|
|
|
213
216
|
if (!simpleUpdates) {
|
|
@@ -277,6 +280,8 @@ export class View extends HoistBase {
|
|
|
277
280
|
{dimensions, includeRoot} = query,
|
|
278
281
|
rootId = 'root';
|
|
279
282
|
|
|
283
|
+
this._bucketDependentFields.clear();
|
|
284
|
+
|
|
280
285
|
const records = this._aggContext.filteredRecords;
|
|
281
286
|
const leafMap: Map<StoreRecordId, LeafRow> = new Map();
|
|
282
287
|
let newRows = this.groupAndInsertRecords(records, dimensions, rootId, {}, leafMap);
|
|
@@ -360,13 +365,19 @@ export class View extends HoistBase {
|
|
|
360
365
|
if (!query.bucketSpecFn) return rows;
|
|
361
366
|
if (!query.includeLeaves && rows[0]?.isLeaf) return rows;
|
|
362
367
|
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
368
|
+
const bucketSpecOrConf = query.bucketSpecFn(rows);
|
|
369
|
+
if (!bucketSpecOrConf) return rows;
|
|
365
370
|
|
|
366
|
-
const
|
|
371
|
+
const bucketSpec =
|
|
372
|
+
bucketSpecOrConf instanceof BucketSpec
|
|
373
|
+
? bucketSpecOrConf
|
|
374
|
+
: new BucketSpec(bucketSpecOrConf);
|
|
375
|
+
const {name: bucketName, bucketFn, dependentFields} = bucketSpec,
|
|
367
376
|
buckets: Record<string, BaseRow[]> = {},
|
|
368
377
|
ret: BaseRow[] = [];
|
|
369
378
|
|
|
379
|
+
dependentFields.forEach(it => this._bucketDependentFields.add(it));
|
|
380
|
+
|
|
370
381
|
// Determine which bucket to put this row into (if any)
|
|
371
382
|
rows.forEach(row => {
|
|
372
383
|
const bucketVal = bucketFn(row);
|
|
@@ -394,14 +405,14 @@ export class View extends HoistBase {
|
|
|
394
405
|
|
|
395
406
|
// return a list of simple data updates we can apply to leaves.
|
|
396
407
|
// false if leaf population changing, or aggregations are complex
|
|
397
|
-
private getSimpleUpdates(t): StoreRecord[] | false {
|
|
408
|
+
private getSimpleUpdates(t: StoreChangeLog): StoreRecord[] | false {
|
|
398
409
|
if (!t) return [];
|
|
399
410
|
if (!this.aggregatorsAreSimple) return false;
|
|
400
411
|
const {_leafMap, query} = this;
|
|
401
412
|
|
|
402
413
|
// 1) Simple case: no filter
|
|
403
414
|
if (!query.filter) {
|
|
404
|
-
return isEmpty(t.add) && isEmpty(t.remove) && !this.
|
|
415
|
+
return isEmpty(t.add) && isEmpty(t.remove) && !this.hasDimOrBucketUpdates(t.update)
|
|
405
416
|
? t.update
|
|
406
417
|
: false;
|
|
407
418
|
}
|
|
@@ -425,19 +436,21 @@ export class View extends HoistBase {
|
|
|
425
436
|
|
|
426
437
|
// 2c) Examine the final set of updates for any changes to dimension field values which would
|
|
427
438
|
// require rebuilding the row hierarchy
|
|
428
|
-
if (this.
|
|
439
|
+
if (this.hasDimOrBucketUpdates(ret)) return false;
|
|
429
440
|
|
|
430
441
|
return ret;
|
|
431
442
|
}
|
|
432
443
|
|
|
433
|
-
private
|
|
434
|
-
const {dimensions} = this.query
|
|
435
|
-
|
|
444
|
+
private hasDimOrBucketUpdates(update: StoreRecord[]): boolean {
|
|
445
|
+
const {dimensions} = this.query,
|
|
446
|
+
bucketDependentFields = Array.from(this._bucketDependentFields);
|
|
447
|
+
|
|
448
|
+
if (isEmpty(dimensions) && isEmpty(bucketDependentFields)) return false;
|
|
436
449
|
|
|
437
|
-
const
|
|
450
|
+
const fieldNames = uniq([...dimensions.map(it => it.name), ...bucketDependentFields]);
|
|
438
451
|
for (const rec of update) {
|
|
439
452
|
const curRec = this._leafMap.get(rec.id);
|
|
440
|
-
if (
|
|
453
|
+
if (fieldNames.some(name => rec.data[name] !== curRec.data[name])) return true;
|
|
441
454
|
}
|
|
442
455
|
|
|
443
456
|
return false;
|
|
@@ -13,7 +13,7 @@ import {View} from '../View';
|
|
|
13
13
|
/**
|
|
14
14
|
* Row within a dataset produced by a Cube / View representing aggregated data on a dimension that
|
|
15
15
|
* has been further grouped into a dynamic child "bucket" - a subset of the dimension-level
|
|
16
|
-
* {@link AggregateRow} produced as per a specified {@link
|
|
16
|
+
* {@link AggregateRow} produced as per a specified {@link BucketSpecFn}.
|
|
17
17
|
*
|
|
18
18
|
* This is an internal data structure - {@link ViewRowData} is the public row-level data API.
|
|
19
19
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "78.0.0-SNAPSHOT.
|
|
3
|
+
"version": "78.0.0-SNAPSHOT.1763737348746",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|