@xh/hoist 76.0.0 → 76.2.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +63 -39
  2. package/admin/jsonsearch/impl/JsonSearchImplModel.ts +1 -1
  3. package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +1 -1
  4. package/admin/tabs/cluster/instances/logs/LogDisplayModel.ts +1 -1
  5. package/admin/tabs/userData/users/UserModel.ts +1 -0
  6. package/appcontainer/ImpersonationBarModel.ts +8 -3
  7. package/build/types/appcontainer/ImpersonationBarModel.d.ts +2 -0
  8. package/build/types/cmp/chart/ChartModel.d.ts +2 -0
  9. package/build/types/core/model/HoistModel.d.ts +1 -1
  10. package/build/types/data/Store.d.ts +1 -0
  11. package/build/types/data/cube/Query.d.ts +1 -0
  12. package/build/types/data/cube/ViewRowData.d.ts +2 -0
  13. package/build/types/desktop/cmp/dash/DashViewModel.d.ts +9 -2
  14. package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -0
  15. package/build/types/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.d.ts +4 -2
  16. package/build/types/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.d.ts +10 -3
  17. package/cmp/chart/ChartModel.ts +8 -2
  18. package/cmp/dataview/DataView.ts +1 -1
  19. package/core/model/HoistModel.ts +1 -1
  20. package/data/Store.ts +13 -3
  21. package/data/cube/Query.ts +2 -0
  22. package/data/cube/View.ts +18 -12
  23. package/data/cube/ViewRowData.ts +3 -0
  24. package/data/cube/row/AggregateRow.ts +1 -0
  25. package/data/cube/row/BucketRow.ts +1 -0
  26. package/data/cube/row/LeafRow.ts +1 -1
  27. package/desktop/appcontainer/AppContainer.ts +15 -10
  28. package/desktop/appcontainer/ImpersonationBar.ts +17 -6
  29. package/desktop/appcontainer/VersionBar.ts +5 -2
  30. package/desktop/cmp/dash/DashViewModel.ts +14 -2
  31. package/desktop/cmp/dash/canvas/impl/DashCanvasView.ts +3 -3
  32. package/desktop/cmp/dash/container/DashContainerModel.ts +22 -15
  33. package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilter.ts +0 -1
  34. package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.ts +12 -4
  35. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTab.scss +12 -4
  36. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTab.ts +8 -2
  37. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +42 -10
  38. package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +1 -1
  39. package/mobile/appcontainer/ImpersonationBar.ts +1 -1
  40. package/package.json +5 -5
  41. package/promise/Promise.ts +3 -3
  42. package/styles/vars.scss +2 -2
  43. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,54 +1,75 @@
1
1
  # Changelog
2
2
 
3
- ## 76.0.0 - 2025-09-26
3
+ ## 76.2.0 - 2025-10-22
4
4
 
5
- ### 💥 Breaking Changes (upgrade difficulty: 🟠 MEDIUM - AG Grid update, Hoist React upgrade)
5
+ ### ⚙️ Technical
6
+
7
+ * Performance improvements to Store for large data sets.
8
+ * New property `cubeRowType` on `ViewRowData` supports identifying bucketed rows.
9
+ * `waitFor` can accept a null value for a timeout.
6
10
 
7
- * Hoist v76 upgrades AG Grid to v34 (from v31), covering three major AG Grid releases with their own
8
- potentially breaking changes. Fortunately, internal Hoist updates to our managed API wrappers mean
9
- that most apps will see very minimal changes, although there are required adjustments to app-level
10
- `package.json` to install updated grid dependencies and `Bootstrap.ts` to import and register
11
- your licensed grid modules at their new import paths.
11
+ ## 76.1.0 - 2025-10-17
12
12
 
13
- Applications implementing `groupRowRenderer` should note that the `value` property passed to this
14
- function is no longer stringified, but is instead the raw field value for the group.
13
+ ### 🎁 New Features
15
14
 
16
- See AG's upgrade guides for more details:
17
- ** [Upgrade to v32](https://www.ag-grid.com/react-data-grid/upgrading-to-ag-grid-32/)
18
- ** [Upgrade to v33](https://www.ag-grid.com/react-data-grid/upgrading-to-ag-grid-33/)
19
- ** [Upgrade to v34](https://www.ag-grid.com/react-data-grid/upgrading-to-ag-grid-34/)
15
+ * Added a public `@bindable titleDetails` config to `DashViewModel` to support displaying additional
16
+ information in the title bar of dashboard widgets. The new property is not persisted, allowing
17
+ apps to programmatically show dynamic info in a widget header without perturbing its saved state.
18
+ * Enhanced grid column filtering to support sorting the list of available values.
20
19
 
21
- * The constructor for `TabModel` has changed to take its owning container as a second argument.
22
- (Most applications do not create `TabModels` directly, but it is possible.)
23
- * The `Exception` class and `HoistException` type have been moved from `@xh\hoist\core` to a new
24
- lower level package `@xh\hoist\exception`. This new structure is not expected to effect most
25
- applications, and was put in place to reduce the risk of circular dependencies between internal
26
- hoist packages.
20
+ ### ⚙️ Technical
27
21
 
28
- ### 🎁 New Features
22
+ * Autofocus the user input when the impersonation bar is shown.
29
23
 
30
- * Added new `extraConfirmText`, `extraConfirmLabel` properties to `MessageOptions`. Use this option
24
+ ### 📚 Libraries
25
+
26
+ * @auth0/auth0-spa-js `2.4 → 2.7`
27
+ * @azure/msal-browser `4.23 → 4.25`
28
+ * dompurify `3.2 → 3.3`
29
+ * mobx `6.13 → 6.15`
30
+
31
+ ## 76.0.0 - 2025-09-26
32
+
33
+ ### 💥 Breaking Changes (upgrade difficulty: 🟠 MEDIUM - AG Grid update, Hoist React upgrade)
34
+
35
+ * Hoist v76 **upgrades AG Grid to v34** (from v31), covering three major AG Grid releases with their
36
+ own potentially breaking changes.
37
+ * Fortunately, internal Hoist updates to our managed API wrappers mean that most apps will see
38
+ very minimal changes, although there are required adjustments to app-level `package.json` to
39
+ install updated grid dependencies and `Bootstrap.ts` to import and register your licensed grid
40
+ modules at their new import paths.
41
+ * Applications implementing `groupRowRenderer` should note that the `value` property passed
42
+ to this function is no longer stringified, but is instead the raw field value for the group.
43
+ * See AG's upgrade guides for more details:
44
+ * [Upgrade to v32](https://www.ag-grid.com/react-data-grid/upgrading-to-ag-grid-32/)
45
+ * [Upgrade to v33](https://www.ag-grid.com/react-data-grid/upgrading-to-ag-grid-33/)
46
+ * [Upgrade to v34](https://www.ag-grid.com/react-data-grid/upgrading-to-ag-grid-34/)
47
+ * Modified the `TabModel` constructor to take its owning container as a second argument.
48
+ * Apps very rarely create `TabModels` directly, so this unlikely to require changes.
49
+ * Moved the `Exception` class and `HoistException` type from `@xh\hoist\core` to a new lower-level
50
+ package `@xh\hoist\exception` to reduce the risk of circule dependencies within Hoist.
51
+ * Apps rarely interact with these directly, so also unlikely to require changes.
52
+
53
+ ### 🎁 New Features
54
+
55
+ * Added `extraConfirmText` + `extraConfirmLabel` configs to `MessageOptions`. Use these new options
31
56
  to require the specified text to be re-typed by a user when confirming a potentially destructive
32
- or disruptive action.
33
- * Updated grid column filters to apply on `Enter` / dismiss on `Esc` and tweaked the filter popup
57
+ or disruptive action. Note their usage within Hoist's Admin Console when deleting a role.
58
+ * Updated grid column filters to apply on `Enter` / dismiss on `Esc`. Tweaked the filter popup
34
59
  toolbar for clarity.
35
- * Added new ability to specify nested tab containers in a single declarative config. Apps may now
60
+ * Added new ability to specify nested tab containers in a single declarative config. Apps may now
36
61
  provide a spec for a nested tab container directly to the `TabConfig.content` property.
37
- * Improvements to View Management:
38
- ** Allow users to create 'Global' views directly in 'Save/Save As' Dialog.
39
- ** Simplify presentation/edit of view visibility to new "Visibility" control
40
- ** Support for the 'isDefaultPinned' attribute on global views has been removed. All global
41
- views will be pinned by default. This feature was deemed too confusing, and not useful in
42
- practice. App maintainers should ensure that all global views are appropriate and well
43
- organized enough to be shown immediately to new users in the view menu.
44
- * New constraint rule: `validEmails` - to validate one or more email addresses in an input field.
45
- * `DashCanvas` accepts a new prop `rglOptions` to pass additional options to the underlying
46
- `react-grid-layout`.
47
- * Experimental grid feature `enableFullWidthScroll` has been promoted to a first-class property
48
- on `GridModel`. Set to true to ensure that the grid will have a single horizontal scrollbar
49
- spanning the width of all columns, including any pinned columns.
50
- * New `@sharePendingPromise` decorator for returning a shared Promise across concurrent async calls.
51
-
62
+ * Improved `ViewManager` features:
63
+ * Enabled globally sharing a new view directly from the 'Save/Save As' dialog.
64
+ * Simplified presentation and management of view visibility via new "Visibility" control.
65
+ * Removed support for the `isDefaultPinned` attribute on global views. All global views will be
66
+ pinned (i.e. show up in user menus) by default. Users can still explicitly "unpin" any global
67
+ views to remove them from their menus.
68
+ * Added a `validEmails` constraint rule to validate one or more email addresses in an input field.
69
+ * Added `DashCanvas.rglOptions` prop - passed through to the underlying `react-grid-layout`.
70
+ * Promoted experimental grid feature `enableFullWidthScroll` to a first-class `GridModel` config.
71
+ Set to true to ensure that the grid will have a single horizontal scrollbar spanning the width of
72
+ all columns, including any pinned columns.
52
73
 
53
74
  ### 🐞 Bug Fixes
54
75
 
@@ -64,6 +85,9 @@
64
85
 
65
86
  ### ⚙️ Technical
66
87
 
88
+ * Added a new `@sharePendingPromise` decorator for returning a shared Promise across concurrent
89
+ async calls. Calls made to a decorated method while a prior call with the same args is still
90
+ pending won't kick off a new call, but will instead receive the same Promise as the first call.
67
91
  * Added `XH.logLevel` to define a minimum logging severity threshold for Hoist's client-side logging
68
92
  utilities. Defaulted to 'info' to prevent possible memory and performance impacts of verbose
69
93
  logging on 'debug'. Change at runtime via new `XH.setLogLevel()` when troubleshooting. See
@@ -73,7 +73,7 @@ export class JsonSearchImplModel extends HoistModel {
73
73
  override onLinked() {
74
74
  this.gridModel = new GridModel({
75
75
  ...this.gridModelConfig,
76
- emptyText: 'No matches found...',
76
+ emptyText: 'No matches found.',
77
77
  selModel: 'single'
78
78
  });
79
79
 
@@ -395,7 +395,7 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
395
395
  treeStyle: TreeStyle.HIGHLIGHTS_AND_BORDERS,
396
396
  autosizeOptions: {mode: 'managed', includeCollapsedChildren: true},
397
397
  exportOptions: {filename: exportFilename('activity-summary')},
398
- emptyText: 'No activity reported...',
398
+ emptyText: 'No activity reported.',
399
399
  sortBy: ['cubeLabel'],
400
400
  expandLevel: 1,
401
401
  levelLabels: () => ['Total', ...this.groupingChooserModel.valueDisplayNames],
@@ -136,7 +136,7 @@ export class LogDisplayModel extends HoistModel {
136
136
  hideHeaders: true,
137
137
  rowBorders: false,
138
138
  sizingMode: 'tiny',
139
- emptyText: 'No log entries found...',
139
+ emptyText: 'No log entries found.',
140
140
  sortBy: 'rowNum|asc',
141
141
  autosizeOptions: {mode: 'disabled'},
142
142
  store: {
@@ -25,6 +25,7 @@ export class UserModel extends HoistModel {
25
25
  makeObservable(this);
26
26
 
27
27
  this.gridModel = new GridModel({
28
+ emptyText: 'No users found.',
28
29
  persistWith: this.persistWith,
29
30
  colChooserModel: true,
30
31
  enableExport: true,
@@ -4,9 +4,11 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {HoistInputModel} from '@xh/hoist/cmp/input';
7
8
  import {HoistModel, XH} from '@xh/hoist/core';
8
- import {action, observable, makeObservable, bindable} from '@xh/hoist/mobx';
9
+ import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
9
10
  import {throwIf} from '@xh/hoist/utils/js';
11
+ import {createRef} from 'react';
10
12
 
11
13
  /**
12
14
  * @internal
@@ -18,6 +20,9 @@ export class ImpersonationBarModel extends HoistModel {
18
20
  @observable.ref targets: string[] = [];
19
21
  @bindable pendingTarget: string = null;
20
22
 
23
+ // For managed focus of desktop select.
24
+ inputRef = createRef<HoistInputModel>();
25
+
21
26
  constructor() {
22
27
  super();
23
28
  makeObservable(this);
@@ -58,7 +63,7 @@ export class ImpersonationBarModel extends HoistModel {
58
63
  @action
59
64
  toggleVisibility() {
60
65
  if (this.isOpen) {
61
- this.hide();
66
+ XH.identityService.isImpersonating ? this.inputRef.current?.focus() : this.hide();
62
67
  } else {
63
68
  this.show();
64
69
  }
@@ -73,7 +78,7 @@ export class ImpersonationBarModel extends HoistModel {
73
78
  try {
74
79
  await XH.identityService.impersonateAsync(pendingTarget);
75
80
  } catch (e) {
76
- this.pendingTarget = '';
81
+ this.pendingTarget = null;
77
82
  XH.handleException(e, {logOnServer: false}); // likely to be an unknown user
78
83
  }
79
84
  };
@@ -1,3 +1,4 @@
1
+ import { HoistInputModel } from '@xh/hoist/cmp/input';
1
2
  import { HoistModel } from '@xh/hoist/core';
2
3
  /**
3
4
  * @internal
@@ -7,6 +8,7 @@ export declare class ImpersonationBarModel extends HoistModel {
7
8
  showRequested: boolean;
8
9
  targets: string[];
9
10
  pendingTarget: string;
11
+ inputRef: import("react").RefObject<HoistInputModel>;
10
12
  constructor();
11
13
  init(): void;
12
14
  get isOpen(): boolean;
@@ -26,6 +26,8 @@ export declare class ChartModel extends HoistModel {
26
26
  * be done via {@link setHighchartsConfig} or {@link setSeries}.
27
27
  */
28
28
  highchart: any;
29
+ /** True if this chart has no series to display */
30
+ get empty(): boolean;
29
31
  constructor(config?: ChartConfig);
30
32
  /**
31
33
  * Update the Highcharts instance configuration.
@@ -9,7 +9,7 @@ import { Class } from 'type-fest';
9
9
  *
10
10
  * The most common use of `HoistModel` is to support Hoist components. Components can be configured
11
11
  * to create or lookup an instance of an appropriate model subclass using the `model` config passed
12
- * to {@link hoistComponent.factory}. Hoist will automatically pass the resolved model instance as a
12
+ * to {@link hoistCmp.factory}. Hoist will automatically pass the resolved model instance as a
13
13
  * prop to the component's `render()` function, where the model's properties can be read/rendered
14
14
  * and any imperative APIs wired to buttons, callbacks, and other handlers.
15
15
  *
@@ -401,6 +401,7 @@ export declare class Store extends HoistBase {
401
401
  private rebuildFiltered;
402
402
  private createRecord;
403
403
  private createRecords;
404
+ private get summaryRecordIds();
404
405
  private parseRaw;
405
406
  private parseUpdate;
406
407
  private createDataDefaults;
@@ -97,6 +97,7 @@ export declare class Query {
97
97
  readonly fields: CubeField[];
98
98
  readonly dimensions: CubeField[];
99
99
  readonly filter: Filter;
100
+ readonly hasFilter: boolean;
100
101
  readonly includeRoot: boolean;
101
102
  readonly includeLeaves: boolean;
102
103
  readonly provideLeaves: boolean;
@@ -7,6 +7,8 @@ export declare class ViewRowData {
7
7
  constructor(id: string);
8
8
  /** Unique id. */
9
9
  id: string;
10
+ /** Denotes a type for the row */
11
+ cubeRowType: 'leaf' | 'aggregate' | 'bucket';
10
12
  /**
11
13
  * Label of the row. The dimension value or, for leaf rows. the underlying cubeId.
12
14
  * Suitable for display, although apps will typically wish to customize leaf row rendering.
@@ -1,6 +1,6 @@
1
+ import { ReactElement } from 'react';
1
2
  import { HoistModel, MenuItemLike, PlainObject, RefreshMode, RenderMode } from '@xh/hoist/core';
2
3
  import '@xh/hoist/desktop/register';
3
- import { ReactElement } from 'react';
4
4
  import { DashViewSpec } from './DashViewSpec';
5
5
  export type DashViewState = PlainObject;
6
6
  /**
@@ -23,8 +23,15 @@ export declare class DashViewModel<T extends DashViewSpec = DashViewSpec> extend
23
23
  * constructing these models - no need to specify manually.
24
24
  */
25
25
  containerModel: any;
26
- /** Title with which to initialize the view. */
26
+ /** Title with which to initialize the view. Value is persisted. */
27
27
  title: string;
28
+ /**
29
+ * Additional info that will be displayed after the title.
30
+ * Applications can bind to this property to provide dynamic title details.
31
+ * Value is not persisted.
32
+ **/
33
+ titleDetails: string;
34
+ get fullTitle(): string;
28
35
  /** Icon with which to initialize the view. */
29
36
  icon: ReactElement;
30
37
  /** State with which to initialize the view. */
@@ -150,6 +150,7 @@ export declare class DashContainerModel extends DashModel<DashContainerViewSpec,
150
150
  private showTitleForm;
151
151
  private hideTitleForm;
152
152
  private createGoldenLayout;
153
+ private getTitleElement;
153
154
  private destroyGoldenLayout;
154
155
  destroy(): void;
155
156
  }
@@ -1,10 +1,10 @@
1
+ import { Column, GridFilterFieldSpec, GridFilterModel, GridModel } from '@xh/hoist/cmp/grid';
1
2
  import { TabContainerModel } from '@xh/hoist/cmp/tab';
2
3
  import { HoistModel } from '@xh/hoist/core';
3
4
  import { CompoundFilter, FieldFilter, FieldType, Filter, FilterLike, Store } from '@xh/hoist/data';
4
- import { GridFilterFieldSpec, GridFilterModel } from '@xh/hoist/cmp/grid';
5
+ import { ColumnHeaderFilterModel } from '../ColumnHeaderFilterModel';
5
6
  import { CustomTabModel } from './custom/CustomTabModel';
6
7
  import { ValuesTabModel } from './values/ValuesTabModel';
7
- import { ColumnHeaderFilterModel } from '../ColumnHeaderFilterModel';
8
8
  export declare class HeaderFilterModel extends HoistModel {
9
9
  xhImpl: boolean;
10
10
  fieldSpec: GridFilterFieldSpec;
@@ -14,7 +14,9 @@ export declare class HeaderFilterModel extends HoistModel {
14
14
  customTabModel: CustomTabModel;
15
15
  get filterModel(): GridFilterModel;
16
16
  get field(): string;
17
+ get gridModel(): GridModel;
17
18
  get store(): Store;
19
+ get column(): Column;
18
20
  get fieldType(): FieldType;
19
21
  get currentGridFilter(): Filter;
20
22
  get columnFilters(): FieldFilter[];
@@ -5,12 +5,16 @@ import { HeaderFilterModel } from '../HeaderFilterModel';
5
5
  export declare class ValuesTabModel extends HoistModel {
6
6
  xhImpl: boolean;
7
7
  headerFilterModel: HeaderFilterModel;
8
- /** Checkbox grid to display enumerated set of values */
8
+ /** Checkbox grid to display enumerated set of values. */
9
9
  gridModel: GridModel;
10
- /** List of currently checked values in the list*/
10
+ /** List of currently checked values. */
11
11
  pendingValues: any[];
12
- /** Bound search term for `StoreFilterField` */
12
+ /** Bound search term for `StoreFilterField`. */
13
13
  filterText: string;
14
+ /**
15
+ * Merge current filter with pendingValues on commit.
16
+ * Used when commitOnChange is false.
17
+ */
14
18
  combineCurrentFilters: boolean;
15
19
  /** FieldFilter output by this model. */
16
20
  get filter(): FieldFilterSpec;
@@ -22,15 +26,18 @@ export declare class ValuesTabModel extends HoistModel {
22
26
  get values(): any[];
23
27
  get valueCount(): number;
24
28
  get hasHiddenValues(): boolean;
29
+ get sortIcon(): any;
25
30
  constructor(headerFilterModel: HeaderFilterModel);
26
31
  syncWithFilter(): void;
27
32
  reset(): void;
28
33
  setRecsChecked(isChecked: boolean, values: any[]): void;
34
+ toggleSort(): void;
29
35
  toggleAllRecsChecked(): void;
30
36
  private onFilterTextChange;
31
37
  private onCombineCurrentFiltersToggle;
32
38
  private getFilter;
33
39
  private doSyncWithFilter;
34
40
  private syncGrid;
41
+ private initGridSortBy;
35
42
  private createGridModel;
36
43
  }
@@ -8,8 +8,8 @@ import {type MouseEvent} from 'react';
8
8
  import type {ChartContextMenuSpec, ChartMenuToken} from '@xh/hoist/cmp/chart/Types';
9
9
  import {getContextMenuItems} from '@xh/hoist/cmp/chart/impl/ChartContextMenuItems';
10
10
  import {HoistModel, PlainObject, Some, XH} from '@xh/hoist/core';
11
- import {action, makeObservable, observable} from '@xh/hoist/mobx';
12
- import {castArray, cloneDeep, isFunction, isNil} from 'lodash';
11
+ import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
12
+ import {castArray, cloneDeep, isEmpty, isFunction, isNil} from 'lodash';
13
13
  import {mergeDeep} from '@xh/hoist/utils/js';
14
14
 
15
15
  interface ChartConfig {
@@ -59,6 +59,12 @@ export class ChartModel extends HoistModel {
59
59
  @observable.ref
60
60
  highchart: any;
61
61
 
62
+ /** True if this chart has no series to display */
63
+ @computed
64
+ get empty(): boolean {
65
+ return isEmpty(this.series);
66
+ }
67
+
62
68
  constructor(config?: ChartConfig) {
63
69
  super();
64
70
  makeObservable(this);
@@ -82,7 +82,7 @@ class DataViewLocalModel extends HoistModel {
82
82
  const {model} = this;
83
83
  return {
84
84
  headerHeight: 0,
85
- suppressMakeColumnVisibleAfterUnGroup: true,
85
+ suppressGroupChangesColumnVisibility: 'suppressShowOnUngroup',
86
86
  getRowHeight: agParams => {
87
87
  const {groupRowHeight, itemHeight} = model;
88
88
 
@@ -20,7 +20,7 @@ import {Class} from 'type-fest';
20
20
  *
21
21
  * The most common use of `HoistModel` is to support Hoist components. Components can be configured
22
22
  * to create or lookup an instance of an appropriate model subclass using the `model` config passed
23
- * to {@link hoistComponent.factory}. Hoist will automatically pass the resolved model instance as a
23
+ * to {@link hoistCmp.factory}. Hoist will automatically pass the resolved model instance as a
24
24
  * prop to the component's `render()` function, where the model's properties can be read/rendered
25
25
  * and any imperative APIs wired to buttons, callbacks, and other handlers.
26
26
  *
package/data/Store.ts CHANGED
@@ -1041,26 +1041,36 @@ export class Store extends HoistBase {
1041
1041
  return ret;
1042
1042
  }
1043
1043
 
1044
- private createRecords(rawData: PlainObject[], parent: StoreRecord, recordMap = new Map()) {
1044
+ private createRecords(
1045
+ rawData: PlainObject[],
1046
+ parent: StoreRecord,
1047
+ recordMap: Map<StoreRecordId, StoreRecord> = new Map(),
1048
+ summaryRecordIds: Set<StoreRecordId> = this.summaryRecordIds
1049
+ ) {
1045
1050
  const {loadTreeData, loadTreeDataFrom} = this;
1051
+
1046
1052
  rawData.forEach(raw => {
1047
1053
  const rec = this.createRecord(raw, parent),
1048
1054
  {id} = rec;
1049
1055
 
1050
1056
  throwIf(
1051
- recordMap.has(id) || this.summaryRecords?.some(it => it.id === id),
1057
+ recordMap.has(id) || summaryRecordIds.has(id),
1052
1058
  `ID ${id} is not unique. Use the 'Store.idSpec' config to resolve a unique ID for each record.`
1053
1059
  );
1054
1060
 
1055
1061
  recordMap.set(id, rec);
1056
1062
 
1057
1063
  if (loadTreeData && raw[loadTreeDataFrom]) {
1058
- this.createRecords(raw[loadTreeDataFrom], rec, recordMap);
1064
+ this.createRecords(raw[loadTreeDataFrom], rec, recordMap, summaryRecordIds);
1059
1065
  }
1060
1066
  });
1061
1067
  return recordMap;
1062
1068
  }
1063
1069
 
1070
+ private get summaryRecordIds(): Set<StoreRecordId> {
1071
+ return new Set(this.summaryRecords?.map(it => it.id) ?? []);
1072
+ }
1073
+
1064
1074
  private parseRaw(data: PlainObject): PlainObject {
1065
1075
  // a) create/prepare the data object
1066
1076
  const ret = Object.create(this._dataDefaults);
@@ -127,6 +127,7 @@ export class Query {
127
127
  readonly fields: CubeField[];
128
128
  readonly dimensions: CubeField[];
129
129
  readonly filter: Filter;
130
+ readonly hasFilter: boolean;
130
131
  readonly includeRoot: boolean;
131
132
  readonly includeLeaves: boolean;
132
133
  readonly provideLeaves: boolean;
@@ -164,6 +165,7 @@ export class Query {
164
165
  this.omitFn = omitFn;
165
166
 
166
167
  this._testFn = this.filter?.getTestFn(this.cube.store) ?? null;
168
+ this.hasFilter = this._testFn != null;
167
169
  }
168
170
 
169
171
  clone(overrides: Partial<QueryConfig>) {
package/data/cube/View.ts CHANGED
@@ -309,12 +309,12 @@ export class View extends HoistBase {
309
309
  appliedDimensions: PlainObject,
310
310
  leafMap: Map<StoreRecordId, LeafRow>
311
311
  ): BaseRow[] {
312
- if (isEmpty(records)) return [];
312
+ if (!records?.length) return [];
313
313
 
314
314
  const rootId = parentId + Cube.RECORD_ID_DELIMITER;
315
315
 
316
- if (isEmpty(dimensions)) {
317
- return map(records, r => {
316
+ if (!dimensions?.length) {
317
+ return records.map(r => {
318
318
  const id = rootId + r.id,
319
319
  leaf = this.cachedRow(id, null, () => new LeafRow(this, id, r));
320
320
  leafMap.set(r.id, leaf);
@@ -355,16 +355,17 @@ export class View extends HoistBase {
355
355
  parentId: string,
356
356
  appliedDimensions: PlainObject
357
357
  ): BaseRow[] {
358
- if (!this.query.bucketSpecFn) return rows;
358
+ const {query} = this;
359
359
 
360
- const bucketSpec = this.query.bucketSpecFn(rows);
361
- if (!bucketSpec) return rows;
360
+ if (!query.bucketSpecFn) return rows;
361
+ if (!query.includeLeaves && rows[0]?.isLeaf) return rows;
362
362
 
363
- if (!this.query.includeLeaves && rows[0]?.isLeaf) return rows;
363
+ const bucketSpec = query.bucketSpecFn(rows);
364
+ if (!bucketSpec) return rows;
364
365
 
365
366
  const {name: bucketName, bucketFn} = bucketSpec,
366
- buckets = {},
367
- ret = [];
367
+ buckets: Record<string, BaseRow[]> = {},
368
+ ret: BaseRow[] = [];
368
369
 
369
370
  // Determine which bucket to put this row into (if any)
370
371
  rows.forEach(row => {
@@ -372,8 +373,8 @@ export class View extends HoistBase {
372
373
  if (isNil(bucketVal)) {
373
374
  ret.push(row);
374
375
  } else {
375
- if (!buckets[bucketVal]) buckets[bucketVal] = [];
376
- buckets[bucketVal].push(row);
376
+ const bucketRows = buckets[bucketVal] ??= [];
377
+ bucketRows.push(row);
377
378
  }
378
379
  });
379
380
 
@@ -454,8 +455,13 @@ export class View extends HoistBase {
454
455
 
455
456
  private filterRecords() {
456
457
  const {query, cube} = this,
458
+ {hasFilter} = query,
457
459
  ret = new Map();
458
- cube.store.records.filter(r => query.test(r)).forEach(r => ret.set(r.id, r));
460
+
461
+ cube.store.records.forEach(r => {
462
+ if (!hasFilter || query.test(r)) ret.set(r.id, r);
463
+ });
464
+
459
465
  this._recordMap = ret;
460
466
  }
461
467
 
@@ -19,6 +19,9 @@ export class ViewRowData {
19
19
  /** Unique id. */
20
20
  id: string;
21
21
 
22
+ /** Denotes a type for the row */
23
+ cubeRowType: 'leaf' | 'aggregate' | 'bucket';
24
+
22
25
  /**
23
26
  * Label of the row. The dimension value or, for leaf rows. the underlying cubeId.
24
27
  * Suitable for display, although apps will typically wish to customize leaf row rendering.
@@ -38,6 +38,7 @@ export class AggregateRow extends BaseRow {
38
38
 
39
39
  this.dim = dim;
40
40
  this.dimName = dimName;
41
+ this.data.cubeRowType = 'aggregate';
41
42
  this.data.cubeLabel = strVal;
42
43
  this.data.cubeDimension = dimName;
43
44
 
@@ -35,6 +35,7 @@ export class BucketRow extends BaseRow {
35
35
  super(view, id);
36
36
 
37
37
  this.bucketSpec = bucketSpec;
38
+ this.data.cubeRowType = 'bucket';
38
39
  this.data.cubeLabel = bucketSpec.labelFn(bucketVal);
39
40
  this.data.cubeDimension = bucketSpec.name;
40
41
 
@@ -33,8 +33,8 @@ export class LeafRow extends BaseRow {
33
33
 
34
34
  constructor(view: View, id: string, rawRecord: StoreRecord) {
35
35
  super(view, id);
36
-
37
36
  this.cubeRecordId = rawRecord.id;
37
+ this.data.cubeRowType = 'leaf';
38
38
  this.data.cubeLabel = rawRecord.id.toString();
39
39
  this.data.cubeDimension = null;
40
40