@gooddata/sdk-ui-dashboard 11.65.0-alpha.10 → 11.65.0-alpha.11

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 (61) hide show
  1. package/NOTICE +1 -39
  2. package/esm/__version.d.ts +1 -1
  3. package/esm/__version.js +1 -1
  4. package/esm/index.d.ts +2 -2
  5. package/esm/index.d.ts.map +1 -1
  6. package/esm/model/commandHandlers/dashboard/common/filterViews.d.ts +36 -3
  7. package/esm/model/commandHandlers/dashboard/common/filterViews.d.ts.map +1 -1
  8. package/esm/model/commandHandlers/dashboard/common/filterViews.js +138 -26
  9. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/index.d.ts +15 -4
  10. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/index.d.ts.map +1 -1
  11. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/index.js +55 -11
  12. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/preloadAttributeFiltersData.d.ts +6 -1
  13. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/preloadAttributeFiltersData.d.ts.map +1 -1
  14. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/preloadAttributeFiltersData.js +14 -6
  15. package/esm/model/commandHandlers/dashboard/resetDashboardHandler.d.ts.map +1 -1
  16. package/esm/model/commandHandlers/dashboard/resetDashboardHandler.js +21 -8
  17. package/esm/model/commandHandlers/filterContext/changeFilterContextSelectionHandler.d.ts.map +1 -1
  18. package/esm/model/commandHandlers/filterContext/changeFilterContextSelectionHandler.js +9 -3
  19. package/esm/model/commandHandlers/filterContext/filterViewHandler.d.ts +10 -0
  20. package/esm/model/commandHandlers/filterContext/filterViewHandler.d.ts.map +1 -1
  21. package/esm/model/commandHandlers/filterContext/filterViewHandler.js +79 -24
  22. package/esm/model/commandHandlers/filterContext/tabSync/writtenTargets.d.ts +17 -0
  23. package/esm/model/commandHandlers/filterContext/tabSync/writtenTargets.d.ts.map +1 -0
  24. package/esm/model/commandHandlers/filterContext/tabSync/writtenTargets.js +79 -0
  25. package/esm/model/commandHandlers/parameters/changeParameterValuesHandler.d.ts.map +1 -1
  26. package/esm/model/commandHandlers/parameters/changeParameterValuesHandler.js +7 -3
  27. package/esm/model/commandHandlers/tabs/switchDashboardTabHandler.d.ts.map +1 -1
  28. package/esm/model/commandHandlers/tabs/switchDashboardTabHandler.js +3 -1
  29. package/esm/model/commands/filters.d.ts +16 -1
  30. package/esm/model/commands/filters.d.ts.map +1 -1
  31. package/esm/model/commands/filters.js +2 -1
  32. package/esm/model/commands/parameters.d.ts +10 -1
  33. package/esm/model/commands/parameters.d.ts.map +1 -1
  34. package/esm/model/commands/parameters.js +2 -1
  35. package/esm/model/store/filterViews/filterViewsReducers.d.ts +8 -0
  36. package/esm/model/store/filterViews/filterViewsReducers.d.ts.map +1 -1
  37. package/esm/model/store/filterViews/filterViewsReducers.js +11 -0
  38. package/esm/model/store/filterViews/filterViewsReducersSelectors.d.ts +6 -0
  39. package/esm/model/store/filterViews/filterViewsReducersSelectors.d.ts.map +1 -1
  40. package/esm/model/store/filterViews/filterViewsReducersSelectors.js +6 -0
  41. package/esm/model/store/filterViews/filterViewsState.d.ts +6 -0
  42. package/esm/model/store/filterViews/filterViewsState.d.ts.map +1 -1
  43. package/esm/model/store/filterViews/index.d.ts +2 -0
  44. package/esm/model/store/filterViews/index.d.ts.map +1 -1
  45. package/esm/model/store/meta/metaSelectors.d.ts +7 -0
  46. package/esm/model/store/meta/metaSelectors.d.ts.map +1 -1
  47. package/esm/model/store/meta/metaSelectors.js +10 -0
  48. package/esm/model/store/tabs/filterContext/filterContextReducers.d.ts +24 -0
  49. package/esm/model/store/tabs/filterContext/filterContextReducers.d.ts.map +1 -1
  50. package/esm/model/store/tabs/filterContext/filterContextReducers.js +75 -0
  51. package/esm/model/store/tabs/index.d.ts +8 -0
  52. package/esm/model/store/tabs/index.d.ts.map +1 -1
  53. package/esm/model/store/tabs/parameters/parametersReducers.d.ts +16 -0
  54. package/esm/model/store/tabs/parameters/parametersReducers.d.ts.map +1 -1
  55. package/esm/model/store/tabs/parameters/parametersReducers.js +25 -0
  56. package/esm/presentation/insightEditor/InsightEditorContent.d.ts.map +1 -1
  57. package/esm/presentation/insightEditor/InsightEditorContent.js +24 -3
  58. package/esm/sdk-ui-dashboard.d.ts +74 -2
  59. package/package.json +23 -23
  60. package/styles/css/main.css +1 -0
  61. package/styles/css/main.css.map +1 -1
@@ -1,16 +1,27 @@
1
1
  import { type BatchAction } from "redux-batched-actions";
2
2
  import { type SagaIterator } from "redux-saga";
3
- import { type IDashboard, type IDateHierarchyTemplate } from "@gooddata/sdk-model";
3
+ import { type IAttributeWithReferences } from "@gooddata/sdk-backend-spi";
4
+ import { type IDashboard, type IDateHierarchyTemplate, type ObjRef } from "@gooddata/sdk-model";
4
5
  import { type InitializeDashboard } from "../../../commands/dashboard.js";
5
6
  import { type DashboardContext } from "../../../types/commonTypes.js";
7
+ type IPreloadedAttributeFilters = {
8
+ refs: ObjRef[];
9
+ attributesWithReferences: IAttributeWithReferences[];
10
+ };
6
11
  export declare function requestCatalog(ctx: DashboardContext, cmd: InitializeDashboard): Generator<import("redux-saga/effects").AllEffect<import("redux-saga/effects").CallEffect<IDateHierarchyTemplate[]> | import("redux-saga/effects").CallEffect<import("./loadCatalog.js").ILoadedCatalog>> | import("redux-saga/effects").PutEffect<{
7
12
  payload: import("../../../../index.js").SetCatalogItemsPayload;
8
13
  type: "catalog/setCatalogItems";
9
14
  }> | import("redux-saga/effects").SelectEffect, void, boolean & [import("./loadCatalog.js").ILoadedCatalog, IDateHierarchyTemplate[]]>;
10
- export declare function preloadAttributeFiltersData(ctx: DashboardContext, dashboard: IDashboard): Generator<import("redux-saga/effects").CallEffect<import("@gooddata/sdk-backend-spi").IAttributeWithReferences[]> | import("redux-saga/effects").PutEffect<{
11
- payload: import("@gooddata/sdk-backend-spi").IAttributeWithReferences[];
15
+ /**
16
+ * Loads the attributes of the attribute filters, unless the initialization already loaded the attributes
17
+ * of all of them. Applying the default view can switch a filter to another display form, so the dashboard
18
+ * can need more attributes than the initialization loaded.
19
+ */
20
+ export declare function preloadAttributeFiltersData(ctx: DashboardContext, dashboard: IDashboard, preloaded?: IPreloadedAttributeFilters): Generator<import("redux-saga/effects").CallEffect<IAttributeWithReferences[]> | import("redux-saga/effects").PutEffect<{
21
+ payload: IAttributeWithReferences[];
12
22
  type: "tabs/setPreloadedAttributesWithReferences";
13
- }> | import("redux-saga/effects").SelectEffect, void, import("@gooddata/sdk-model").IUnavailableReference[] & import("@gooddata/sdk-backend-spi").IAttributeWithReferences[]>;
23
+ }> | import("redux-saga/effects").SelectEffect, void, import("@gooddata/sdk-model").IUnavailableReference[] & IAttributeWithReferences[]>;
14
24
  export declare function requestDashboardsList(ctx: DashboardContext): Generator<import("redux-saga/effects").CallEffect<import("@gooddata/sdk-model").IListedDashboard[]> | import("redux-saga/effects").PutEffect<BatchAction>, void, import("@gooddata/sdk-model").IListedDashboard[]>;
15
25
  export declare function initializeDashboardHandler(ctx: DashboardContext, cmd: InitializeDashboard): SagaIterator;
26
+ export {};
16
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/model/commandHandlers/dashboard/initializeDashboardHandler/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,OAAO,EACH,KAAK,UAAU,EAIf,KAAK,sBAAsB,EAW9B,MAAM,qBAAqB,CAAC;AAO7B,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AA0B1E,OAAO,EACH,KAAK,gBAAgB,EAGxB,MAAM,+BAA+B,CAAC;AA2fvC,wBAAiB,cAAc,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,mBAAmB;;;uIAyB9E;AAkCD,wBAAiB,2BAA2B,CAAC,GAAG,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU;;;8KAOxF;AAED,wBAAiB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,sNAW3D;AAoCD,wBAAiB,0BAA0B,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,mBAAmB,GAAG,YAAY,CAoCzG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/model/commandHandlers/dashboard/initializeDashboardHandler/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EACH,KAAK,wBAAwB,EAIhC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACH,KAAK,UAAU,EAKf,KAAK,sBAAsB,EAK3B,KAAK,MAAM,EAOd,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AA0B1E,OAAO,EACH,KAAK,gBAAgB,EAGxB,MAAM,+BAA+B,CAAC;AA+JvC,KAAK,0BAA0B,GAAG;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wBAAwB,EAAE,wBAAwB,EAAE,CAAC;CACxD,CAAC;AAuYF,wBAAiB,cAAc,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,mBAAmB;;;uIAyB9E;AAgED;;;;GAIG;AACH,wBAAiB,2BAA2B,CACxC,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,UAAU,EACrB,SAAS,CAAC,EAAE,0BAA0B;;;0IAiBzC;AAED,wBAAiB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,sNAW3D;AAqCD,wBAAiB,0BAA0B,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,mBAAmB,GAAG,YAAY,CA0CzG"}
@@ -2,8 +2,8 @@
2
2
  import { uniqBy } from "lodash-es";
3
3
  import { batchActions } from "redux-batched-actions";
4
4
  import { all, call, put, select, spawn } from "redux-saga/effects";
5
- import { walkLayout } from "@gooddata/sdk-backend-spi";
6
- import { areObjRefsEqual, isDrillToInsight, isInsightWidget, isVisualizationSwitcherWidget, serializeObjRef, } from "@gooddata/sdk-model";
5
+ import { walkLayout, } from "@gooddata/sdk-backend-spi";
6
+ import { areObjRefsEqual, isDrillToInsight, isInsightWidget, isVisualizationSwitcherWidget, objRefToString, serializeObjRef, } from "@gooddata/sdk-model";
7
7
  import { createDisplayFormMap, createDisplayFormMapFromCatalog, } from "../../../../_staging/catalog/displayFormMap.js";
8
8
  import { deriveAbsoluteFormGranularitiesFromRelativeForm } from "../../../../_staging/dateFilterConfig/merge.js";
9
9
  import { changeDashboardTimezoneOverride } from "../../../commands/timezone.js";
@@ -34,7 +34,7 @@ import { userActions } from "../../../store/user/index.js";
34
34
  import { collectDashboardParameterRoots } from "../../parameters/dashboardParameterRoots.js";
35
35
  import { loadParameterDependencies } from "../../parameters/loadParameterDependencies.js";
36
36
  import { changeDashboardTimezoneOverrideHandler } from "../../timezone/changeDashboardTimezoneOverrideHandler.js";
37
- import { applyDefaultFilterViewForRenderMode } from "../common/filterViews.js";
37
+ import { applyDefaultFilterViewForRenderMode, isDefaultFilterViewSharedAcrossTabs, } from "../common/filterViews.js";
38
38
  import { actionsToInitializeExistingDashboard, actionsToInitializeNewDashboard, } from "../common/stateInitializers.js";
39
39
  import { loadAutomationTimezone } from "./loadAutomationTimezone.js";
40
40
  import { loadCatalog } from "./loadCatalog.js";
@@ -47,7 +47,7 @@ import { loadFilterViews } from "./loadFilterViews.js";
47
47
  import { dashboardLoadReferenceTypes, loadUnavailableReferences } from "./loadUnavailableReferences.js";
48
48
  import { loadUser } from "./loadUser.js";
49
49
  import { mergeDateFilterConfigWithOverrides } from "./mergeDateFilterConfigs.js";
50
- import { preloadAttributeFiltersData as preloadAttributeFiltersDataFromBackend } from "./preloadAttributeFiltersData.js";
50
+ import { attributeFilterDisplayFormRefs, loadAttributesWithReferences, } from "./preloadAttributeFiltersData.js";
51
51
  import { resolveDashboardConfigAndFeatureFlagDependentCalls } from "./resolveDashboardConfig.js";
52
52
  import { resolveEntitlements } from "./resolveEntitlements.js";
53
53
  import { resolvePermissions } from "./resolvePermissions.js";
@@ -191,10 +191,18 @@ function* loadExistingDashboard(ctx, cmd, dashboardRef) {
191
191
  // FF off => nothing is ever marked unavailable and behavior is unchanged
192
192
  const unavailableReferences = yield call(loadUnavailableReferences, ctx, loadedDashboard, unavailable, Boolean(config.settings?.enableDashboardPartialRendering), !!cmd.payload.persistedDashboard);
193
193
  yield put(unavailableObjectsActions.setUnavailableObjects(unavailableReferences));
194
- const dashboardWithFilterView = applyDefaultFilterViewForRenderMode(loadedDashboard, filterViews, config.exportId, unavailableReferences, config.initialRenderMode);
195
194
  // Resolve active tab: URL's initialTabId takes precedence, otherwise default to first tab
196
195
  // Note: activeTabLocalIdentifier is app state only, NOT persisted to/read from dashboard MD
197
- const resolvedActiveTabId = resolveActiveTabId(dashboardWithFilterView.tabs, cmd.payload.initialTabId, undefined);
196
+ const resolvedActiveTabId = resolveActiveTabId(loadedDashboard.tabs, cmd.payload.initialTabId, undefined);
197
+ // same rule as selectEnablePersistentFiltersAcrossTabs, which cannot be selected before init
198
+ const persistFiltersAcrossTabs = !!config.settings.enableDashboardPersistentFiltersAcrossTabs &&
199
+ loadedDashboard.disablePersistentFiltersAcrossTabs === false;
200
+ const filterViewOptions = { persistFiltersAcrossTabs, activeTabLocalIdentifier: resolvedActiveTabId };
201
+ const preloadedAttributeFilters = yield call(loadAttributesForSharedDefaultFilterView, ctx, loadedDashboard, filterViews, config.initialRenderMode, filterViewOptions, unavailableReferences);
202
+ const dashboardWithFilterView = applyDefaultFilterViewForRenderMode(loadedDashboard, filterViews, config.exportId, unavailableReferences, config.initialRenderMode, {
203
+ ...filterViewOptions,
204
+ attributesWithReferences: preloadedAttributeFilters?.attributesWithReferences,
205
+ });
198
206
  const dashboard = dashboardWithFilterView;
199
207
  const enableParameters = config.settings?.enableParameters ?? false;
200
208
  const [{ tabsAttributeFilterConfigs, tabsDateFilterConfig, tabsDateFilterConfigSource }, workspaceParameters, parameterDependencies,] = yield all([
@@ -225,6 +233,11 @@ function* loadExistingDashboard(ctx, cmd, dashboardRef) {
225
233
  catalogActions.mergeCatalogParameterDependencies(parameterDependencies.byRoot),
226
234
  catalogActions.markParameterDependenciesFailed(parameterDependencies.failedRoots),
227
235
  ...initActions,
236
+ ...(preloadedAttributeFilters
237
+ ? [
238
+ tabsActions.setPreloadedAttributesWithReferences(preloadedAttributeFilters.attributesWithReferences),
239
+ ]
240
+ : []),
228
241
  // NOTE: Tab configs (dateFilterConfig, dateFilterConfigs, attributeFilterConfigs, filterContext)
229
242
  // are now initialized as part of the tabs state in initActions via setTabs action
230
243
  uiActions.setMenuButtonItemsVisibility(config.menuButtonItemsVisibility),
@@ -238,6 +251,7 @@ function* loadExistingDashboard(ctx, cmd, dashboardRef) {
238
251
  dashboard: ctx.dashboardRef, // should be defined as we are in existing dashboard load fn
239
252
  filterViews,
240
253
  }),
254
+ filterViewsActions.setDefaultFilterViewAppliedTabs(resolvedActiveTabId ? [resolvedActiveTabId] : []),
241
255
  ], "@@GDC.DASH/BATCH.INIT.EXISTING");
242
256
  const event = dashboardInitialized(ctx, dashboard, insights, config, permissions, cmd.correlationId);
243
257
  return {
@@ -245,6 +259,7 @@ function* loadExistingDashboard(ctx, cmd, dashboardRef) {
245
259
  event,
246
260
  // an export render takes its timezone from the export itself
247
261
  automationTimezone: exportTimezone ? undefined : automationTimezone,
262
+ preloadedAttributeFilters,
248
263
  };
249
264
  }
250
265
  function* initializeNewDashboard(ctx, cmd) {
@@ -361,9 +376,38 @@ function makeCatalogParametersPayload(result) {
361
376
  }
362
377
  return { status: "loaded", parameters: result };
363
378
  }
364
- export function* preloadAttributeFiltersData(ctx, dashboard) {
379
+ /**
380
+ * The attributes of the attribute filters, which match the filters of the tabs when the default view of the
381
+ * active tab is written to the other tabs. The view is not shared, or the attributes fail to load: none,
382
+ * so filters match on equal display form refs.
383
+ */
384
+ function* loadAttributesForSharedDefaultFilterView(ctx, dashboard, filterViews, renderMode, options, unavailableObjects) {
385
+ if (!isDefaultFilterViewSharedAcrossTabs(dashboard, filterViews, renderMode, options)) {
386
+ return undefined;
387
+ }
388
+ const refs = attributeFilterDisplayFormRefs(dashboard, unavailableObjects);
389
+ try {
390
+ const attributesWithReferences = yield call(loadAttributesWithReferences, ctx, refs);
391
+ return { refs, attributesWithReferences };
392
+ }
393
+ catch (error) {
394
+ console.warn("Loading the attributes of the dashboard filters failed", error);
395
+ return undefined;
396
+ }
397
+ }
398
+ /**
399
+ * Loads the attributes of the attribute filters, unless the initialization already loaded the attributes
400
+ * of all of them. Applying the default view can switch a filter to another display form, so the dashboard
401
+ * can need more attributes than the initialization loaded.
402
+ */
403
+ export function* preloadAttributeFiltersData(ctx, dashboard, preloaded) {
365
404
  const unavailableObjects = yield select(selectUnavailableObjects);
366
- const attributesWithReferences = yield call(preloadAttributeFiltersDataFromBackend, ctx, dashboard, unavailableObjects);
405
+ const refs = attributeFilterDisplayFormRefs(dashboard, unavailableObjects);
406
+ const preloadedRefs = new Set(preloaded?.refs.map(objRefToString));
407
+ if (preloaded && refs.every((ref) => preloadedRefs.has(objRefToString(ref)))) {
408
+ return;
409
+ }
410
+ const attributesWithReferences = yield call(loadAttributesWithReferences, ctx, refs);
367
411
  yield put(tabsActions.setPreloadedAttributesWithReferences(attributesWithReferences));
368
412
  }
369
413
  export function* requestDashboardsList(ctx) {
@@ -374,10 +418,10 @@ export function* requestDashboardsList(ctx) {
374
418
  ]);
375
419
  yield put(dashboardsListActions);
376
420
  }
377
- function* advancedLoader(ctx, cmd, dashboard) {
421
+ function* advancedLoader(ctx, cmd, dashboard, preloadedAttributeFilters) {
378
422
  yield all([
379
423
  call(requestCatalog, ctx, cmd),
380
- call(preloadAttributeFiltersData, ctx, dashboard),
424
+ call(preloadAttributeFiltersData, ctx, dashboard, preloadedAttributeFilters),
381
425
  call(requestDashboardsList, ctx),
382
426
  ]);
383
427
  }
@@ -418,7 +462,7 @@ export function* initializeDashboardHandler(ctx, cmd) {
418
462
  if (dashboardRef) {
419
463
  // let's run effects which are not essential for the existing
420
464
  // dashboard to be rendered, such as catalog load
421
- yield spawn(advancedLoader, ctx, cmd, result.event.payload.dashboard);
465
+ yield spawn(advancedLoader, ctx, cmd, result.event.payload.dashboard, result.preloadedAttributeFilters);
422
466
  }
423
467
  }
424
468
  catch (e) {
@@ -1,5 +1,10 @@
1
1
  import { type IAttributeWithReferences, type IUnavailableDashboardReference } from "@gooddata/sdk-backend-spi";
2
- import { type IDashboard } from "@gooddata/sdk-model";
2
+ import { type IDashboard, type ObjRef } from "@gooddata/sdk-model";
3
3
  import { type DashboardContext } from "../../../types/commonTypes.js";
4
4
  export declare function preloadAttributeFiltersData(ctx: DashboardContext, dashboard?: IDashboard, unavailableObjects?: IUnavailableDashboardReference[]): Promise<IAttributeWithReferences[]>;
5
+ export declare function loadAttributesWithReferences(ctx: DashboardContext, refs: ObjRef[]): Promise<IAttributeWithReferences[]>;
6
+ /**
7
+ * The unique display forms of the attribute filters of the dashboard and its tabs that the user can read.
8
+ */
9
+ export declare function attributeFilterDisplayFormRefs(dashboard?: IDashboard, unavailableObjects?: IUnavailableDashboardReference[]): ObjRef[];
5
10
  //# sourceMappingURL=preloadAttributeFiltersData.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preloadAttributeFiltersData.d.ts","sourceRoot":"","sources":["../../../../../src/model/commandHandlers/dashboard/initializeDashboardHandler/preloadAttributeFiltersData.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACtC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACH,KAAK,UAAU,EAIlB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,wBAAgB,2BAA2B,CACvC,GAAG,EAAE,gBAAgB,EACrB,SAAS,CAAC,EAAE,UAAU,EACtB,kBAAkB,GAAE,8BAA8B,EAAO,GAC1D,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAyBrC"}
1
+ {"version":3,"file":"preloadAttributeFiltersData.d.ts","sourceRoot":"","sources":["../../../../../src/model/commandHandlers/dashboard/initializeDashboardHandler/preloadAttributeFiltersData.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACtC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACH,KAAK,UAAU,EACf,KAAK,MAAM,EAGd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,wBAAgB,2BAA2B,CACvC,GAAG,EAAE,gBAAgB,EACrB,SAAS,CAAC,EAAE,UAAU,EACtB,kBAAkB,GAAE,8BAA8B,EAAO,GAC1D,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAErC;AAED,wBAAgB,4BAA4B,CACxC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAOrC;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC1C,SAAS,CAAC,EAAE,UAAU,EACtB,kBAAkB,GAAE,8BAA8B,EAAO,GAC1D,MAAM,EAAE,CAkBV"}
@@ -1,9 +1,21 @@
1
- // (C) 2021-2025 GoodData Corporation
1
+ // (C) 2021-2026 GoodData Corporation
2
2
  import { uniqBy } from "lodash-es";
3
3
  import { isDashboardAttributeFilter, objRefToString, } from "@gooddata/sdk-model";
4
4
  import { isDashboardObjectRestricted } from "../../../store/filtering/restrictedFilterUtils.js";
5
5
  export function preloadAttributeFiltersData(ctx, dashboard, unavailableObjects = []) {
6
+ return loadAttributesWithReferences(ctx, attributeFilterDisplayFormRefs(dashboard, unavailableObjects));
7
+ }
8
+ export function loadAttributesWithReferences(ctx, refs) {
9
+ if (!refs.length) {
10
+ return Promise.resolve([]);
11
+ }
6
12
  const { backend, workspace } = ctx;
13
+ return backend.workspace(workspace).attributes().getAttributesWithReferences(refs);
14
+ }
15
+ /**
16
+ * The unique display forms of the attribute filters of the dashboard and its tabs that the user can read.
17
+ */
18
+ export function attributeFilterDisplayFormRefs(dashboard, unavailableObjects = []) {
7
19
  const dashboardAttributeFilterRefs = dashboard?.filterContext?.filters
8
20
  .filter(isDashboardAttributeFilter)
9
21
  .map((filter) => filter.attributeFilter.displayForm);
@@ -15,9 +27,5 @@ export function preloadAttributeFiltersData(ctx, dashboard, unavailableObjects =
15
27
  }, []);
16
28
  const allRefs = [...(dashboardAttributeFilterRefs ?? []), ...(tabsAttributeFilterRefs ?? [])].filter((ref) => !isDashboardObjectRestricted(ref, "displayForm", unavailableObjects));
17
29
  // Remove duplicates by comparing ObjRef string representations
18
- const uniqueRefs = uniqBy(allRefs, objRefToString);
19
- if (!uniqueRefs?.length) {
20
- return Promise.resolve([]);
21
- }
22
- return backend.workspace(workspace).attributes().getAttributesWithReferences(uniqueRefs);
30
+ return uniqBy(allRefs, objRefToString);
23
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resetDashboardHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/dashboard/resetDashboardHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAa/C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,2BAA2B,CAAC;AAiBvF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAWnE,wBAAiB,qBAAqB,CAClC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,eAAe,GACrB,YAAY,CAAC,kBAAkB,CAAC,CASlC;AAED;;GAEG;AACH,wBAAiB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU;;;;;;;;;;;;;;;GAUzG"}
1
+ {"version":3,"file":"resetDashboardHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/dashboard/resetDashboardHandler.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAa/C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,2BAA2B,CAAC;AAsBvF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAWnE,wBAAiB,qBAAqB,CAClC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,eAAe,GACrB,YAAY,CAAC,kBAAkB,CAAC,CASlC;AAED;;GAEG;AACH,wBAAiB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU;;;;;;;;;;;;;;;GAUzG"}
@@ -8,8 +8,10 @@ import { selectAllCatalogDisplayFormsMap, selectCatalogParameters, } from "../..
8
8
  import { selectDateFilterConfig, selectEnableImmediateAttributeFilterDisplayAsLabelMigration, selectSettings, } from "../../store/config/configSelectors.js";
9
9
  import { selectCrossFilteringFiltersLocalIdentifiers } from "../../store/drill/drillSelectors.js";
10
10
  import { selectDashboardFilterViews } from "../../store/filterViews/filterViewsReducersSelectors.js";
11
- import { selectPersistedDashboard } from "../../store/meta/metaSelectors.js";
11
+ import { filterViewsActions } from "../../store/filterViews/index.js";
12
+ import { selectPersistedDashboard, selectPersistedEnablePersistentFiltersAcrossTabs, } from "../../store/meta/metaSelectors.js";
12
13
  import { selectRenderMode } from "../../store/renderMode/renderModeSelectors.js";
14
+ import { selectPreloadedAttributesWithReferences } from "../../store/tabs/filterContext/filterContextSelectors.js";
13
15
  import { selectActiveTabLocalIdentifier, selectTabs } from "../../store/tabs/tabsSelectors.js";
14
16
  import { DEFAULT_TAB_ID } from "../../store/tabs/tabsState.js";
15
17
  import { selectUnavailableObjects } from "../../store/unavailableObjects/unavailableObjectsSelectors.js";
@@ -119,12 +121,6 @@ function* resetDashboardFromPersisted(ctx, renderMode) {
119
121
  // all tabs are rebuilt, so each needs its own default view, not only the active one
120
122
  const filterViews = yield select(selectDashboardFilterViews);
121
123
  const unavailableObjects = yield select(selectUnavailableObjects);
122
- const dashboardWithUpdatedFilterContext = applyDefaultFilterViewForRenderMode(persistedDashboard, filterViews, ctx.config?.exportId, unavailableObjects, renderMode);
123
- const insightRefsFromWidgets = insightReferencesFromDashboard(dashboardWithUpdatedFilterContext);
124
- const uniqueInsightRefsFromWidgets = uniqWith(insightRefsFromWidgets, areObjRefsEqual);
125
- const resolvedInsights = yield call(resolveInsights, ctx, uniqueInsightRefsFromWidgets);
126
- const displayForms = yield select(selectAllCatalogDisplayFormsMap);
127
- const resolvedInsightsValues = [...resolvedInsights.resolved.values()];
128
124
  const currentActiveTabId = yield select(selectActiveTabLocalIdentifier);
129
125
  // Validate that the current active tab exists in the persisted dashboard
130
126
  // If not (e.g., user created a new tab but didn't save), fall back to first tab
@@ -132,8 +128,25 @@ function* resetDashboardFromPersisted(ctx, renderMode) {
132
128
  const effectiveActiveTabId = activeTabExistsInPersisted
133
129
  ? currentActiveTabId
134
130
  : persistedDashboard.tabs?.[0]?.localIdentifier;
131
+ // the state is rebuilt from the persisted dashboard, so an unsaved change of the setting does not apply
132
+ const persistFiltersAcrossTabs = yield select(selectPersistedEnablePersistentFiltersAcrossTabs);
133
+ const attributesWithReferences = yield select(selectPreloadedAttributesWithReferences);
134
+ const dashboardWithUpdatedFilterContext = applyDefaultFilterViewForRenderMode(persistedDashboard, filterViews, ctx.config?.exportId, unavailableObjects, renderMode, {
135
+ persistFiltersAcrossTabs,
136
+ activeTabLocalIdentifier: effectiveActiveTabId,
137
+ attributesWithReferences,
138
+ });
139
+ const insightRefsFromWidgets = insightReferencesFromDashboard(dashboardWithUpdatedFilterContext);
140
+ const uniqueInsightRefsFromWidgets = uniqWith(insightRefsFromWidgets, areObjRefsEqual);
141
+ const resolvedInsights = yield call(resolveInsights, ctx, uniqueInsightRefsFromWidgets);
142
+ const displayForms = yield select(selectAllCatalogDisplayFormsMap);
143
+ const resolvedInsightsValues = [...resolvedInsights.resolved.values()];
135
144
  const workspaceParameters = yield select(selectCatalogParameters);
136
- batch = yield call(actionsToInitializeExistingDashboard, ctx, dashboardWithUpdatedFilterContext, resolvedInsightsValues, settings, isImmediateAttributeFilterMigrationEnabled, effectiveOriginalFilterContext, migratedAttributeFilters, effectiveAttributeFilterConfigs, dateFilterConfig, tabsDateFilterConfigSource, displayForms, persistedDashboard, effectiveActiveTabId, workspaceParameters);
145
+ const initActions = yield call(actionsToInitializeExistingDashboard, ctx, dashboardWithUpdatedFilterContext, resolvedInsightsValues, settings, isImmediateAttributeFilterMigrationEnabled, effectiveOriginalFilterContext, migratedAttributeFilters, effectiveAttributeFilterConfigs, dateFilterConfig, tabsDateFilterConfigSource, displayForms, persistedDashboard, effectiveActiveTabId, workspaceParameters);
146
+ batch = [
147
+ ...initActions,
148
+ filterViewsActions.setDefaultFilterViewAppliedTabs(effectiveActiveTabId ? [effectiveActiveTabId] : []),
149
+ ];
137
150
  }
138
151
  else {
139
152
  const settings = yield select(selectSettings);
@@ -1 +1 @@
1
- {"version":3,"file":"changeFilterContextSelectionHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/filterContext/changeFilterContextSelectionHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAyC/C,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAuC9E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAgOnE,wBAAiB,mCAAmC,CAChD,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,4BAA4B,GAClC,YAAY,CAAC,IAAI,CAAC,CAqOpB"}
1
+ {"version":3,"file":"changeFilterContextSelectionHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/filterContext/changeFilterContextSelectionHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAyC/C,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAuC9E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAiOnE,wBAAiB,mCAAmC,CAChD,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,4BAA4B,GAClC,YAAY,CAAC,IAAI,CAAC,CA4OpB"}
@@ -1,6 +1,6 @@
1
1
  // (C) 2021-2026 GoodData Corporation
2
2
  import { compact, partition, uniqBy } from "lodash-es";
3
- import { all, call, select } from "redux-saga/effects";
3
+ import { all, call, put, select } from "redux-saga/effects";
4
4
  import { NotSupported } from "@gooddata/sdk-backend-spi";
5
5
  import { areObjRefsEqual, attributeElementsIsEmpty, dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemFilterElementsBy, dashboardAttributeFilterItemFilterElementsByDate, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemValidateElementsBy, dashboardFilterLocalIdentifier, dashboardFilterObjRef, dashboardMeasureValueFilterLocalIdentifier, getAttributeElementsItems, isAllTimeDashboardDateFilter, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMeasureValueFilter, isDashboardTextAttributeFilter, isSingleSelectionFilter, isUriRef, objRefToString, serializeObjRef, updateAttributeElementsItems, } from "@gooddata/sdk-model";
6
6
  import { dashboardFilterToFilterContextItem } from "../../../_staging/dashboard/dashboardFilterContext.js";
@@ -20,6 +20,7 @@ import { resolveAndRegisterDisplayFormMetadata } from "./attributeFilter/resolve
20
20
  import { computedAttributesDisabledMessage, findDisabledComputedAttributeRef, } from "./attributeFilter/validation/computedAttributesEnabledValidation.js";
21
21
  import { canApplyDateFilter } from "./common.js";
22
22
  import { beginTabSync, endTabSync, putTabSynced } from "./tabSync/tabSyncSaga.js";
23
+ import { writtenFiltersOf } from "./tabSync/writtenTargets.js";
23
24
  function classifyRestore(type, restoresOriginal) {
24
25
  // A computed key is not inferred as a single-property mapped type.
25
26
  return { [type]: restoresOriginal };
@@ -121,7 +122,7 @@ function* selectAttributeFilterConfigsSelectionTypeMapTabAware(tabLocalIdentifie
121
122
  return yield select(selectAttributeFilterConfigsSelectionTypeMap);
122
123
  }
123
124
  export function* changeFilterContextSelectionHandler(ctx, cmd) {
124
- const { filters, resetOthers, attributeFilterConfigs = [], tabLocalIdentifier, matchByLocalIdentifier = false, restorePerTabOriginals = false, } = cmd.payload;
125
+ const { filters, resetOthers, attributeFilterConfigs = [], tabLocalIdentifier, matchByLocalIdentifier = false, restorePerTabOriginals = false, adoptAsOriginal = false, } = cmd.payload;
125
126
  // Validate that the target tab exists if tabLocalIdentifier is provided
126
127
  if (tabLocalIdentifier) {
127
128
  const tabs = yield select(selectTabs);
@@ -233,13 +234,18 @@ export function* changeFilterContextSelectionHandler(ctx, cmd) {
233
234
  }
234
235
  }
235
236
  }
236
- yield call(putTabSynced, session, markRestoreTargetOriginal([
237
+ const dispatchedActions = yield call(putTabSynced, session, markRestoreTargetOriginal([
237
238
  ...attributeFilterResult.actions,
238
239
  ...commonDateFilterUpdateActions,
239
240
  ...dateFiltersUpdateActions,
240
241
  ...measureValueFilterUpdateActions,
241
242
  ], restorePerTabOriginals));
242
243
  yield call(endTabSync, ctx, cmd, session, tabLocalIdentifier);
244
+ // A default filter view initializes the shared state. The values it wrote are that initial state,
245
+ // on this tab and on every tab they were synced to, so a reset restores them.
246
+ if (adoptAsOriginal) {
247
+ yield put(tabsActions.adoptAppliedFiltersAsOriginal(writtenFiltersOf(dispatchedActions)));
248
+ }
243
249
  }
244
250
  function* getDashboardFilterByAttributeMatching(filterRef, resolvedDisplayForms, ctx, tabLocalIdentifier, isFilterExcluded, areDisplayFormsEquivalent = areObjRefsEqual) {
245
251
  if (isUriRef(filterRef) && !ctx.backend.capabilities.supportsObjectUris) {
@@ -1,9 +1,19 @@
1
1
  import { type SagaIterator } from "redux-saga";
2
2
  import { type IApplyFilterView, type IDeleteFilterView, type ISaveFilterView, type ISetFilterViewAsDefault } from "../../commands/filters.js";
3
+ import { type ISwitchDashboardTab } from "../../commands/tabs.js";
3
4
  import { type DashboardContext } from "../../types/commonTypes.js";
4
5
  export declare function saveFilterViewHandler(ctx: DashboardContext, cmd: ISaveFilterView): SagaIterator<void>;
5
6
  export declare function deleteFilterViewHandler(ctx: DashboardContext, cmd: IDeleteFilterView): SagaIterator<void>;
6
7
  export declare function applyFilterViewHandler(ctx: DashboardContext, cmd: IApplyFilterView): SagaIterator<void>;
8
+ /**
9
+ * While filters persist across tabs, the dashboard opens with the default view of its active tab as
10
+ * the shared state. Every other tab applies its own default view, to all tabs, on its first visit in
11
+ * the session. Later visits keep the shared state. A drill to the tab sets the shared state itself, so
12
+ * it counts as the first visit without applying the view.
13
+ *
14
+ * @internal
15
+ */
16
+ export declare function applyDefaultFilterViewOnFirstTabVisit(ctx: DashboardContext, cmd: ISwitchDashboardTab, tabLocalIdentifier: string): SagaIterator<void>;
7
17
  export declare function setFilterViewAsDefaultHandler(ctx: DashboardContext, cmd: ISetFilterViewAsDefault): SagaIterator<void>;
8
18
  export declare function reloadFilterViewsHandler(ctx: DashboardContext): SagaIterator<void>;
9
19
  //# sourceMappingURL=filterViewHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filterViewHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/filterContext/filterViewHandler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAc/C,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAE/B,MAAM,2BAA2B,CAAC;AA4BnC,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAenE,wBAAiB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,eAAe,GAAG,YAAY,CAAC,IAAI,CAAC,CAqEtG;AAMD,wBAAiB,uBAAuB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB,GAAG,YAAY,CAAC,IAAI,CAAC,CAsB1G;AAOD,wBAAiB,sBAAsB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,gBAAgB,GAAG,YAAY,CAAC,IAAI,CAAC,CAkDxG;AAMD,wBAAiB,6BAA6B,CAC1C,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,uBAAuB,GAC7B,YAAY,CAAC,IAAI,CAAC,CAuBpB;AAED,wBAAiB,wBAAwB,CAAC,GAAG,EAAE,gBAAgB,GAAG,YAAY,CAAC,IAAI,CAAC,CAcnF"}
1
+ {"version":3,"file":"filterViewHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/filterContext/filterViewHandler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAe/C,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAE/B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAoClE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAiBnE,wBAAiB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,eAAe,GAAG,YAAY,CAAC,IAAI,CAAC,CAqEtG;AAMD,wBAAiB,uBAAuB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB,GAAG,YAAY,CAAC,IAAI,CAAC,CAsB1G;AAOD,wBAAiB,sBAAsB,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,gBAAgB,GAAG,YAAY,CAAC,IAAI,CAAC,CAYxG;AA6DD;;;;;;;GAOG;AACH,wBAAiB,qCAAqC,CAClD,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,mBAAmB,EACxB,kBAAkB,EAAE,MAAM,GAC3B,YAAY,CAAC,IAAI,CAAC,CAyCpB;AAMD,wBAAiB,6BAA6B,CAC1C,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,uBAAuB,GAC7B,YAAY,CAAC,IAAI,CAAC,CAuBpB;AAED,wBAAiB,wBAAwB,CAAC,GAAG,EAAE,gBAAgB,GAAG,YAAY,CAAC,IAAI,CAAC,CAcnF"}
@@ -8,16 +8,20 @@ import { filterViewApplicationFailed, filterViewApplicationSucceeded, filterView
8
8
  import { selectCatalogParameters } from "../../store/catalog/catalogSelectors.js";
9
9
  import { selectIsApplyFiltersAllAtOnceEnabledAndSet } from "../../store/config/configSelectors.js";
10
10
  import { selectCrossFilteringFiltersLocalIdentifiers } from "../../store/drill/drillSelectors.js";
11
- import { selectFilterViews } from "../../store/filterViews/filterViewsReducersSelectors.js";
11
+ import { selectDashboardFilterViews, selectDefaultFilterViewAppliedTabs, selectFilterViews, } from "../../store/filterViews/filterViewsReducersSelectors.js";
12
12
  import { filterViewsActions } from "../../store/filterViews/index.js";
13
+ import { selectEnablePersistentFiltersAcrossTabs } from "../../store/meta/metaSelectors.js";
14
+ import { selectIsInEditMode } from "../../store/renderMode/renderModeSelectors.js";
13
15
  import { selectAttributeFilterConfigsOverrides } from "../../store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
14
16
  import { selectFilterContextDefinition, selectWorkingFilterContextDefinition, } from "../../store/tabs/filterContext/filterContextSelectors.js";
15
17
  import { tabsActions } from "../../store/tabs/index.js";
16
18
  import { selectDashboardParameterEntries, selectFilterViewDisplayParameters, } from "../../store/tabs/parameters/parametersSelectors.js";
17
- import { selectActiveTabLocalIdentifier } from "../../store/tabs/tabsSelectors.js";
19
+ import { selectActiveTabLocalIdentifier, selectFirstTabLocalIdentifier, } from "../../store/tabs/tabsSelectors.js";
20
+ import { findTabDefaultFilterView } from "../dashboard/common/filterViews.js";
18
21
  import { resolveParameterValuesForFilterView } from "../dashboard/common/parameterHydration.js";
19
22
  import { loadFilterViews } from "../dashboard/initializeDashboardHandler/loadFilterViews.js";
20
23
  import { changeParameterValuesHandler } from "../parameters/changeParameterValuesHandler.js";
24
+ import { changeFilterContextSelectionHandler } from "./changeFilterContextSelectionHandler.js";
21
25
  import { resetCrossFiltering } from "./common.js";
22
26
  function createFilterView(ctx, filterView) {
23
27
  return ctx.backend.workspace(ctx.workspace).dashboards().createFilterView(filterView);
@@ -93,35 +97,86 @@ function* findFilterView(ref) {
93
97
  }
94
98
  export function* applyFilterViewHandler(ctx, cmd) {
95
99
  const filterView = yield call(findFilterView, cmd.payload.ref);
96
- const attributeFilterConfigs = yield select(selectAttributeFilterConfigsOverrides);
97
100
  if (filterView) {
98
- yield call(resetCrossFiltering, cmd);
99
- const isApplyAllAtOnceEnabledAndSet = yield select(selectIsApplyFiltersAllAtOnceEnabledAndSet);
100
- if (isApplyAllAtOnceEnabledAndSet) {
101
- yield put(tabsActions.resetWorkingSelection());
102
- }
103
- yield put(changeFilterContextSelectionByParams({
104
- filters: filterView.filterContext.filters,
105
- attributeFilterConfigs,
106
- resetOthers: true,
107
- correlationId: cmd.correlationId,
108
- // a filter view snapshots this dashboard's own filter context, so its localIdentifiers
109
- // are authoritative; display form matching alone cannot tell apart filters that share
110
- // a display form
111
- matchByLocalIdentifier: true,
112
- }));
113
- const parameterEntries = yield select(selectDashboardParameterEntries);
114
- const workspaceParameters = yield select(selectCatalogParameters);
115
- yield call(changeParameterValuesHandler, ctx, changeParameterValues({
116
- parameters: resolveParameterValuesForFilterView(parameterEntries, filterView.parameters ?? [], workspaceParameters),
117
- correlationId: cmd.correlationId,
118
- }));
101
+ yield call(applyFilterViewSelection, ctx, cmd, filterView);
119
102
  yield put(filterViewApplicationSucceeded(ctx, filterView, cmd.correlationId));
120
103
  }
121
104
  else {
122
105
  yield put(filterViewApplicationFailed(ctx));
123
106
  }
124
107
  }
108
+ /**
109
+ * Applies the filters and parameters of `filterView` to the active tab. While filters persist across
110
+ * tabs, the filter and parameter handlers mirror the values to every other tab, so the view sets the
111
+ * shared state.
112
+ */
113
+ function* applyFilterViewSelection(ctx, cmd, filterView, options) {
114
+ const attributeFilterConfigs = yield select(selectAttributeFilterConfigsOverrides);
115
+ yield call(resetCrossFiltering, cmd);
116
+ const isApplyAllAtOnceEnabledAndSet = yield select(selectIsApplyFiltersAllAtOnceEnabledAndSet);
117
+ if (isApplyAllAtOnceEnabledAndSet) {
118
+ yield put(tabsActions.resetWorkingSelection());
119
+ }
120
+ const parameterEntries = yield select(selectDashboardParameterEntries);
121
+ const workspaceParameters = yield select(selectCatalogParameters);
122
+ yield call(changeParameterValuesHandler, ctx, changeParameterValues({
123
+ parameters: resolveParameterValuesForFilterView(parameterEntries, filterView.parameters ?? [], workspaceParameters),
124
+ correlationId: cmd.correlationId,
125
+ adoptAsOriginal: options?.adoptAsOriginal,
126
+ }));
127
+ // inline, so the view is applied before the caller continues: a queued command would run after
128
+ // the caller's own filter changes (e.g. automation filters after a tab switch) and overwrite them
129
+ yield call(changeFilterContextSelectionHandler, ctx, changeFilterContextSelectionByParams({
130
+ filters: filterView.filterContext.filters,
131
+ attributeFilterConfigs,
132
+ resetOthers: true,
133
+ correlationId: cmd.correlationId,
134
+ // a default view initializes the shared state, so the applied values are what a reset restores
135
+ adoptAsOriginal: options?.adoptAsOriginal,
136
+ // a filter view snapshots this dashboard's own filter context, so its localIdentifiers
137
+ // are authoritative; display form matching alone cannot tell apart filters that share
138
+ // a display form
139
+ matchByLocalIdentifier: true,
140
+ }));
141
+ }
142
+ /**
143
+ * While filters persist across tabs, the dashboard opens with the default view of its active tab as
144
+ * the shared state. Every other tab applies its own default view, to all tabs, on its first visit in
145
+ * the session. Later visits keep the shared state. A drill to the tab sets the shared state itself, so
146
+ * it counts as the first visit without applying the view.
147
+ *
148
+ * @internal
149
+ */
150
+ export function* applyDefaultFilterViewOnFirstTabVisit(ctx, cmd, tabLocalIdentifier) {
151
+ const persistFiltersAcrossTabs = yield select(selectEnablePersistentFiltersAcrossTabs);
152
+ // edit mode starts from the saved filters, same as on load
153
+ const isInEditMode = yield select(selectIsInEditMode);
154
+ if (!persistFiltersAcrossTabs || isInEditMode) {
155
+ return;
156
+ }
157
+ const appliedTabs = yield select(selectDefaultFilterViewAppliedTabs);
158
+ if (appliedTabs.includes(tabLocalIdentifier)) {
159
+ return;
160
+ }
161
+ if (cmd.payload.source === "drillToSelf") {
162
+ yield put(filterViewsActions.markDefaultFilterViewApplied(tabLocalIdentifier));
163
+ return;
164
+ }
165
+ const filterViews = yield select(selectDashboardFilterViews);
166
+ const firstTabLocalIdentifier = yield select(selectFirstTabLocalIdentifier);
167
+ const defaultFilterView = findTabDefaultFilterView(filterViews, tabLocalIdentifier, tabLocalIdentifier === firstTabLocalIdentifier);
168
+ // A failed view must not fail the caller (the tab is already switched, a save already persisted).
169
+ // The tab stays unmarked, so its next visit retries the view.
170
+ try {
171
+ if (defaultFilterView) {
172
+ yield call(applyFilterViewSelection, ctx, cmd, defaultFilterView, { adoptAsOriginal: true });
173
+ }
174
+ yield put(filterViewsActions.markDefaultFilterViewApplied(tabLocalIdentifier));
175
+ }
176
+ catch (error) {
177
+ defaultErrorHandler(error);
178
+ }
179
+ }
125
180
  function setFilterViewAsDefault(ctx, ref, isDefault) {
126
181
  return ctx.backend.workspace(ctx.workspace).dashboards().setFilterViewAsDefault(ref, isDefault);
127
182
  }
@@ -0,0 +1,17 @@
1
+ import { type Action } from "@reduxjs/toolkit";
2
+ import { type IWrittenTabFilters } from "../../../store/tabs/filterContext/filterContextReducers.js";
3
+ import { type IWrittenTabParameters } from "../../../store/tabs/parameters/parametersReducers.js";
4
+ /**
5
+ * The applied filters that `actions` write, per tab. Staged selection changes are not applied, so they
6
+ * are skipped.
7
+ *
8
+ * @internal
9
+ */
10
+ export declare function writtenFiltersOf(actions: readonly Action[]): IWrittenTabFilters[];
11
+ /**
12
+ * The parameters that `actions` write, per tab.
13
+ *
14
+ * @internal
15
+ */
16
+ export declare function writtenParametersOf(actions: readonly Action[]): IWrittenTabParameters[];
17
+ //# sourceMappingURL=writtenTargets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writtenTargets.d.ts","sourceRoot":"","sources":["../../../../../src/model/commandHandlers/filterContext/tabSync/writtenTargets.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK/C,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,4DAA4D,CAAC;AAErG,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,sDAAsD,CAAC;AAMlG;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,EAAE,CA8CjF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,qBAAqB,EAAE,CAgBvF"}
@@ -0,0 +1,79 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { BATCH } from "redux-batched-actions";
3
+ import { areObjRefsEqual } from "@gooddata/sdk-model";
4
+ import { tabsActions } from "../../../store/tabs/index.js";
5
+ /**
6
+ * The applied filters that `actions` write, per tab. Staged selection changes are not applied, so they
7
+ * are skipped.
8
+ *
9
+ * @internal
10
+ */
11
+ export function writtenFiltersOf(actions) {
12
+ const byTab = new Map();
13
+ const forTab = (tabLocalIdentifier) => {
14
+ let written = byTab.get(tabLocalIdentifier);
15
+ if (!written) {
16
+ written = { tabLocalIdentifier, filterLocalIds: [], dateDataSets: [], commonDateFilter: false };
17
+ byTab.set(tabLocalIdentifier, written);
18
+ }
19
+ return written;
20
+ };
21
+ const addFilters = (tabLocalIdentifier, localIdentifiers) => {
22
+ const written = forTab(tabLocalIdentifier);
23
+ written.filterLocalIds = [...new Set([...written.filterLocalIds, ...localIdentifiers])];
24
+ };
25
+ for (const action of actions.flatMap(unbatched)) {
26
+ if (tabsActions.updateAttributeFilterSelection.match(action) ||
27
+ tabsActions.replaceAttributeFilterItem.match(action) ||
28
+ tabsActions.changeAttributeDisplayForm.match(action)) {
29
+ if (!action.payload.isWorkingSelectionChange) {
30
+ addFilters(action.payload.tabLocalIdentifier, [action.payload.filterLocalId]);
31
+ }
32
+ }
33
+ else if (tabsActions.clearAttributeFiltersSelection.match(action)) {
34
+ addFilters(action.payload.tabLocalIdentifier, action.payload.filterLocalIds);
35
+ }
36
+ else if (tabsActions.changeMeasureValueFilterCondition.match(action) ||
37
+ tabsActions.setMeasureValueFilterDimensionality.match(action)) {
38
+ if (!("isWorkingSelectionChange" in action.payload && action.payload.isWorkingSelectionChange)) {
39
+ addFilters(action.payload.tabLocalIdentifier, [action.payload.localIdentifier]);
40
+ }
41
+ }
42
+ else if (tabsActions.upsertDateFilter.match(action) && !action.payload.isWorkingSelectionChange) {
43
+ const written = forTab(action.payload.tabLocalIdentifier);
44
+ const { dataSet } = action.payload;
45
+ if (!dataSet) {
46
+ written.commonDateFilter = true;
47
+ }
48
+ else if (!written.dateDataSets.some((writtenDataSet) => areObjRefsEqual(writtenDataSet, dataSet))) {
49
+ written.dateDataSets = [...written.dateDataSets, dataSet];
50
+ }
51
+ }
52
+ }
53
+ return [...byTab.values()];
54
+ }
55
+ /**
56
+ * The parameters that `actions` write, per tab.
57
+ *
58
+ * @internal
59
+ */
60
+ export function writtenParametersOf(actions) {
61
+ const byTab = new Map();
62
+ for (const action of actions.flatMap(unbatched)) {
63
+ if (!tabsActions.setParameterRuntimeValues.match(action)) {
64
+ continue;
65
+ }
66
+ const { tabLocalIdentifier, values } = action.payload;
67
+ const refs = byTab.get(tabLocalIdentifier) ?? [];
68
+ for (const { ref } of values) {
69
+ if (!refs.some((writtenRef) => areObjRefsEqual(writtenRef, ref))) {
70
+ refs.push(ref);
71
+ }
72
+ }
73
+ byTab.set(tabLocalIdentifier, refs);
74
+ }
75
+ return [...byTab.entries()].map(([tabLocalIdentifier, refs]) => ({ tabLocalIdentifier, refs }));
76
+ }
77
+ function unbatched(action) {
78
+ return action.type === BATCH ? action.payload.flatMap(unbatched) : [action];
79
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"changeParameterValuesHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/parameters/changeParameterValuesHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EAAE,KAAK,sBAAsB,EAAmB,MAAM,qBAAqB,CAAC;AAEnF,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAY3E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGnE;;;;;;;GAOG;AACH,wBAAiB,4BAA4B,CACzC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,sBAAsB,GAC5B,YAAY,CAAC,IAAI,CAAC,CAwBpB;AAoCD;;;;;GAKG;AACH,wBAAiB,wCAAwC,CACrD,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,GAAG,SAAS,CAAC,EAClE,WAAW,EAAE,MAAM,GACpB,YAAY,CAAC,IAAI,CAAC,CAOpB;AAED;;;;;GAKG;AACH,wBAAiB,yBAAyB,CACtC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,iBAAiB,EACtB,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,sBAAsB,EAAE,GAAG,SAAS,GACnD,YAAY,CAAC,IAAI,CAAC,CASpB"}
1
+ {"version":3,"file":"changeParameterValuesHandler.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/parameters/changeParameterValuesHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EAAE,KAAK,sBAAsB,EAAmB,MAAM,qBAAqB,CAAC;AAEnF,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAY3E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAInE;;;;;;;GAOG;AACH,wBAAiB,4BAA4B,CACzC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,sBAAsB,GAC5B,YAAY,CAAC,IAAI,CAAC,CA2BpB;AAoCD;;;;;GAKG;AACH,wBAAiB,wCAAwC,CACrD,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,GAAG,SAAS,CAAC,EAClE,WAAW,EAAE,MAAM,GACpB,YAAY,CAAC,IAAI,CAAC,CAOpB;AAED;;;;;GAKG;AACH,wBAAiB,yBAAyB,CACtC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,iBAAiB,EACtB,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,sBAAsB,EAAE,GAAG,SAAS,GACnD,YAAY,CAAC,IAAI,CAAC,CASpB"}