@gooddata/sdk-ui-dashboard 11.62.0-alpha.2 → 11.62.0-alpha.3

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 (179) hide show
  1. package/NOTICE +33 -610
  2. package/esm/__version.d.ts +1 -1
  3. package/esm/__version.js +1 -1
  4. package/esm/_staging/dashboard/dashboardFilterContext.d.ts.map +1 -1
  5. package/esm/_staging/dashboard/dashboardFilterContext.js +4 -2
  6. package/esm/_staging/dashboard/dashboardFilterContextValidation.js +1 -1
  7. package/esm/index.d.ts +5 -3
  8. package/esm/index.d.ts.map +1 -1
  9. package/esm/index.js +1 -1
  10. package/esm/model/commandHandlers/dashboard/common/filterViews.d.ts +3 -2
  11. package/esm/model/commandHandlers/dashboard/common/filterViews.d.ts.map +1 -1
  12. package/esm/model/commandHandlers/dashboard/common/filterViews.js +12 -6
  13. package/esm/model/commandHandlers/dashboard/common/stateInitializers.d.ts.map +1 -1
  14. package/esm/model/commandHandlers/dashboard/common/stateInitializers.js +5 -6
  15. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/index.d.ts.map +1 -1
  16. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/index.js +1 -1
  17. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/loadUnavailableReferences.d.ts.map +1 -1
  18. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/loadUnavailableReferences.js +2 -0
  19. package/esm/model/commandHandlers/dashboard/resetDashboardHandler.d.ts.map +1 -1
  20. package/esm/model/commandHandlers/dashboard/resetDashboardHandler.js +3 -1
  21. package/esm/model/commandHandlers/dashboard/saveDashboardHandler.d.ts.map +1 -1
  22. package/esm/model/commandHandlers/dashboard/saveDashboardHandler.js +6 -5
  23. package/esm/model/commandHandlers/drill/drillToDashboardHandler.d.ts.map +1 -1
  24. package/esm/model/commandHandlers/drill/drillToDashboardHandler.js +4 -1
  25. package/esm/model/commandHandlers/drill/keyDriverAnalysisHandler.d.ts.map +1 -1
  26. package/esm/model/commandHandlers/drill/keyDriverAnalysisHandler.js +5 -3
  27. package/esm/model/commandHandlers/drill/resolveDrillToCustomUrl.d.ts +21 -1
  28. package/esm/model/commandHandlers/drill/resolveDrillToCustomUrl.d.ts.map +1 -1
  29. package/esm/model/commandHandlers/drill/resolveDrillToCustomUrl.js +56 -54
  30. package/esm/model/commandHandlers/filterContext/attributeFilter/changeAttributeFilterSelectionHandler.d.ts.map +1 -1
  31. package/esm/model/commandHandlers/filterContext/attributeFilter/changeAttributeFilterSelectionHandler.js +17 -4
  32. package/esm/model/commandHandlers/filterContext/attributeFilter/replaceAttributeFilterItemSelectionHandler.d.ts.map +1 -1
  33. package/esm/model/commandHandlers/filterContext/attributeFilter/replaceAttributeFilterItemSelectionHandler.js +6 -1
  34. package/esm/model/commandHandlers/filterContext/changeFilterContextSelectionHandler.d.ts.map +1 -1
  35. package/esm/model/commandHandlers/filterContext/changeFilterContextSelectionHandler.js +188 -70
  36. package/esm/model/commandHandlers/filterContext/common.d.ts +14 -2
  37. package/esm/model/commandHandlers/filterContext/common.d.ts.map +1 -1
  38. package/esm/model/commandHandlers/filterContext/common.js +43 -6
  39. package/esm/model/commandHandlers/filterContext/dateFilter/changeDateFilterSelectionHandler.d.ts.map +1 -1
  40. package/esm/model/commandHandlers/filterContext/dateFilter/changeDateFilterSelectionHandler.js +13 -3
  41. package/esm/model/commandHandlers/filterContext/measureValueFilter/changeMeasureValueFilterConditionHandler.d.ts.map +1 -1
  42. package/esm/model/commandHandlers/filterContext/measureValueFilter/changeMeasureValueFilterConditionHandler.js +11 -2
  43. package/esm/model/commandHandlers/filterContext/measureValueFilter/changeMeasureValueFilterDimensionalityHandler.d.ts.map +1 -1
  44. package/esm/model/commandHandlers/filterContext/measureValueFilter/changeMeasureValueFilterDimensionalityHandler.js +3 -0
  45. package/esm/model/commandHandlers/parameters/changeParameterValuesHandler.d.ts +10 -2
  46. package/esm/model/commandHandlers/parameters/changeParameterValuesHandler.d.ts.map +1 -1
  47. package/esm/model/commandHandlers/parameters/changeParameterValuesHandler.js +20 -3
  48. package/esm/model/commandHandlers/scheduledEmail/initializeAutomationsHandler.d.ts.map +1 -1
  49. package/esm/model/commandHandlers/scheduledEmail/initializeAutomationsHandler.js +21 -4
  50. package/esm/model/commandHandlers/tabs/createDashboardTabHandler.d.ts.map +1 -1
  51. package/esm/model/commandHandlers/tabs/createDashboardTabHandler.js +27 -3
  52. package/esm/model/commands/filters.d.ts +16 -1
  53. package/esm/model/commands/filters.d.ts.map +1 -1
  54. package/esm/model/commands/filters.js +4 -3
  55. package/esm/model/commands/parameters.d.ts +4 -3
  56. package/esm/model/commands/parameters.d.ts.map +1 -1
  57. package/esm/model/commands/parameters.js +3 -2
  58. package/esm/model/store/drill/drillSelectors.d.ts +6 -0
  59. package/esm/model/store/drill/drillSelectors.d.ts.map +1 -1
  60. package/esm/model/store/drill/drillSelectors.js +9 -0
  61. package/esm/model/store/filtering/dashboardFilterSelectors.d.ts +6 -1
  62. package/esm/model/store/filtering/dashboardFilterSelectors.d.ts.map +1 -1
  63. package/esm/model/store/filtering/dashboardFilterSelectors.js +21 -19
  64. package/esm/model/store/filtering/restrictedFilterUtils.d.ts +26 -1
  65. package/esm/model/store/filtering/restrictedFilterUtils.d.ts.map +1 -1
  66. package/esm/model/store/filtering/restrictedFilterUtils.js +74 -6
  67. package/esm/model/store/meta/metaSelectors.d.ts +6 -0
  68. package/esm/model/store/meta/metaSelectors.d.ts.map +1 -1
  69. package/esm/model/store/meta/metaSelectors.js +13 -3
  70. package/esm/model/store/tabs/filterContext/filterContextReducers.d.ts +48 -0
  71. package/esm/model/store/tabs/filterContext/filterContextReducers.d.ts.map +1 -1
  72. package/esm/model/store/tabs/filterContext/filterContextReducers.js +362 -102
  73. package/esm/model/store/tabs/filterContext/filterContextSelectors.d.ts +74 -1
  74. package/esm/model/store/tabs/filterContext/filterContextSelectors.d.ts.map +1 -1
  75. package/esm/model/store/tabs/filterContext/filterContextSelectors.js +27 -21
  76. package/esm/model/store/tabs/filterContext/filterSync.d.ts +101 -0
  77. package/esm/model/store/tabs/filterContext/filterSync.d.ts.map +1 -0
  78. package/esm/model/store/tabs/filterContext/filterSync.js +361 -0
  79. package/esm/model/store/tabs/index.d.ts +2 -0
  80. package/esm/model/store/tabs/index.d.ts.map +1 -1
  81. package/esm/model/store/tabs/parameters/parametersReducers.d.ts +5 -0
  82. package/esm/model/store/tabs/parameters/parametersReducers.d.ts.map +1 -1
  83. package/esm/model/store/tabs/parameters/parametersReducers.js +9 -5
  84. package/esm/model/store/tabs/tabsReducers.d.ts +6 -0
  85. package/esm/model/store/tabs/tabsReducers.d.ts.map +1 -1
  86. package/esm/model/store/tabs/tabsReducers.js +14 -6
  87. package/esm/model/store/tabs/tabsState.d.ts +10 -0
  88. package/esm/model/store/tabs/tabsState.d.ts.map +1 -1
  89. package/esm/model/store/tabs/tabsState.js +20 -0
  90. package/esm/model/store/unavailableObjects/copyBlockingReferences.d.ts +5 -3
  91. package/esm/model/store/unavailableObjects/copyBlockingReferences.d.ts.map +1 -1
  92. package/esm/model/store/unavailableObjects/copyBlockingReferences.js +9 -16
  93. package/esm/model/store/unavailableObjects/unavailableObjectsSelectors.d.ts +0 -16
  94. package/esm/model/store/unavailableObjects/unavailableObjectsSelectors.d.ts.map +1 -1
  95. package/esm/model/store/unavailableObjects/unavailableObjectsSelectors.js +0 -18
  96. package/esm/model/utils/attributeFilterMatching.d.ts +38 -0
  97. package/esm/model/utils/attributeFilterMatching.d.ts.map +1 -0
  98. package/esm/model/utils/attributeFilterMatching.js +110 -0
  99. package/esm/model/utils/attributeFilterSelectionTypeCompatibility.d.ts.map +1 -0
  100. package/esm/presentation/automations/alerting/state/types.d.ts +2 -2
  101. package/esm/presentation/automations/alerting/state/types.d.ts.map +1 -1
  102. package/esm/presentation/automations/connectors/hooks/useBuildAutomationsContext.d.ts.map +1 -1
  103. package/esm/presentation/automations/connectors/hooks/useBuildAutomationsContext.js +7 -3
  104. package/esm/presentation/automations/contexts/AutomationsContext.d.ts +6 -2
  105. package/esm/presentation/automations/contexts/AutomationsContext.d.ts.map +1 -1
  106. package/esm/presentation/automations/scheduledEmail/state/types.d.ts +2 -2
  107. package/esm/presentation/automations/scheduledEmail/state/types.d.ts.map +1 -1
  108. package/esm/presentation/automations/scheduledEmail/types.d.ts +2 -2
  109. package/esm/presentation/automations/scheduledEmail/types.d.ts.map +1 -1
  110. package/esm/presentation/automations/shared/automationFilters/automationParameters.d.ts +11 -1
  111. package/esm/presentation/automations/shared/automationFilters/automationParameters.d.ts.map +1 -1
  112. package/esm/presentation/automations/shared/automationFilters/automationParameters.js +2 -0
  113. package/esm/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.d.ts +3 -3
  114. package/esm/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.d.ts.map +1 -1
  115. package/esm/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.js +3 -13
  116. package/esm/presentation/automations/shared/automationFilters/hooks/useDefaultSelectedFiltersForExistingAutomation.d.ts +1 -1
  117. package/esm/presentation/automations/shared/automationFilters/hooks/useDefaultSelectedFiltersForExistingAutomation.d.ts.map +1 -1
  118. package/esm/presentation/automations/shared/automationFilters/hooks/useDefaultSelectedFiltersForExistingAutomation.js +1 -1
  119. package/esm/presentation/automations/shared/automationFilters/hooks/useValidateExistingAutomationFilters.d.ts.map +1 -1
  120. package/esm/presentation/automations/shared/automationFilters/hooks/useValidateExistingAutomationFilters.js +1 -1
  121. package/esm/presentation/automations/shared/automationFilters/useAutomationAlertParameters.d.ts +2 -2
  122. package/esm/presentation/automations/shared/automationFilters/useAutomationAlertParameters.d.ts.map +1 -1
  123. package/esm/presentation/automations/shared/automationFilters/useAutomationExportParameters.d.ts +6 -2
  124. package/esm/presentation/automations/shared/automationFilters/useAutomationExportParameters.d.ts.map +1 -1
  125. package/esm/presentation/automations/shared/automationFilters/useAutomationFilters.d.ts.map +1 -1
  126. package/esm/presentation/automations/shared/automationFilters/useAutomationFilters.js +2 -2
  127. package/esm/presentation/automations/shared/automationFilters/utils.d.ts.map +1 -1
  128. package/esm/presentation/automations/shared/automationFilters/utils.js +8 -0
  129. package/esm/presentation/automations/shared/filters/conversions.d.ts +1 -1
  130. package/esm/presentation/automations/shared/filters/conversions.d.ts.map +1 -1
  131. package/esm/presentation/automations/shared/filters/conversions.js +5 -3
  132. package/esm/presentation/automations/shared/slots/types.d.ts +2 -2
  133. package/esm/presentation/automations/shared/slots/types.d.ts.map +1 -1
  134. package/esm/presentation/dashboardSettingsDialog/useDialogData.js +1 -1
  135. package/esm/presentation/filterBar/attributeFilter/DefaultDashboardAttributeFilter.d.ts.map +1 -1
  136. package/esm/presentation/filterBar/attributeFilter/DefaultDashboardAttributeFilter.js +1 -4
  137. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/AttributesDropdown.d.ts +2 -11
  138. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/AttributesDropdown.d.ts.map +1 -1
  139. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/AttributesDropdown.js +17 -16
  140. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/staticListItems.d.ts +11 -0
  141. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/staticListItems.d.ts.map +1 -0
  142. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/staticListItems.js +7 -0
  143. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/useMetricDropdownItems.d.ts +2 -11
  144. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/useMetricDropdownItems.d.ts.map +1 -1
  145. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/useMetricDropdownItems.js +0 -6
  146. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/useParameterDropdownItems.d.ts +34 -0
  147. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/useParameterDropdownItems.d.ts.map +1 -0
  148. package/esm/presentation/filterBar/attributeFilter/addAttributeFilter/useParameterDropdownItems.js +41 -0
  149. package/esm/presentation/filterBar/attributeFilter/dashboardDropdownBody/configuration/hooks/useLimitingItemsConfiguration.d.ts.map +1 -1
  150. package/esm/presentation/filterBar/attributeFilter/dashboardDropdownBody/configuration/hooks/useLimitingItemsConfiguration.js +4 -17
  151. package/esm/presentation/filterBar/attributeFilter/useDependentDateFilters.d.ts.map +1 -1
  152. package/esm/presentation/filterBar/attributeFilter/useDependentDateFilters.js +4 -13
  153. package/esm/presentation/filterBar/attributeFilter/useParentFilters.d.ts.map +1 -1
  154. package/esm/presentation/filterBar/attributeFilter/useParentFilters.js +3 -10
  155. package/esm/presentation/filterBar/filterBar/DefaultDashboardFilterGroup.d.ts.map +1 -1
  156. package/esm/presentation/filterBar/filterBar/DefaultDashboardFilterGroup.js +0 -9
  157. package/esm/presentation/filterBar/filterBar/DefaultFilterBarItem.d.ts.map +1 -1
  158. package/esm/presentation/filterBar/filterBar/DefaultFilterBarItem.js +15 -3
  159. package/esm/presentation/filterBar/filterBar/hooks/useResetFiltersButton.d.ts.map +1 -1
  160. package/esm/presentation/filterBar/filterBar/hooks/useResetFiltersButton.js +4 -0
  161. package/esm/presentation/localization/bundles/en-US.localization-bundle.d.ts +8 -0
  162. package/esm/presentation/localization/bundles/en-US.localization-bundle.d.ts.map +1 -1
  163. package/esm/presentation/localization/bundles/en-US.localization-bundle.js +8 -0
  164. package/esm/presentation/widget/insight/configuration/DrillFilters/drillFiltersConfigUtils.d.ts +2 -3
  165. package/esm/presentation/widget/insight/configuration/DrillFilters/drillFiltersConfigUtils.d.ts.map +1 -1
  166. package/esm/presentation/widget/insight/configuration/DrillFilters/drillFiltersConfigUtils.js +29 -133
  167. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapDashboardFilterToOption.d.ts.map +1 -1
  168. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapDashboardFilterToOption.js +1 -1
  169. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapSourceInsightFilterToOption.d.ts.map +1 -1
  170. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapSourceInsightFilterToOption.js +1 -1
  171. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapSourceMeasureFilterToOption.d.ts.map +1 -1
  172. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapSourceMeasureFilterToOption.js +1 -1
  173. package/esm/sdk-ui-dashboard.d.ts +135 -13
  174. package/package.json +23 -23
  175. package/styles/css/main.css +4 -0
  176. package/styles/css/main.css.map +1 -1
  177. package/esm/model/commandHandlers/dashboard/common/attributeFilterSelectionTypeCompatibility.d.ts.map +0 -1
  178. /package/esm/model/{commandHandlers/dashboard/common → utils}/attributeFilterSelectionTypeCompatibility.d.ts +0 -0
  179. /package/esm/model/{commandHandlers/dashboard/common → utils}/attributeFilterSelectionTypeCompatibility.js +0 -0
@@ -3,53 +3,100 @@ import { compact, partition, uniqBy } from "lodash-es";
3
3
  import { batchActions } from "redux-batched-actions";
4
4
  import { all, call, put, select } from "redux-saga/effects";
5
5
  import { NotSupported } from "@gooddata/sdk-backend-spi";
6
- import { areObjRefsEqual, attributeElementsIsEmpty, dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemFilterElementsBy, dashboardAttributeFilterItemFilterElementsByDate, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemValidateElementsBy, dashboardFilterObjRef, dashboardMeasureValueFilterLocalIdentifier, getAttributeElementsItems, isAllTimeDashboardDateFilter, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMeasureValueFilter, isDashboardTextAttributeFilter, isSingleSelectionFilter, isUriRef, objRefToString, serializeObjRef, updateAttributeElementsItems, } from "@gooddata/sdk-model";
6
+ 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";
7
7
  import { dashboardFilterToFilterContextItem } from "../../../_staging/dashboard/dashboardFilterContext.js";
8
8
  import { invalidArgumentsProvided } from "../../events/general.js";
9
9
  import { dispatchDashboardEvent } from "../../store/_infra/eventDispatcher.js";
10
- import { isDashboardFilterRestricted } from "../../store/filtering/restrictedFilterUtils.js";
10
+ import { selectCrossFilteringFiltersLocalIdentifiersByTab } from "../../store/drill/drillSelectors.js";
11
+ import { selectRestrictedDashboardFilterLocalIdentifiers, selectRestrictedDashboardFilterLocalIdentifiersForTab, } from "../../store/filtering/dashboardFilterSelectors.js";
12
+ import { isDashboardFilterUsingRestrictedObject } from "../../store/filtering/restrictedFilterUtils.js";
13
+ import { selectEnablePersistentFiltersAcrossTabs } from "../../store/meta/metaSelectors.js";
11
14
  import { selectAttributeFilterConfigsOverrides, selectAttributeFilterConfigsOverridesByTab, selectAttributeFilterConfigsSelectionTypeMap, selectAttributeFilterConfigsSelectionTypeMapByTab, } from "../../store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
12
- import { selectAttributeFilterDisplayFormsMap, selectFilterContextAttributeFilterByDisplayForm, selectFilterContextAttributeFilterByDisplayFormForTab, selectFilterContextAttributeFilterByLocalId, selectFilterContextAttributeFilterByLocalIdForTab, selectFilterContextAttributeFilterItemByDisplayForm, selectFilterContextAttributeFilterItemByDisplayFormForTab, selectFilterContextAttributeFilterItemByLocalId, selectFilterContextAttributeFilterItemByLocalIdForTab, selectFilterContextAttributeFilterItems, selectFilterContextAttributeFilterItemsForTab, selectFilterContextAttributeFilters, selectFilterContextAttributeFiltersForTab, selectFilterContextDateFilterByDataSet, selectFilterContextDateFilterByDataSetForTab, selectFilterContextDateFiltersWithDimension, selectFilterContextDateFiltersWithDimensionForTab, selectFilterContextMeasureValueFilters, } from "../../store/tabs/filterContext/filterContextSelectors.js";
15
+ import { isDisplayFormRefOf, selectAttributeFilterDisplayFormsMap, selectFilterContextAttributeFilterItems, selectFilterContextAttributeFilterItemsForTab, selectFilterContextDateFilterByDataSet, selectFilterContextDateFilterByDataSetForTab, selectFilterContextDateFiltersWithDimension, selectFilterContextDateFiltersWithDimensionForTab, selectFilterContextFilters, selectFilterContextMeasureValueFilters, selectFiltersForTab, } from "../../store/tabs/filterContext/filterContextSelectors.js";
13
16
  import { tabsActions } from "../../store/tabs/index.js";
14
17
  import { selectTabs } from "../../store/tabs/tabsSelectors.js";
15
18
  import { selectUnavailableObjects } from "../../store/unavailableObjects/unavailableObjectsSelectors.js";
19
+ import { canApplyFilterTypeToTarget } from "../../utils/attributeFilterSelectionTypeCompatibility.js";
16
20
  import { resolveAttributeMetadata } from "../../utils/attributeResolver.js";
17
21
  import { resolveDisplayFormMetadata, } from "../../utils/displayFormResolver.js";
18
- import { canApplyFilterTypeToTarget } from "../dashboard/common/attributeFilterSelectionTypeCompatibility.js";
19
22
  import { resolveAndRegisterDisplayFormMetadata } from "./attributeFilter/resolveDisplayFormMetadata.js";
20
23
  import { computedAttributesDisabledMessage, findDisabledComputedAttributeRef, } from "./attributeFilter/validation/computedAttributesEnabledValidation.js";
21
- import { canApplyDateFilter, dispatchFilterContextChanged } from "./common.js";
24
+ import { canApplyDateFilter, captureFilterContextByTabSnapshot, dispatchFilterContextChanged, dispatchFilterContextChangedForMutatedTabs, } from "./common.js";
25
+ function classifyRestore(type, restoresOriginal) {
26
+ // A computed key is not inferred as a single-property mapped type.
27
+ return { [type]: restoresOriginal };
28
+ }
29
+ const RESTORE_TARGET_ORIGINAL = {
30
+ ...classifyRestore(tabsActions.updateAttributeFilterSelection.type, true),
31
+ ...classifyRestore(tabsActions.replaceAttributeFilterItem.type, true),
32
+ ...classifyRestore(tabsActions.clearAttributeFiltersSelection.type, true),
33
+ ...classifyRestore(tabsActions.upsertDateFilter.type, true),
34
+ ...classifyRestore(tabsActions.changeMeasureValueFilterCondition.type, true),
35
+ ...classifyRestore(tabsActions.setMeasureValueFilterDimensionality.type, true),
36
+ ...classifyRestore(tabsActions.addAttributeFilterDisplayForm.type, false),
37
+ ...classifyRestore(tabsActions.changeAttributeDisplayForm.type, false),
38
+ ...classifyRestore(tabsActions.changeDisplayAsLabel.type, false),
39
+ };
40
+ /**
41
+ * Reset persists one payload (the active tab's saved values). Mark the value-writing actions so
42
+ * other tabs restore their own originals instead of receiving that payload.
43
+ */
44
+ function markRestoreTargetOriginal(actions, enabled) {
45
+ if (!enabled) {
46
+ return actions;
47
+ }
48
+ for (const action of actions) {
49
+ if (RESTORE_TARGET_ORIGINAL[action.type]) {
50
+ action.payload.restoreTargetOriginal = true;
51
+ }
52
+ }
53
+ return actions;
54
+ }
22
55
  // Tab-aware select helpers — encapsulate the "if tab select ForTab else select" pattern.
23
56
  // Named after the base selector they wrap.
24
- function* selectFilterContextAttributeFilterByDisplayFormTabAware(displayForm, tabLocalIdentifier) {
57
+ //
58
+ // The attribute filter helpers see readable filters only. A restricted filter keeps its stored selection,
59
+ // so matching passes it over to a readable filter with the same label and resetOthers leaves it alone;
60
+ // an entry that addresses it exactly is dropped before matching.
61
+ function* selectRestrictedDashboardFilterLocalIdentifiersTabAware(tabLocalIdentifier) {
25
62
  return tabLocalIdentifier
26
- ? yield select(selectFilterContextAttributeFilterByDisplayFormForTab(displayForm, tabLocalIdentifier))
27
- : yield select(selectFilterContextAttributeFilterByDisplayForm(displayForm));
63
+ ? yield select(selectRestrictedDashboardFilterLocalIdentifiersForTab(tabLocalIdentifier))
64
+ : yield select(selectRestrictedDashboardFilterLocalIdentifiers);
28
65
  }
29
- function* selectFilterContextAttributeFilterItemByLocalIdTabAware(localId, tabLocalIdentifier) {
30
- return tabLocalIdentifier
31
- ? yield select(selectFilterContextAttributeFilterItemByLocalIdForTab(localId, tabLocalIdentifier))
32
- : yield select(selectFilterContextAttributeFilterItemByLocalId(localId));
66
+ function* selectFilterContextAttributeFilterItemsTabAware(tabLocalIdentifier) {
67
+ const items = tabLocalIdentifier
68
+ ? yield select(selectFilterContextAttributeFilterItemsForTab(tabLocalIdentifier))
69
+ : yield select(selectFilterContextAttributeFilterItems);
70
+ const restrictedLocalIdentifiers = yield call(selectRestrictedDashboardFilterLocalIdentifiersTabAware, tabLocalIdentifier);
71
+ return items.filter((item) => !restrictedLocalIdentifiers.has(dashboardAttributeFilterItemLocalIdentifier(item) ?? ""));
33
72
  }
34
- function* selectFilterContextAttributeFilterItemByDisplayFormTabAware(displayForm, tabLocalIdentifier) {
35
- return tabLocalIdentifier
36
- ? yield select(selectFilterContextAttributeFilterItemByDisplayFormForTab(displayForm, tabLocalIdentifier))
37
- : yield select(selectFilterContextAttributeFilterItemByDisplayForm(displayForm));
73
+ function* selectFilterContextAttributeFiltersTabAware(tabLocalIdentifier) {
74
+ const items = yield call(selectFilterContextAttributeFilterItemsTabAware, tabLocalIdentifier);
75
+ return items.filter(isDashboardAttributeFilter);
76
+ }
77
+ function* selectFilterContextAttributeFilterItemByLocalIdTabAware(localId, tabLocalIdentifier) {
78
+ const items = yield call(selectFilterContextAttributeFilterItemsTabAware, tabLocalIdentifier);
79
+ return items.find((item) => dashboardAttributeFilterItemLocalIdentifier(item) === localId);
38
80
  }
39
81
  function* selectFilterContextAttributeFilterByLocalIdTabAware(localId, tabLocalIdentifier) {
40
- return tabLocalIdentifier
41
- ? yield select(selectFilterContextAttributeFilterByLocalIdForTab(localId, tabLocalIdentifier))
42
- : yield select(selectFilterContextAttributeFilterByLocalId(localId));
82
+ const filters = yield call(selectFilterContextAttributeFiltersTabAware, tabLocalIdentifier);
83
+ return filters.find((filter) => filter.attributeFilter.localIdentifier === localId);
43
84
  }
44
- function* selectFilterContextAttributeFiltersTabAware(tabLocalIdentifier) {
45
- return tabLocalIdentifier
46
- ? yield select(selectFilterContextAttributeFiltersForTab(tabLocalIdentifier))
47
- : yield select(selectFilterContextAttributeFilters);
85
+ function* selectFilterContextAttributeFilterItemByDisplayFormTabAware(displayForm, tabLocalIdentifier) {
86
+ const items = yield call(selectFilterContextAttributeFilterItemsTabAware, tabLocalIdentifier);
87
+ const displayFormsMap = yield select(selectAttributeFilterDisplayFormsMap);
88
+ const df = displayFormsMap.get(displayForm);
89
+ return df
90
+ ? items.find((item) => isDisplayFormRefOf(dashboardAttributeFilterItemDisplayForm(item), df))
91
+ : undefined;
48
92
  }
49
- function* selectFilterContextAttributeFilterItemsTabAware(tabLocalIdentifier) {
50
- return tabLocalIdentifier
51
- ? yield select(selectFilterContextAttributeFilterItemsForTab(tabLocalIdentifier))
52
- : yield select(selectFilterContextAttributeFilterItems);
93
+ function* selectFilterContextAttributeFilterByDisplayFormTabAware(displayForm, tabLocalIdentifier) {
94
+ const filters = yield call(selectFilterContextAttributeFiltersTabAware, tabLocalIdentifier);
95
+ const displayFormsMap = yield select(selectAttributeFilterDisplayFormsMap);
96
+ const df = displayFormsMap.get(displayForm);
97
+ return df
98
+ ? filters.find((filter) => isDisplayFormRefOf(filter.attributeFilter.displayForm, df))
99
+ : undefined;
53
100
  }
54
101
  function* selectFilterContextDateFilterByDataSetTabAware(dataSet, tabLocalIdentifier) {
55
102
  return tabLocalIdentifier
@@ -69,7 +116,7 @@ function* selectAttributeFilterConfigsSelectionTypeMapTabAware(tabLocalIdentifie
69
116
  return yield select(selectAttributeFilterConfigsSelectionTypeMap);
70
117
  }
71
118
  export function* changeFilterContextSelectionHandler(ctx, cmd) {
72
- const { filters, resetOthers, attributeFilterConfigs = [], tabLocalIdentifier, matchByLocalIdentifier = false, } = cmd.payload;
119
+ const { filters, resetOthers, attributeFilterConfigs = [], tabLocalIdentifier, matchByLocalIdentifier = false, restorePerTabOriginals = false, } = cmd.payload;
73
120
  // Validate that the target tab exists if tabLocalIdentifier is provided
74
121
  if (tabLocalIdentifier) {
75
122
  const tabs = yield select(selectTabs);
@@ -82,8 +129,32 @@ export function* changeFilterContextSelectionHandler(ctx, cmd) {
82
129
  // Cross-filtering is always compatible with dashboard tabs now
83
130
  // (removed the check that prevented cross-filtering without tabs)
84
131
  const unavailableObjects = yield select(selectUnavailableObjects);
85
- // A restricted attribute filter keeps its stored selection: its label is never resolved, so the
86
- // filter can be neither matched nor changed.
132
+ // A restricted filter keeps its stored selection. An entry that uses a restricted object is dropped:
133
+ // a restricted label is never resolved, and a restricted "For attribute" would restrict the measure
134
+ // value filter it is applied to. With matchByLocalIdentifier, an entry that addresses a restricted
135
+ // filter exactly is dropped too; a list entry addresses a filter exactly only with an equivalent
136
+ // display form, as its element values are valid for one display form only.
137
+ const restrictedLocalIdentifiers = yield call(selectRestrictedDashboardFilterLocalIdentifiersTabAware, tabLocalIdentifier);
138
+ const tabFilters = tabLocalIdentifier
139
+ ? yield select(selectFiltersForTab(tabLocalIdentifier))
140
+ : yield select(selectFilterContextFilters);
141
+ const displayFormsMap = yield select(selectAttributeFilterDisplayFormsMap);
142
+ const addressesRestrictedFilter = (entry) => {
143
+ const localIdentifier = dashboardFilterLocalIdentifier(entry);
144
+ const target = localIdentifier && restrictedLocalIdentifiers.has(localIdentifier)
145
+ ? tabFilters.find((filter) => dashboardFilterLocalIdentifier(filter) === localIdentifier)
146
+ : undefined;
147
+ if (isDashboardAttributeFilter(entry)) {
148
+ const df = displayFormsMap.get(entry.attributeFilter.displayForm);
149
+ return (!!df &&
150
+ isDashboardAttributeFilter(target) &&
151
+ isDisplayFormRefOf(target.attributeFilter.displayForm, df));
152
+ }
153
+ if (isDashboardAttributeFilterItem(entry)) {
154
+ return isDashboardAttributeFilterItem(target);
155
+ }
156
+ return isDashboardMeasureValueFilter(entry) && isDashboardMeasureValueFilter(target);
157
+ };
87
158
  const normalizedFilters = compact(filters.map((filter) => {
88
159
  if (isDashboardAttributeFilterItem(filter) ||
89
160
  isDashboardDateFilter(filter) ||
@@ -91,8 +162,8 @@ export function* changeFilterContextSelectionHandler(ctx, cmd) {
91
162
  return filter;
92
163
  }
93
164
  return dashboardFilterToFilterContextItem(filter, !!ctx.backend.capabilities.supportsMultipleDateFilters);
94
- })).filter((filter) => !(isDashboardAttributeFilterItem(filter) &&
95
- isDashboardFilterRestricted(filter, unavailableObjects)));
165
+ })).filter((filter) => !((isDashboardAttributeFilterItem(filter) || isDashboardMeasureValueFilter(filter)) &&
166
+ isDashboardFilterUsingRestrictedObject(filter, unavailableObjects)) && !(matchByLocalIdentifier && addressesRestrictedFilter(filter)));
96
167
  const disabledComputedAttributeRef = yield call(findDisabledComputedAttributeRef, [
97
168
  ...normalizedFilters
98
169
  .filter(isDashboardAttributeFilterItem)
@@ -140,11 +211,13 @@ export function* changeFilterContextSelectionHandler(ctx, cmd) {
140
211
  const [[commonDateFilter], dateFiltersWithDimension] = partition(dateFilters, isDashboardCommonDateFilter);
141
212
  const selectionTypeMap = yield call(selectAttributeFilterConfigsSelectionTypeMapTabAware, tabLocalIdentifier);
142
213
  const [attributeFilterResult, commonDateFilterUpdateActions, dateFiltersUpdateActions, measureValueFilterUpdateActions,] = yield all([
143
- call(getAttributeFiltersUpdateActions, [...attributeFilters].reverse(), textAttributeFilters, attributeFilterConfigs, resetOthers, unavailableObjects, ctx, tabLocalIdentifier, selectionTypeMap, matchByLocalIdentifier),
214
+ call(getAttributeFiltersUpdateActions, [...attributeFilters].reverse(), textAttributeFilters, attributeFilterConfigs, resetOthers, ctx, tabLocalIdentifier, selectionTypeMap, matchByLocalIdentifier),
144
215
  call(getDateFilterUpdateActions, commonDateFilter, resetOthers, tabLocalIdentifier),
145
216
  call(getDateFiltersUpdateActions, dateFiltersWithDimension, resetOthers, tabLocalIdentifier),
146
217
  call(getMeasureValueFiltersUpdateActions, measureValueFilters, resetOthers),
147
218
  ]);
219
+ const enablePersistentFiltersAcrossTabs = yield select(selectEnablePersistentFiltersAcrossTabs);
220
+ const filterContextSnapshot = yield call(captureFilterContextByTabSnapshot);
148
221
  // Ensure display form metadata is loaded for filters that will actually be applied.
149
222
  // This handles the case where a default saved view changed filter types (e.g., list→text)
150
223
  // and then local storage restores original types whose display forms weren't loaded during init.
@@ -156,13 +229,18 @@ export function* changeFilterContextSelectionHandler(ctx, cmd) {
156
229
  }
157
230
  }
158
231
  }
159
- yield put(batchActions([
232
+ yield put(batchActions(markRestoreTargetOriginal([
160
233
  ...attributeFilterResult.actions,
161
234
  ...commonDateFilterUpdateActions,
162
235
  ...dateFiltersUpdateActions,
163
236
  ...measureValueFilterUpdateActions,
164
- ]));
165
- yield call(dispatchFilterContextChanged, ctx, cmd, tabLocalIdentifier);
237
+ ], restorePerTabOriginals)));
238
+ if (enablePersistentFiltersAcrossTabs) {
239
+ yield call(dispatchFilterContextChangedForMutatedTabs, ctx, cmd, filterContextSnapshot);
240
+ }
241
+ else {
242
+ yield call(dispatchFilterContextChanged, ctx, cmd, tabLocalIdentifier);
243
+ }
166
244
  }
167
245
  function* getDashboardFilterByAttributeMatching(filterRef, resolvedDisplayForms, ctx, tabLocalIdentifier, isFilterExcluded, areDisplayFormsEquivalent = areObjRefsEqual) {
168
246
  if (isUriRef(filterRef) && !ctx.backend.capabilities.supportsObjectUris) {
@@ -203,11 +281,15 @@ function* getDashboardFilterByDisplayAsLabelMatching(attributeFilter, attributeF
203
281
  const dashboardFiltersConfigs = tabLocalIdentifier
204
282
  ? dashboardFiltersConfigsByTab[tabLocalIdentifier]
205
283
  : dashboardFiltersConfigsForActiveTab;
206
- const matchingDashboardFilterConfig = dashboardFiltersConfigs.find((config) => areObjRefsEqual(config.displayAsLabel, filterRef));
207
- if (matchingDashboardFilterConfig) {
208
- dashboardFilter = yield call(selectFilterContextAttributeFilterByLocalIdTabAware, matchingDashboardFilterConfig?.localIdentifier, tabLocalIdentifier);
209
- foundByDashboardFilterDisplayAsLabel = !!dashboardFilter;
284
+ // the config of a restricted filter may come first, its filter is not found
285
+ const matchingDashboardFilterConfigs = dashboardFiltersConfigs.filter((config) => areObjRefsEqual(config.displayAsLabel, filterRef));
286
+ for (const config of matchingDashboardFilterConfigs) {
287
+ dashboardFilter = yield call(selectFilterContextAttributeFilterByLocalIdTabAware, config.localIdentifier, tabLocalIdentifier);
288
+ if (dashboardFilter) {
289
+ break;
290
+ }
210
291
  }
292
+ foundByDashboardFilterDisplayAsLabel = !!dashboardFilter;
211
293
  }
212
294
  return { foundByDisplayAsLabel, foundByDashboardFilterDisplayAsLabel, dashboardFilter };
213
295
  }
@@ -229,17 +311,24 @@ function* findTargetFilterItemByDisplayForm(displayFormRef, tabLocalIdentifier)
229
311
  const dashboardFiltersConfigs = tabLocalIdentifier
230
312
  ? dashboardFiltersConfigsByTab[tabLocalIdentifier]
231
313
  : dashboardFiltersConfigsForActiveTab;
232
- const matchingConfig = dashboardFiltersConfigs?.find((config) => areObjRefsEqual(config.displayAsLabel, displayFormRef));
233
- if (matchingConfig) {
234
- return yield call(selectFilterContextAttributeFilterItemByLocalIdTabAware, matchingConfig.localIdentifier, tabLocalIdentifier);
314
+ // the config of a restricted filter may come first, its filter is not found
315
+ const matchingConfigs = dashboardFiltersConfigs?.filter((config) => areObjRefsEqual(config.displayAsLabel, displayFormRef)) ??
316
+ [];
317
+ for (const config of matchingConfigs) {
318
+ const target = yield call(selectFilterContextAttributeFilterItemByLocalIdTabAware, config.localIdentifier, tabLocalIdentifier);
319
+ if (target) {
320
+ return target;
321
+ }
235
322
  }
236
323
  return undefined;
237
324
  }
238
- function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilters, attributeFilterConfigs, resetOthers, unavailableObjects, ctx, tabLocalIdentifier, selectionTypeMap, matchByLocalIdentifier) {
325
+ function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilters, attributeFilterConfigs, resetOthers, ctx, tabLocalIdentifier, selectionTypeMap, matchByLocalIdentifier) {
239
326
  const updateActions = [];
240
327
  const displayFormsToResolve = [];
241
328
  const handledLocalIds = new Set();
329
+ const crossFilteringLocalIdsByTab = yield select(selectCrossFilteringFiltersLocalIdentifiersByTab);
242
330
  const resolvedDisplayForms = yield call(resolveDisplayFormMetadata, ctx, attributeFilters.map((af) => dashboardAttributeFilterItemDisplayForm(af)));
331
+ const enablePersistentFiltersAcrossTabs = yield select(selectEnablePersistentFiltersAcrossTabs);
243
332
  // A payload can give a display form as a URI ref, and the dashboard can store it as an
244
333
  // identifier ref. The two refs are equal when they point to the same display form metadata.
245
334
  const displayFormsMap = yield select(selectAttributeFilterDisplayFormsMap);
@@ -394,6 +483,8 @@ function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilter
394
483
  updateActions.push(tabsActions.updateAttributeFilterSelection({
395
484
  ...getAttributeFilterSelectionPayload(attributeFilter, dashboardFilter),
396
485
  tabLocalIdentifier,
486
+ crossFilteringLocalIdsByTab,
487
+ enablePersistentFiltersAcrossTabs,
397
488
  }));
398
489
  handledLocalIds.add(dashboardFilterLocalId);
399
490
  }
@@ -431,20 +522,15 @@ function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilter
431
522
  if (canApplyFilterTypeToTarget("list", configSelectionType, targetTextFilter)) {
432
523
  // Text → list: set displayAsLabel to the text filter's displayForm
433
524
  // so the list filter visually shows the same label.
434
- // Normalize: use target's localIdentifier so attributeFilterConfigs
435
- // (keyed by localId) keep matching, and strip selectionMode so the
436
- // target's own config determines single/multi presentation.
437
- const normalizedListFilter = {
438
- attributeFilter: {
439
- ...attributeFilter.attributeFilter,
440
- localIdentifier: targetLocalId,
441
- selectionMode: undefined,
442
- },
443
- };
525
+ // Strip selectionMode so the target's own config determines single/multi
526
+ // presentation.
527
+ const normalizedListFilter = fitReplacementToTarget({ attributeFilter: { ...attributeFilter.attributeFilter, selectionMode: undefined } }, targetTextFilter);
444
528
  updateActions.push(tabsActions.replaceAttributeFilterItem({
445
529
  filterLocalId: targetLocalId,
446
530
  filter: normalizedListFilter,
447
531
  tabLocalIdentifier,
532
+ crossFilteringLocalIdsByTab,
533
+ enablePersistentFiltersAcrossTabs,
448
534
  }), tabsActions.changeDisplayAsLabel({
449
535
  localIdentifier: targetLocalId,
450
536
  displayAsLabel: dashboardAttributeFilterItemDisplayForm(targetTextFilter),
@@ -497,13 +583,13 @@ function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilter
497
583
  continue;
498
584
  }
499
585
  }
500
- // Normalize: use target's localIdentifier so attributeFilterConfigs
501
- // (keyed by localId) keep matching after the replacement.
502
- const normalizedTextFilter = normalizeFilterLocalIdentifier(textFilter, targetLocalId);
586
+ const normalizedTextFilter = fitReplacementToTarget(textFilter, existingFilter);
503
587
  updateActions.push(tabsActions.replaceAttributeFilterItem({
504
588
  filterLocalId: targetLocalId,
505
589
  filter: normalizedTextFilter,
506
590
  tabLocalIdentifier,
591
+ crossFilteringLocalIdsByTab,
592
+ enablePersistentFiltersAcrossTabs,
507
593
  }));
508
594
  const textDisplayForm = dashboardAttributeFilterItemDisplayForm(textFilter);
509
595
  // List → text: update displayAsLabel to match the text filter's displayForm
@@ -522,20 +608,20 @@ function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilter
522
608
  if (resetOthers) {
523
609
  const currentAttributeFilters = yield call(selectFilterContextAttributeFiltersTabAware, tabLocalIdentifier);
524
610
  // for element-based filters that have not been handled, create clear selection actions
525
- const unhandledFilters = currentAttributeFilters.filter((filter) => !handledLocalIds.has(dashboardAttributeFilterItemLocalIdentifier(filter)) &&
526
- !isDashboardFilterRestricted(filter, unavailableObjects));
611
+ const unhandledFilters = currentAttributeFilters.filter((filter) => !handledLocalIds.has(dashboardAttributeFilterItemLocalIdentifier(filter)));
527
612
  if (unhandledFilters.length > 0) {
528
613
  updateActions.push(tabsActions.clearAttributeFiltersSelection({
529
614
  filterLocalIds: unhandledFilters.map((filter) => dashboardAttributeFilterItemLocalIdentifier(filter)),
530
615
  tabLocalIdentifier,
616
+ crossFilteringLocalIdsByTab,
617
+ enablePersistentFiltersAcrossTabs,
531
618
  }));
532
619
  }
533
620
  // for text filters (arbitrary/match) that have not been handled, reset to All state
534
621
  // (negative arbitrary filter with empty selection)
535
622
  const currentAllFilterItems = yield call(selectFilterContextAttributeFilterItemsTabAware, tabLocalIdentifier);
536
623
  const unhandledTextFilters = currentAllFilterItems.filter((filter) => isDashboardTextAttributeFilter(filter) &&
537
- !handledLocalIds.has(dashboardAttributeFilterItemLocalIdentifier(filter)) &&
538
- !isDashboardFilterRestricted(filter, unavailableObjects));
624
+ !handledLocalIds.has(dashboardAttributeFilterItemLocalIdentifier(filter)));
539
625
  for (const textFilter of unhandledTextFilters) {
540
626
  const localId = dashboardAttributeFilterItemLocalIdentifier(textFilter);
541
627
  const resetFilter = {
@@ -553,12 +639,15 @@ function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilter
553
639
  filterLocalId: localId,
554
640
  filter: resetFilter,
555
641
  tabLocalIdentifier,
642
+ crossFilteringLocalIdsByTab,
643
+ enablePersistentFiltersAcrossTabs,
556
644
  }));
557
645
  }
558
646
  }
559
647
  return { actions: updateActions, displayFormsToResolve };
560
648
  }
561
649
  function* getDateFilterUpdateActions(dateFilter, resetOthers, tabLocalIdentifier) {
650
+ const enablePersistentFiltersAcrossTabs = yield select(selectEnablePersistentFiltersAcrossTabs);
562
651
  if (dateFilter) {
563
652
  const canApply = yield call(canApplyDateFilter, dateFilter);
564
653
  if (!canApply) {
@@ -577,6 +666,7 @@ function* getDateFilterUpdateActions(dateFilter, resetOthers, tabLocalIdentifier
577
666
  ...localIdentifierObj,
578
667
  ...emptyValueHandlingObj,
579
668
  tabLocalIdentifier,
669
+ enablePersistentFiltersAcrossTabs,
580
670
  }
581
671
  : {
582
672
  type: dateFilter.dateFilter.type,
@@ -590,19 +680,30 @@ function* getDateFilterUpdateActions(dateFilter, resetOthers, tabLocalIdentifier
590
680
  : {}),
591
681
  ...emptyValueHandlingObj,
592
682
  tabLocalIdentifier,
683
+ enablePersistentFiltersAcrossTabs,
593
684
  };
594
685
  return [tabsActions.upsertDateFilter(upsertPayload)];
595
686
  }
596
687
  else if (resetOthers) {
597
- return [tabsActions.upsertDateFilter({ type: "allTime", tabLocalIdentifier })];
688
+ return [
689
+ tabsActions.upsertDateFilter({
690
+ type: "allTime",
691
+ tabLocalIdentifier,
692
+ enablePersistentFiltersAcrossTabs,
693
+ }),
694
+ ];
598
695
  }
599
696
  return [];
600
697
  }
601
698
  function* getMeasureValueFiltersUpdateActions(measureValueFilters, resetOthers) {
602
699
  const updateActions = [];
603
700
  const handledLocalIds = new Set();
604
- const currentMeasureValueFilters = yield select(selectFilterContextMeasureValueFilters);
701
+ // a restricted filter keeps its stored conditions: it is neither matched nor reset
702
+ const allMeasureValueFilters = yield select(selectFilterContextMeasureValueFilters);
703
+ const restrictedLocalIdentifiers = yield select(selectRestrictedDashboardFilterLocalIdentifiers);
704
+ const currentMeasureValueFilters = allMeasureValueFilters.filter((filter) => !restrictedLocalIdentifiers.has(dashboardMeasureValueFilterLocalIdentifier(filter)));
605
705
  const currentByLocalId = new Map(currentMeasureValueFilters.map((f) => [dashboardMeasureValueFilterLocalIdentifier(f), f]));
706
+ const enablePersistentFiltersAcrossTabs = yield select(selectEnablePersistentFiltersAcrossTabs);
606
707
  for (const incoming of measureValueFilters) {
607
708
  const incomingLocalIdentifier = dashboardMeasureValueFilterLocalIdentifier(incoming);
608
709
  const currentFilter = currentByLocalId.get(incomingLocalIdentifier) ??
@@ -618,11 +719,13 @@ function* getMeasureValueFiltersUpdateActions(measureValueFilters, resetOthers)
618
719
  updateActions.push(tabsActions.changeMeasureValueFilterCondition({
619
720
  localIdentifier,
620
721
  conditions: incoming.dashboardMeasureValueFilter.conditions,
722
+ enablePersistentFiltersAcrossTabs,
621
723
  }));
622
724
  if ("dimensionality" in incoming.dashboardMeasureValueFilter) {
623
725
  updateActions.push(tabsActions.setMeasureValueFilterDimensionality({
624
726
  localIdentifier,
625
727
  dimensionality: incoming.dashboardMeasureValueFilter.dimensionality,
728
+ enablePersistentFiltersAcrossTabs,
626
729
  }));
627
730
  }
628
731
  }
@@ -637,6 +740,7 @@ function* getMeasureValueFiltersUpdateActions(measureValueFilters, resetOthers)
637
740
  updateActions.push(tabsActions.changeMeasureValueFilterCondition({
638
741
  localIdentifier,
639
742
  conditions: undefined,
743
+ enablePersistentFiltersAcrossTabs,
640
744
  }));
641
745
  }
642
746
  }
@@ -645,6 +749,7 @@ function* getMeasureValueFiltersUpdateActions(measureValueFilters, resetOthers)
645
749
  function* getDateFiltersUpdateActions(dateFilters, resetOthers, tabLocalIdentifier) {
646
750
  const updateActions = [];
647
751
  const handledDataSets = new Set();
752
+ const enablePersistentFiltersAcrossTabs = yield select(selectEnablePersistentFiltersAcrossTabs);
648
753
  for (const dateFilter of dateFilters) {
649
754
  const filterRef = dateFilter.dateFilter.dataSet;
650
755
  const dashboardFilter = yield call(selectFilterContextDateFilterByDataSetTabAware, filterRef, tabLocalIdentifier);
@@ -663,6 +768,7 @@ function* getDateFiltersUpdateActions(dateFilters, resetOthers, tabLocalIdentifi
663
768
  ...localIdentifierObj,
664
769
  ...emptyValueHandlingObj,
665
770
  tabLocalIdentifier,
771
+ enablePersistentFiltersAcrossTabs,
666
772
  }
667
773
  : {
668
774
  type: dateFilter.dateFilter.type,
@@ -676,6 +782,7 @@ function* getDateFiltersUpdateActions(dateFilters, resetOthers, tabLocalIdentifi
676
782
  : {}),
677
783
  ...emptyValueHandlingObj,
678
784
  tabLocalIdentifier,
785
+ enablePersistentFiltersAcrossTabs,
679
786
  };
680
787
  updateActions.push(tabsActions.upsertDateFilter(upsertPayload));
681
788
  }
@@ -694,6 +801,7 @@ function* getDateFiltersUpdateActions(dateFilters, resetOthers, tabLocalIdentifi
694
801
  dataSet: dateFilter.dateFilter.dataSet,
695
802
  ...localIdentifierObj,
696
803
  tabLocalIdentifier,
804
+ enablePersistentFiltersAcrossTabs,
697
805
  }));
698
806
  }
699
807
  }
@@ -744,20 +852,30 @@ function canMapDashboardFilterFromAnotherDisplayForm(ctx) {
744
852
  return ctx.backend.capabilities.supportsElementUris;
745
853
  }
746
854
  /**
747
- * Returns a shallow copy of the filter with its localIdentifier replaced.
748
- * Used when replacing a target filter with an incoming filter from another dashboard
749
- * to preserve the target's localIdentifier so that attributeFilterConfigs (keyed by localId)
750
- * continue to match.
855
+ * Fits a replacement to the dashboard filter it replaces. It takes the target's localIdentifier, so
856
+ * attributeFilterConfigs (keyed by it) keep matching, and the target's dependencies, also when the target
857
+ * has none: an entry's parents may name filters of another context, and such a parent could make the
858
+ * replaced filter restricted.
751
859
  */
752
- function normalizeFilterLocalIdentifier(filter, localIdentifier) {
860
+ function fitReplacementToTarget(filter, target) {
861
+ const localIdentifier = dashboardAttributeFilterItemLocalIdentifier(target);
862
+ const dependencies = {
863
+ filterElementsBy: dashboardAttributeFilterItemFilterElementsBy(target),
864
+ filterElementsByDate: dashboardAttributeFilterItemFilterElementsByDate(target),
865
+ validateElementsBy: dashboardAttributeFilterItemValidateElementsBy(target),
866
+ };
753
867
  if (isDashboardAttributeFilter(filter)) {
754
868
  return {
755
- attributeFilter: { ...filter.attributeFilter, localIdentifier },
869
+ attributeFilter: { ...filter.attributeFilter, localIdentifier, ...dependencies },
756
870
  };
757
871
  }
758
872
  if (isDashboardArbitraryAttributeFilter(filter)) {
759
873
  return {
760
- arbitraryAttributeFilter: { ...filter.arbitraryAttributeFilter, localIdentifier },
874
+ arbitraryAttributeFilter: {
875
+ ...filter.arbitraryAttributeFilter,
876
+ localIdentifier,
877
+ ...dependencies,
878
+ },
761
879
  };
762
880
  }
763
881
  // IDashboardMatchAttributeFilter
@@ -1,14 +1,26 @@
1
1
  import { type SagaIterator } from "redux-saga";
2
- import { type IDashboardDateFilter } from "@gooddata/sdk-model";
2
+ import { type IDashboardAttributeFilterConfig, type IDashboardDateFilter, type IFilterContextDefinition } from "@gooddata/sdk-model";
3
3
  import { type IDashboardCommand } from "../../commands/base.js";
4
+ import { type IWorkingFilterContextDefinition } from "../../store/tabs/filterContext/filterContextState.js";
4
5
  import { type DashboardContext } from "../../types/commonTypes.js";
6
+ /**
7
+ * Applied filter context and attribute filter configs of every tab, taken before a write.
8
+ * Compared with the store afterwards so a mirrored change emits one event per tab updated.
9
+ */
10
+ export interface IFilterContextByTabSnapshot {
11
+ readonly filterContexts: Record<string, IFilterContextDefinition | undefined>;
12
+ readonly workingFilterContexts: Record<string, IWorkingFilterContextDefinition | undefined>;
13
+ readonly attributeFilterConfigs: Record<string, IDashboardAttributeFilterConfig[]>;
14
+ }
15
+ export declare function captureFilterContextByTabSnapshot(): SagaIterator<IFilterContextByTabSnapshot>;
16
+ export declare function dispatchFilterContextChangedForMutatedTabs(ctx: DashboardContext, cmd: IDashboardCommand, snapshot: IFilterContextByTabSnapshot): SagaIterator<void>;
5
17
  export declare function dispatchFilterContextChanged(ctx: DashboardContext, cmd: IDashboardCommand, tabLocalIdentifier?: string): SagaIterator<void>;
6
18
  export declare function canApplyDateFilter(dateFilter: IDashboardDateFilter): SagaIterator<boolean>;
7
19
  export declare function resetCrossFiltering(cmd: IDashboardCommand): Generator<import("redux-saga/effects").PutEffect<import("../../commands/filters.js").IRemoveAttributeFilters> | import("redux-saga/effects").PutEffect<{
8
20
  payload: string | undefined;
9
21
  type: "drill/resetCrossFiltering";
10
22
  }> | import("redux-saga/effects").SelectEffect, void, string[] & string>;
11
- export declare function applyWorkingSelectionHandler(ctx: DashboardContext, cmd: IDashboardCommand): Generator<import("redux-saga/effects").CallEffect<void> | import("redux-saga/effects").PutEffect<{
23
+ export declare function applyWorkingSelectionHandler(ctx: DashboardContext, cmd: IDashboardCommand): Generator<import("redux-saga/effects").CallEffect<void> | import("redux-saga/effects").CallEffect<IFilterContextByTabSnapshot> | import("redux-saga/effects").PutEffect<{
12
24
  payload: import("../../../index.js").IApplyWorkingSelectionPayload;
13
25
  type: "tabs/applyWorkingSelection";
14
26
  }> | import("redux-saga/effects").PutEffect<import("../../events/base.js").ICustomDashboardEvent<any> | import("../../events/base.js").IDashboardEvent<any>> | import("redux-saga/effects").SelectEffect, void, never>;
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/filterContext/common.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAuBhE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGnE,wBAAiB,4BAA4B,CACzC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,iBAAiB,EACtB,kBAAkB,CAAC,EAAE,MAAM,GAC5B,YAAY,CAAC,IAAI,CAAC,CAkCpB;AAED,wBAAiB,kBAAkB,CAAC,UAAU,EAAE,oBAAoB,GAAG,YAAY,CAAC,OAAO,CAAC,CAuB3F;AAED,wBAAiB,mBAAmB,CAAC,GAAG,EAAE,iBAAiB;;;yEAU1D;AAED,wBAAiB,4BAA4B,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB;;;uNAuB1F;AAED,wBAAiB,4BAA4B;;;mBAE5C"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../../src/model/commandHandlers/filterContext/common.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,OAAO,EACH,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAChC,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAuBhE,OAAO,EAAE,KAAK,+BAA+B,EAAE,MAAM,sDAAsD,CAAC;AAI5G,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGnE;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IACxC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC,CAAC;IAC9E,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,+BAA+B,GAAG,SAAS,CAAC,CAAC;IAC5F,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,+BAA+B,EAAE,CAAC,CAAC;CACtF;AAED,wBAAiB,iCAAiC,IAAI,YAAY,CAAC,2BAA2B,CAAC,CAU9F;AAED,wBAAiB,0CAA0C,CACvD,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,iBAAiB,EACtB,QAAQ,EAAE,2BAA2B,GACtC,YAAY,CAAC,IAAI,CAAC,CAwBpB;AAED,wBAAiB,4BAA4B,CACzC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,iBAAiB,EACtB,kBAAkB,CAAC,EAAE,MAAM,GAC5B,YAAY,CAAC,IAAI,CAAC,CAkCpB;AAED,wBAAiB,kBAAkB,CAAC,UAAU,EAAE,oBAAoB,GAAG,YAAY,CAAC,OAAO,CAAC,CAuB3F;AAED,wBAAiB,mBAAmB,CAAC,GAAG,EAAE,iBAAiB;;;yEAU1D;AAED,wBAAiB,4BAA4B,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB;;;uNAwC1F;AAED,wBAAiB,4BAA4B;;;mBAE5C"}
@@ -1,4 +1,5 @@
1
1
  // (C) 2021-2026 GoodData Corporation
2
+ import { isEqual } from "lodash-es";
2
3
  import { call, put, select } from "redux-saga/effects";
3
4
  import { invariant } from "ts-invariant";
4
5
  import { findDateFilterOptionByValue } from "../../../_staging/dateFilterConfig/dateFilterOptionMapping.js";
@@ -6,15 +7,39 @@ import { removeAttributeFilters } from "../../commands/filters.js";
6
7
  import { filterContextChanged, filterContextWorkingSelectionApplied } from "../../events/filters.js";
7
8
  import { dispatchDashboardEvent } from "../../store/_infra/eventDispatcher.js";
8
9
  import { selectEnableImmediateAttributeFilterDisplayAsLabelMigration } from "../../store/config/configSelectors.js";
9
- import { selectCrossFilteringFiltersLocalIdentifiers, selectIsCrossFiltering, } from "../../store/drill/drillSelectors.js";
10
+ import { selectCrossFilteringFiltersLocalIdentifiers, selectCrossFilteringFiltersLocalIdentifiersByTab, selectIsCrossFiltering, } from "../../store/drill/drillSelectors.js";
10
11
  import { drillActions } from "../../store/drill/index.js";
12
+ import { selectEnablePersistentFiltersAcrossTabs } from "../../store/meta/metaSelectors.js";
11
13
  import { selectAttributeFilterConfigsOverrides, selectAttributeFilterConfigsOverridesByTab, } from "../../store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
12
14
  import { selectEffectiveDateFilterOptions } from "../../store/tabs/dateFilterConfig/dateFilterConfigSelectors.js";
13
- import { selectFilterContextDefinition, selectFilterContextDefinitionsByTab, selectIsWorkingFilterContextChanged, } from "../../store/tabs/filterContext/filterContextSelectors.js";
15
+ import { selectFilterContextDefinition, selectFilterContextDefinitionsByTab, selectIsWorkingFilterContextChanged, selectWorkingFilterContextDefinitionsByTab, } from "../../store/tabs/filterContext/filterContextSelectors.js";
14
16
  import { tabsActions } from "../../store/tabs/index.js";
15
17
  import { selectParameterValuesByTab } from "../../store/tabs/parameters/parametersSelectors.js";
16
18
  import { selectActiveOrDefaultTabLocalIdentifier } from "../../store/tabs/tabsSelectors.js";
17
- import { dispatchParametersChanged } from "../parameters/changeParameterValuesHandler.js";
19
+ import { dispatchParametersChangedForAffectedTabs } from "../parameters/changeParameterValuesHandler.js";
20
+ export function* captureFilterContextByTabSnapshot() {
21
+ const filterContexts = yield select(selectFilterContextDefinitionsByTab);
22
+ const workingFilterContexts = yield select(selectWorkingFilterContextDefinitionsByTab);
23
+ const attributeFilterConfigs = yield select(selectAttributeFilterConfigsOverridesByTab);
24
+ return { filterContexts, workingFilterContexts, attributeFilterConfigs };
25
+ }
26
+ export function* dispatchFilterContextChangedForMutatedTabs(ctx, cmd, snapshot) {
27
+ const filterContexts = yield select(selectFilterContextDefinitionsByTab);
28
+ const attributeFilterConfigs = yield select(selectAttributeFilterConfigsOverridesByTab);
29
+ const workingFilterContexts = yield select(selectWorkingFilterContextDefinitionsByTab);
30
+ for (const tabId of Object.keys(filterContexts)) {
31
+ if (!filterContexts[tabId]) {
32
+ continue;
33
+ }
34
+ const configs = attributeFilterConfigs[tabId] ?? [];
35
+ if (isEqual(snapshot.filterContexts[tabId], filterContexts[tabId]) &&
36
+ isEqual(snapshot.workingFilterContexts[tabId], workingFilterContexts[tabId]) &&
37
+ isEqual(snapshot.attributeFilterConfigs[tabId] ?? [], configs)) {
38
+ continue;
39
+ }
40
+ yield call(dispatchFilterContextChanged, ctx, cmd, tabId);
41
+ }
42
+ }
18
43
  export function* dispatchFilterContextChanged(ctx, cmd, tabLocalIdentifier) {
19
44
  let filterContext;
20
45
  let attributeFilterConfigs;
@@ -64,14 +89,26 @@ export function* applyWorkingSelectionHandler(ctx, cmd) {
64
89
  const isWorkingFilterContextChanged = yield select(selectIsWorkingFilterContextChanged);
65
90
  const tabId = yield select(selectActiveOrDefaultTabLocalIdentifier);
66
91
  const valuesBefore = yield select(selectParameterValuesByTab);
67
- yield put(tabsActions.applyWorkingSelection({ enableImmediateAttributeFilterDisplayAsLabelMigration }));
92
+ const crossFilteringLocalIdsByTab = yield select(selectCrossFilteringFiltersLocalIdentifiersByTab);
93
+ const enablePersistentFiltersAcrossTabs = yield select(selectEnablePersistentFiltersAcrossTabs);
94
+ const filterContextSnapshot = yield call(captureFilterContextByTabSnapshot);
95
+ yield put(tabsActions.applyWorkingSelection({
96
+ enableImmediateAttributeFilterDisplayAsLabelMigration,
97
+ crossFilteringLocalIdsByTab,
98
+ enablePersistentFiltersAcrossTabs,
99
+ }));
68
100
  const isCrossFiltering = yield select(selectIsCrossFiltering);
69
101
  if (isWorkingFilterContextChanged && isCrossFiltering) {
70
102
  yield call(resetCrossFiltering, cmd);
71
103
  }
72
104
  yield dispatchDashboardEvent(filterContextWorkingSelectionApplied(ctx, cmd.correlationId));
73
- yield call(dispatchFilterContextChanged, ctx, cmd);
74
- yield call(dispatchParametersChanged, ctx, cmd, tabId, valuesBefore[tabId]);
105
+ if (enablePersistentFiltersAcrossTabs) {
106
+ yield call(dispatchFilterContextChangedForMutatedTabs, ctx, cmd, filterContextSnapshot);
107
+ }
108
+ else {
109
+ yield call(dispatchFilterContextChanged, ctx, cmd);
110
+ }
111
+ yield call(dispatchParametersChangedForAffectedTabs, ctx, cmd, valuesBefore, tabId);
75
112
  }
76
113
  export function* resetWorkingSelectionHandler() {
77
114
  yield put(tabsActions.resetWorkingSelection());
@@ -1 +1 @@
1
- {"version":3,"file":"changeDateFilterSelectionHandler.d.ts","sourceRoot":"","sources":["../../../../../src/model/commandHandlers/filterContext/dateFilter/changeDateFilterSelectionHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAU/C,OAAO,EAAE,KAAK,yBAAyB,EAA4B,MAAM,8BAA8B,CAAC;AAWxG,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGtE,wBAAiB,gCAAgC,CAC7C,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,yBAAyB,GAC/B,YAAY,CAAC,IAAI,CAAC,CAqEpB"}
1
+ {"version":3,"file":"changeDateFilterSelectionHandler.d.ts","sourceRoot":"","sources":["../../../../../src/model/commandHandlers/filterContext/dateFilter/changeDateFilterSelectionHandler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAU/C,OAAO,EAAE,KAAK,yBAAyB,EAA4B,MAAM,8BAA8B,CAAC;AAYxG,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAUtE,wBAAiB,gCAAgC,CAC7C,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,yBAAyB,GAC/B,YAAY,CAAC,IAAI,CAAC,CA8EpB"}