@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
@@ -0,0 +1,361 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { areObjRefsEqual, dashboardAttributeFilterItemFilterElementsBy, dashboardAttributeFilterItemFilterElementsByDate, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemValidateElementsBy, dashboardFilterLocalIdentifier, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMatchAttributeFilter, isDashboardMeasureValueFilter, } from "@gooddata/sdk-model";
3
+ import { findAttributeFilterCounterpart } from "../../../utils/attributeFilterMatching.js";
4
+ /**
5
+ * True when `localIdentifier` belongs to a cross-filtering virtual filter on `tab`.
6
+ * Those filters are applied on their own tab only and are not mirrored to or from other tabs.
7
+ *
8
+ * @internal
9
+ */
10
+ export function isCrossFilteringVirtualFilter(tab, localIdentifier, crossFilteringLocalIdsByTab) {
11
+ if (!localIdentifier) {
12
+ return false;
13
+ }
14
+ return crossFilteringLocalIdsByTab?.[tab.localIdentifier]?.includes(localIdentifier) ?? false;
15
+ }
16
+ /**
17
+ * Finds the filter on `targetTab` that is the same logical filter as `sourceFilter`.
18
+ * Same-type matches take precedence over list/text migrations. A missing or incompatible
19
+ * counterpart is a no-op for the caller.
20
+ *
21
+ * Assumes at most one filter per display form, date data set, and measure with a given
22
+ * dimensionality on a tab.
23
+ *
24
+ * @internal
25
+ */
26
+ export function findCounterpartFilter(sourceTab, sourceFilter, targetTab, attributesWithReferences, options) {
27
+ const targets = appliedFilters(targetTab).filter((filter) => !isCrossFilteringVirtualFilter(targetTab, dashboardFilterLocalIdentifier(filter), options?.crossFilteringLocalIdsByTab));
28
+ if (targets.length === 0) {
29
+ return undefined;
30
+ }
31
+ const displayForms = collectDisplayForms(sourceTab, targetTab, attributesWithReferences);
32
+ if (isDashboardAttributeFilterItem(sourceFilter)) {
33
+ const result = findAttributeFilterCounterpart(sourceFilter, targets.filter(isDashboardAttributeFilterItem), {
34
+ sourceFilterConfigs: sourceTab.attributeFilterConfigs?.attributeFilterConfigs,
35
+ targetFilterConfigs: targetTab.attributeFilterConfigs?.attributeFilterConfigs,
36
+ displayForms,
37
+ attributesWithReferences,
38
+ allowListToText: options?.allowListToText ?? true,
39
+ });
40
+ return result.status === "matched" ? result.counterpart : undefined;
41
+ }
42
+ if (isDashboardDateFilter(sourceFilter)) {
43
+ return findDateCounterpart(sourceFilter, targets);
44
+ }
45
+ if (isDashboardMeasureValueFilter(sourceFilter)) {
46
+ return findMeasureValueCounterpart(sourceFilter, targets);
47
+ }
48
+ return undefined;
49
+ }
50
+ /**
51
+ * The saved initial filter on `tab` that corresponds to `appliedFilter`.
52
+ * Same-tab `localIdentifier` wins; identity matching is the fallback.
53
+ * Returns undefined when this tab has no saved counterpart.
54
+ *
55
+ * @internal
56
+ */
57
+ export function findOriginalFilter(tab, appliedFilter) {
58
+ const originals = tab.filterContext?.originalFilterContextDefinition?.filters;
59
+ if (!originals?.length) {
60
+ return undefined;
61
+ }
62
+ const localIdentifier = dashboardFilterLocalIdentifier(appliedFilter);
63
+ if (localIdentifier) {
64
+ const byLocalId = originals.find((filter) => dashboardFilterLocalIdentifier(filter) === localIdentifier);
65
+ if (byLocalId) {
66
+ return byLocalId;
67
+ }
68
+ }
69
+ if (!tab.filterContext?.originalFilterContextDefinition) {
70
+ return undefined;
71
+ }
72
+ const originalTab = {
73
+ ...tab,
74
+ filterContext: {
75
+ ...tab.filterContext,
76
+ filterContextDefinition: tab.filterContext.originalFilterContextDefinition,
77
+ },
78
+ };
79
+ return findCounterpartFilter(tab, appliedFilter, originalTab);
80
+ }
81
+ /**
82
+ * Rewrites an attribute filter item so it keeps `localIdentifier` (the target tab's id).
83
+ *
84
+ * @internal
85
+ */
86
+ export function retargetAttributeFilterItem(filter, localIdentifier) {
87
+ if (isDashboardAttributeFilter(filter)) {
88
+ return { attributeFilter: { ...filter.attributeFilter, localIdentifier } };
89
+ }
90
+ if (isDashboardArbitraryAttributeFilter(filter)) {
91
+ return { arbitraryAttributeFilter: { ...filter.arbitraryAttributeFilter, localIdentifier } };
92
+ }
93
+ if (isDashboardMatchAttributeFilter(filter)) {
94
+ return { matchAttributeFilter: { ...filter.matchAttributeFilter, localIdentifier } };
95
+ }
96
+ return filter;
97
+ }
98
+ /**
99
+ * Carries over parent-filter dependencies from the filter being replaced.
100
+ *
101
+ * @internal
102
+ */
103
+ export function preserveFilterDependencies(existing, replacement) {
104
+ const filterElementsBy = dashboardAttributeFilterItemFilterElementsBy(existing);
105
+ const filterElementsByDate = dashboardAttributeFilterItemFilterElementsByDate(existing);
106
+ const validateElementsBy = dashboardAttributeFilterItemValidateElementsBy(existing);
107
+ if (!filterElementsBy?.length && !filterElementsByDate?.length && !validateElementsBy?.length) {
108
+ return replacement;
109
+ }
110
+ const preserved = {
111
+ ...(filterElementsBy?.length ? { filterElementsBy } : {}),
112
+ ...(filterElementsByDate?.length ? { filterElementsByDate } : {}),
113
+ ...(validateElementsBy?.length ? { validateElementsBy } : {}),
114
+ };
115
+ if (isDashboardAttributeFilter(replacement)) {
116
+ return { attributeFilter: { ...replacement.attributeFilter, ...preserved } };
117
+ }
118
+ if (isDashboardArbitraryAttributeFilter(replacement)) {
119
+ return { arbitraryAttributeFilter: { ...replacement.arbitraryAttributeFilter, ...preserved } };
120
+ }
121
+ return replacement;
122
+ }
123
+ /**
124
+ * Copies an applied source filter onto an existing target filter, keeping the target's
125
+ * local identifier, parent dependencies, and measure-value dimensionality. Does not create a filter.
126
+ *
127
+ * @internal
128
+ */
129
+ export function applyMirroredFilterValue(source, target) {
130
+ if (isDashboardAttributeFilter(source) && isDashboardAttributeFilter(target)) {
131
+ return {
132
+ attributeFilter: {
133
+ ...target.attributeFilter,
134
+ attributeElements: source.attributeFilter.attributeElements,
135
+ negativeSelection: source.attributeFilter.negativeSelection,
136
+ },
137
+ };
138
+ }
139
+ if (isDashboardDateFilter(source) && isDashboardDateFilter(target)) {
140
+ const dateFilter = {
141
+ ...source.dateFilter,
142
+ };
143
+ if (target.dateFilter.localIdentifier) {
144
+ dateFilter.localIdentifier = target.dateFilter.localIdentifier;
145
+ }
146
+ else {
147
+ delete dateFilter.localIdentifier;
148
+ }
149
+ if (target.dateFilter.dataSet) {
150
+ dateFilter.dataSet = target.dateFilter.dataSet;
151
+ }
152
+ else {
153
+ delete dateFilter.dataSet;
154
+ }
155
+ return { dateFilter };
156
+ }
157
+ if (isDashboardMeasureValueFilter(source) && isDashboardMeasureValueFilter(target)) {
158
+ const { measure, localIdentifier, title, dimensionality } = target.dashboardMeasureValueFilter;
159
+ return {
160
+ dashboardMeasureValueFilter: {
161
+ measure,
162
+ localIdentifier,
163
+ ...(title === undefined ? {} : { title }),
164
+ conditions: source.dashboardMeasureValueFilter.conditions,
165
+ ...(dimensionality?.length ? { dimensionality } : {}),
166
+ },
167
+ };
168
+ }
169
+ if (isDashboardAttributeFilterItem(source) && isDashboardAttributeFilterItem(target)) {
170
+ const targetLocalId = dashboardAttributeFilterItemLocalIdentifier(target);
171
+ if (!targetLocalId) {
172
+ return target;
173
+ }
174
+ return preserveFilterDependencies(target, retargetAttributeFilterItem(source, targetLocalId));
175
+ }
176
+ return target;
177
+ }
178
+ /**
179
+ * After the active tab commits staged filters and parameters, copy those applied values
180
+ * onto matching filters and parameters on the other tabs. Working overlays are not copied.
181
+ * A target tab's own staged value for a mirrored filter or parameter is dropped, so it cannot
182
+ * shadow the mirrored value; staged values of other filters on that tab are kept.
183
+ *
184
+ * @internal
185
+ */
186
+ export function mirrorCommittedWorkingSelection(state, sourceTab, stagedFilters, stagedParameterRefs, crossFilteringLocalIdsByTab, enablePersistentFiltersAcrossTabs) {
187
+ if (!enablePersistentFiltersAcrossTabs) {
188
+ return;
189
+ }
190
+ const others = (state.tabs ?? []).filter((tab) => tab.localIdentifier !== sourceTab.localIdentifier);
191
+ if (others.length === 0) {
192
+ return;
193
+ }
194
+ for (const staged of stagedFilters ?? []) {
195
+ const applied = findAppliedFilterForStaged(sourceTab, staged);
196
+ if (!applied) {
197
+ continue;
198
+ }
199
+ if (isCrossFilteringVirtualFilter(sourceTab, dashboardFilterLocalIdentifier(applied), crossFilteringLocalIdsByTab)) {
200
+ continue;
201
+ }
202
+ for (const tab of others) {
203
+ const match = findCounterpartFilter(sourceTab, applied, tab, state.attributesWithReferences, {
204
+ allowListToText: false,
205
+ crossFilteringLocalIdsByTab,
206
+ });
207
+ if (!match) {
208
+ continue;
209
+ }
210
+ writeFilter(tab, match, applyMirroredFilterValue(applied, match));
211
+ dropStagedOverlay(tab, match);
212
+ }
213
+ }
214
+ for (const ref of stagedParameterRefs) {
215
+ const sourceEntry = sourceTab.parameters?.parameters.find((entry) => areObjRefsEqual(entry.parameter.ref, ref));
216
+ if (!sourceEntry) {
217
+ continue;
218
+ }
219
+ for (const tab of others) {
220
+ const entry = tab.parameters?.parameters.find((candidate) => areObjRefsEqual(candidate.parameter.ref, ref));
221
+ if (!entry) {
222
+ continue;
223
+ }
224
+ delete entry.workingOverride;
225
+ entry.runtimeOverride = sourceEntry.runtimeOverride;
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * Seeds a new tab with the applied filters of `sourceTab` and the configs that belong to them,
231
+ * so every synced filter has a counterpart on the new tab. Cross-filtering virtual filters stay
232
+ * on their own tab. `base` provides the title and description of the new filter context.
233
+ *
234
+ * @internal
235
+ */
236
+ export function seedSyncedTabFilters(sourceTab, base, crossFilteringLocalIdsByTab) {
237
+ const filters = appliedFilters(sourceTab).filter((filter) => !isCrossFilteringVirtualFilter(sourceTab, dashboardFilterLocalIdentifier(filter), crossFilteringLocalIdsByTab));
238
+ const localIds = new Set(filters.map(dashboardFilterLocalIdentifier).filter((id) => id !== undefined));
239
+ const dateDataSets = filters
240
+ .filter(isDashboardDateFilter)
241
+ .map((filter) => filter.dateFilter.dataSet)
242
+ .filter((ref) => ref !== undefined);
243
+ const groups = (sourceTab.filterGroupsConfig?.groups ?? [])
244
+ .map((group) => ({
245
+ ...group,
246
+ filters: group.filters.filter((item) => localIds.has(item.filterLocalIdentifier)),
247
+ }))
248
+ .filter((group) => group.filters.length > 0);
249
+ return {
250
+ filterContextDefinition: { ...base, filters },
251
+ attributeFilterDisplayForms: sourceTab.filterContext?.attributeFilterDisplayForms ?? [],
252
+ attributeFilterConfigs: (sourceTab.attributeFilterConfigs?.attributeFilterConfigs ?? []).filter((config) => localIds.has(config.localIdentifier)),
253
+ dateFilterConfigs: (sourceTab.dateFilterConfigs?.dateFilterConfigs ?? []).filter((config) => dateDataSets.some((ref) => areObjRefsEqual(ref, config.dateDataSet))),
254
+ measureValueFilterConfigs: (sourceTab.measureValueFilterConfigs?.measureValueFilterConfigs ?? []).filter((config) => localIds.has(config.localIdentifier)),
255
+ filterGroupsConfig: groups.length > 0 ? { groups } : undefined,
256
+ };
257
+ }
258
+ function writeFilter(tab, current, next) {
259
+ const filters = tab.filterContext?.filterContextDefinition?.filters;
260
+ if (!filters) {
261
+ return;
262
+ }
263
+ const index = filters.indexOf(current);
264
+ if (index < 0) {
265
+ return;
266
+ }
267
+ filters[index] = next;
268
+ }
269
+ /**
270
+ * Removes the working entries that `applyFilterContext` would overlay on `applied`.
271
+ */
272
+ function dropStagedOverlay(tab, applied) {
273
+ const filterContext = tab.filterContext;
274
+ const working = filterContext?.workingFilterContextDefinition;
275
+ if (!filterContext || !working?.filters.length) {
276
+ return;
277
+ }
278
+ const overlays = isDashboardDateFilter(applied)
279
+ ? (item) => "dateFilter" in item &&
280
+ !!item.dateFilter &&
281
+ (areObjRefsEqual(item.dateFilter.dataSet, applied.dateFilter.dataSet) ||
282
+ (!item.dateFilter.dataSet && !applied.dateFilter.dataSet))
283
+ : (item) => !("dateFilter" in item) &&
284
+ stagedLocalIdentifier(item) !== undefined &&
285
+ stagedLocalIdentifier(item) === dashboardFilterLocalIdentifier(applied);
286
+ const remaining = working.filters.filter((item) => !overlays(item));
287
+ if (remaining.length !== working.filters.length) {
288
+ filterContext.workingFilterContextDefinition = { filters: remaining };
289
+ }
290
+ }
291
+ function findAppliedFilterForStaged(tab, staged) {
292
+ const filters = appliedFilters(tab);
293
+ if ("dateFilter" in staged && staged.dateFilter) {
294
+ if (!staged.dateFilter.dataSet) {
295
+ return filters.find(isDashboardCommonDateFilter);
296
+ }
297
+ return filters.find((filter) => isDashboardDateFilter(filter) &&
298
+ areObjRefsEqual(filter.dateFilter.dataSet, staged.dateFilter.dataSet));
299
+ }
300
+ const localIdentifier = stagedLocalIdentifier(staged);
301
+ if (!localIdentifier) {
302
+ return undefined;
303
+ }
304
+ return filters.find((filter) => dashboardFilterLocalIdentifier(filter) === localIdentifier);
305
+ }
306
+ function stagedLocalIdentifier(item) {
307
+ if ("attributeFilter" in item && item.attributeFilter) {
308
+ return item.attributeFilter.localIdentifier;
309
+ }
310
+ if ("arbitraryAttributeFilter" in item && item.arbitraryAttributeFilter) {
311
+ return item.arbitraryAttributeFilter.localIdentifier;
312
+ }
313
+ if ("matchAttributeFilter" in item && item.matchAttributeFilter) {
314
+ return item.matchAttributeFilter.localIdentifier;
315
+ }
316
+ if ("dateFilter" in item && item.dateFilter) {
317
+ return item.dateFilter.localIdentifier;
318
+ }
319
+ if ("dashboardMeasureValueFilter" in item && item.dashboardMeasureValueFilter) {
320
+ return item.dashboardMeasureValueFilter.localIdentifier;
321
+ }
322
+ return undefined;
323
+ }
324
+ function appliedFilters(tab) {
325
+ return tab.filterContext?.filterContextDefinition?.filters ?? [];
326
+ }
327
+ function collectDisplayForms(sourceTab, targetTab, attributesWithReferences) {
328
+ return [
329
+ ...(sourceTab.filterContext?.attributeFilterDisplayForms ?? []),
330
+ ...(targetTab.filterContext?.attributeFilterDisplayForms ?? []),
331
+ ...(attributesWithReferences ?? []).flatMap((entry) => entry.attribute.displayForms ?? []),
332
+ ];
333
+ }
334
+ function findDateCounterpart(sourceFilter, targets) {
335
+ // Check the data set directly. `isDashboardCommonDateFilter` is typed as
336
+ // `obj is IDashboardDateFilter`, so using it as a guard would narrow the
337
+ // remaining source to `never`.
338
+ const dataSet = sourceFilter.dateFilter.dataSet;
339
+ if (!dataSet) {
340
+ return targets.find(isDashboardCommonDateFilter);
341
+ }
342
+ return targets.find((filter) => isDashboardDateFilter(filter) && areObjRefsEqual(filter.dateFilter.dataSet, dataSet));
343
+ }
344
+ function findMeasureValueCounterpart(sourceFilter, targets) {
345
+ return targets
346
+ .filter(isDashboardMeasureValueFilter)
347
+ .find((filter) => areObjRefsEqual(filter.dashboardMeasureValueFilter.measure, sourceFilter.dashboardMeasureValueFilter.measure) &&
348
+ dimensionalityCompatible(filter.dashboardMeasureValueFilter.dimensionality, sourceFilter.dashboardMeasureValueFilter.dimensionality));
349
+ }
350
+ /**
351
+ * A measure value filter syncs only with a filter on the same measure and the same dimensionality.
352
+ * Missing dimensionality matches only another missing dimensionality.
353
+ */
354
+ function dimensionalityCompatible(left, right) {
355
+ const leftRefs = left ?? [];
356
+ const rightRefs = right ?? [];
357
+ if (leftRefs.length !== rightRefs.length) {
358
+ return false;
359
+ }
360
+ return leftRefs.every((ref, index) => areObjRefsEqual(ref, rightRefs[index]));
361
+ }
@@ -137,6 +137,8 @@ export declare const tabsActions: import("@reduxjs/toolkit").CaseReducerActions<
137
137
  payload: {
138
138
  readonly localIdentifier: string;
139
139
  readonly dimensionality?: import("@gooddata/sdk-model").ObjRef[];
140
+ readonly restoreTargetOriginal?: boolean;
141
+ readonly enablePersistentFiltersAcrossTabs?: boolean;
140
142
  };
141
143
  type: string;
142
144
  }) => void | import("./tabsState.js").ITabsState | import("immer").WritableDraft<import("./tabsState.js").ITabsState>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/model/store/tabs/index.ts"],"names":[],"mappings":"AA+BA,eAAO,MAAM,gBAAgB,8DAAoB,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/model/store/tabs/index.ts"],"names":[],"mappings":"AA+BA,eAAO,MAAM,gBAAgB,8DAAoB,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAAoB,CAAC"}
@@ -20,8 +20,13 @@ export interface ISetParameterRuntimeValuesPayload {
20
20
  values: IParameterValueChange[];
21
21
  /**
22
22
  * Target tab. When omitted, the active tab is used.
23
+ * With `enablePersistentFiltersAcrossTabs`, this tab is the source and every other tab is written too.
23
24
  */
24
25
  tabLocalIdentifier?: string;
26
+ /**
27
+ * When true, write the values onto every tab. A tab that does not define the parameter is unchanged.
28
+ */
29
+ enablePersistentFiltersAcrossTabs?: boolean;
25
30
  }
26
31
  /**
27
32
  * Stage a parameter value under the apply-all-at-once mode. The value is always concrete: the
@@ -1 +1 @@
1
- {"version":3,"file":"parametersReducers.d.ts","sourceRoot":"","sources":["../../../../../src/model/store/tabs/parameters/parametersReducers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AAErF,OAAO,EACH,KAAK,mBAAmB,EACxB,KAAK,MAAM,EACX,KAAK,cAAc,EAEtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAgC,MAAM,iBAAiB,CAAC;AAIhG,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,gBAAgB,EAAE,cAAc,CAAC;CACpC;AAoBD;;GAEG;AACH,MAAM,WAAW,iCAAiC;IAC9C,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAWD;;;;;;GAMG;AACH,MAAM,WAAW,gCAAgC;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,cAAc,CAAC;CACzB;AAkBD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC,GAAG,EAAE,MAAM,CAAC;CACf;AAsCD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAOjE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAIhE;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;CAK9B,CAAC"}
1
+ {"version":3,"file":"parametersReducers.d.ts","sourceRoot":"","sources":["../../../../../src/model/store/tabs/parameters/parametersReducers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AAErF,OAAO,EACH,KAAK,mBAAmB,EACxB,KAAK,MAAM,EACX,KAAK,cAAc,EAEtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EACH,KAAK,SAAS,EACd,KAAK,UAAU,EAIlB,MAAM,iBAAiB,CAAC;AAIzB,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,gBAAgB,EAAE,cAAc,CAAC;CACpC;AAoBD;;GAEG;AACH,MAAM,WAAW,iCAAiC;IAC9C,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,iCAAiC,CAAC,EAAE,OAAO,CAAC;CAC/C;AAmBD;;;;;;GAMG;AACH,MAAM,WAAW,gCAAgC;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,cAAc,CAAC;CACzB;AAkBD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC,GAAG,EAAE,MAAM,CAAC;CACf;AAoCD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAOjE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAIhE;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;CAK9B,CAAC"}
@@ -1,6 +1,6 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { areObjRefsEqual, } from "@gooddata/sdk-model";
3
- import { getActiveTab, getTabOrActive } from "../tabsState.js";
3
+ import { getActiveTab, getTabOrActive, getTabsToMutate, } from "../tabsState.js";
4
4
  import { parametersInitialState } from "./parametersState.js";
5
5
  const addParameter = (state, action) => {
6
6
  const activeTab = getActiveTab(state);
@@ -20,8 +20,12 @@ const addParameter = (state, action) => {
20
20
  };
21
21
  };
22
22
  const setParameterRuntimeValues = (state, action) => {
23
- for (const entry of action.payload.values) {
24
- setRuntimeOverride(state, entry, action.payload.tabLocalIdentifier);
23
+ // Parameter values sync by parameter ref. A tab that does not define the parameter is a no-op.
24
+ // Working overrides are not written here.
25
+ for (const tab of getTabsToMutate(state, action.payload.tabLocalIdentifier, action.payload.enablePersistentFiltersAcrossTabs)) {
26
+ for (const entry of action.payload.values) {
27
+ setRuntimeOverrideOnTab(tab, entry);
28
+ }
25
29
  }
26
30
  };
27
31
  const setParameterWorkingValue = (state, action) => {
@@ -46,8 +50,8 @@ const removeParameter = (state, action) => {
46
50
  parameters: activeTab.parameters.parameters.filter((entry) => !areObjRefsEqual(entry.parameter.ref, action.payload.ref)),
47
51
  };
48
52
  };
49
- function setRuntimeOverride(state, { ref, value }, tabLocalIdentifier) {
50
- const entry = findParameterEntry(state, ref, tabLocalIdentifier);
53
+ function setRuntimeOverrideOnTab(tab, { ref, value }) {
54
+ const entry = tab.parameters?.parameters.find((candidate) => areObjRefsEqual(candidate.parameter.ref, ref));
51
55
  if (!entry) {
52
56
  return;
53
57
  }
@@ -1,5 +1,6 @@
1
1
  import { type Action, type CaseReducer } from "@reduxjs/toolkit";
2
2
  import { type ObjRef } from "@gooddata/sdk-model";
3
+ import { type CrossFilteringLocalIdsByTab } from "./filterContext/filterSync.js";
3
4
  import { type ITabState, type ITabsState } from "./tabsState.js";
4
5
  /**
5
6
  * @alpha
@@ -34,6 +35,11 @@ export interface ISetDashboardAttributeFilterConfigDisplayAsLabelPayload {
34
35
  */
35
36
  export interface IApplyWorkingSelectionPayload {
36
37
  readonly enableImmediateAttributeFilterDisplayAsLabelMigration?: boolean;
38
+ /**
39
+ * Cross-filtering virtual filters per tab. They are not mirrored to or from other tabs.
40
+ */
41
+ readonly crossFilteringLocalIdsByTab?: CrossFilteringLocalIdsByTab;
42
+ readonly enablePersistentFiltersAcrossTabs?: boolean;
37
43
  }
38
44
  export declare const tabsReducers: {
39
45
  setTabs: TabsReducer<{
@@ -1 +1 @@
1
- {"version":3,"file":"tabsReducers.d.ts","sourceRoot":"","sources":["../../../../src/model/store/tabs/tabsReducers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AAErF,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAWlD,OAAO,EAAkB,KAAK,SAAS,EAAE,KAAK,UAAU,EAAgB,MAAM,gBAAgB,CAAC;AAE/F;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAEvE,KAAK,cAAc,GAAG;IAClB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IAEnB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AA0FF;;;GAGG;AACH,MAAM,WAAW,uDAAuD;IACpE;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAkDD;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC1C,QAAQ,CAAC,qDAAqD,CAAC,EAAE,OAAO,CAAC;CAC5E;AAyBD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;mBA1EkC,MAAM;wBAAc,OAAO;;;;;;mBAkBlC,MAAM;mBAAS,MAAM;;;;;;gCAeA,MAAM;;;;;;;;;;;;CAoD9E,CAAC"}
1
+ {"version":3,"file":"tabsReducers.d.ts","sourceRoot":"","sources":["../../../../src/model/store/tabs/tabsReducers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAA+B,MAAM,kBAAkB,CAAC;AAE9F,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAOlD,OAAO,EACH,KAAK,2BAA2B,EAEnC,MAAM,+BAA+B,CAAC;AAKvC,OAAO,EAEH,KAAK,SAAS,EACd,KAAK,UAAU,EAGlB,MAAM,gBAAgB,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAEvE,KAAK,cAAc,GAAG;IAClB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IAEnB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AA0FF;;;GAGG;AACH,MAAM,WAAW,uDAAuD;IACpE;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAkDD;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC1C,QAAQ,CAAC,qDAAqD,CAAC,EAAE,OAAO,CAAC;IACzE;;OAEG;IACH,QAAQ,CAAC,2BAA2B,CAAC,EAAE,2BAA2B,CAAC;IACnE,QAAQ,CAAC,iCAAiC,CAAC,EAAE,OAAO,CAAC;CACxD;AAsCD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;mBA5FkC,MAAM;wBAAc,OAAO;;;;;;mBAkBlC,MAAM;mBAAS,MAAM;;;;;;gCAeA,MAAM;;;;;;;;;;;;CAsE9E,CAAC"}
@@ -1,8 +1,10 @@
1
1
  // (C) 2021-2026 GoodData Corporation
2
+ import { current } from "@reduxjs/toolkit";
2
3
  import { clearFilterContextWorkingValues, commitFilterContextWorkingValues, } from "./filterContext/filterContextReducers.js";
3
4
  import { initializeFilterContext } from "./filterContext/filterContextUtils.js";
5
+ import { mirrorCommittedWorkingSelection, } from "./filterContext/filterSync.js";
4
6
  import { clearParameterWorkingValues, commitParameterWorkingValues, } from "./parameters/parametersReducers.js";
5
- import { DEFAULT_TAB_ID, getActiveTab } from "./tabsState.js";
7
+ import { DEFAULT_TAB_ID, getActiveTab, getTabsToMutate, } from "./tabsState.js";
6
8
  /**
7
9
  * Applies initialization logic to a tab's filterContext if present.
8
10
  * Ensures filters have local identifiers and proper ordering.
@@ -119,16 +121,22 @@ const applyWorkingSelection = (state, action) => {
119
121
  if (!activeTab) {
120
122
  return;
121
123
  }
124
+ const stagedFilters = activeTab.filterContext?.workingFilterContextDefinition
125
+ ? current(activeTab.filterContext.workingFilterContextDefinition).filters
126
+ : undefined;
127
+ const stagedParameterRefs = (activeTab.parameters?.parameters ?? [])
128
+ .filter((entry) => entry.workingOverride !== undefined)
129
+ .map((entry) => entry.parameter.ref);
122
130
  commitFilterContextWorkingValues(activeTab, action.payload.enableImmediateAttributeFilterDisplayAsLabelMigration);
123
131
  commitParameterWorkingValues(activeTab);
132
+ // Mirror the committed applied values, not the working overlay.
133
+ mirrorCommittedWorkingSelection(state, activeTab, stagedFilters, stagedParameterRefs, action.payload.crossFilteringLocalIdsByTab, action.payload.enablePersistentFiltersAcrossTabs);
124
134
  };
125
135
  const resetWorkingSelection = (state) => {
126
- const activeTab = getActiveTab(state);
127
- if (!activeTab) {
128
- return;
136
+ for (const tab of getTabsToMutate(state)) {
137
+ clearFilterContextWorkingValues(tab);
138
+ clearParameterWorkingValues(tab);
129
139
  }
130
- clearFilterContextWorkingValues(activeTab);
131
- clearParameterWorkingValues(activeTab);
132
140
  };
133
141
  export const tabsReducers = {
134
142
  setTabs,
@@ -24,6 +24,16 @@ export declare const getActiveTab: (state: ITabsState) => ITabState | undefined;
24
24
  * @internal
25
25
  */
26
26
  export declare const getTabOrActive: (state: ITabsState, tabLocalIdentifier?: string) => ITabState | undefined;
27
+ /**
28
+ * Tabs a filter or parameter mutation should write.
29
+ *
30
+ * With `persistFiltersAcrossTabs` off, that is the tab addressed by `tabLocalIdentifier`
31
+ * or the active tab. With the flag on, that is every tab, source first. Working-selection
32
+ * edits stay on the source tab so half-applied values are not mirrored.
33
+ *
34
+ * @internal
35
+ */
36
+ export declare const getTabsToMutate: (state: ITabsState, tabLocalIdentifier?: string, enablePersistentFiltersAcrossTabs?: boolean, isWorkingSelectionChange?: boolean) => ITabState[];
27
37
  /**
28
38
  * @alpha
29
39
  */
@@ -1 +1 @@
1
- {"version":3,"file":"tabsState.d.ts","sourceRoot":"","sources":["../../../../src/model/store/tabs/tabsState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,0DAA0D,CAAC;AAC7G,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,KAAK,+BAA+B,EAAE,MAAM,+DAA+D,CAAC;AACrH,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAE7C,eAAO,MAAM,YAAY,UAAW,UAAU,KAAG,SAAS,GAAG,SAK5D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,UAAW,UAAU,uBAAuB,MAAM,KAAG,SAAS,GAAG,SAQ3F,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IAExB,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C,sBAAsB,CAAC,EAAE,4BAA4B,CAAC;IACtD,yBAAyB,CAAC,EAAE,+BAA+B,CAAC;IAC5D,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,kBAAkB,CAAC,EAAE,4BAA4B,CAAC;IAClD,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAE9B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACzD;AAED,eAAO,MAAM,gBAAgB,EAAE,UAI9B,CAAC"}
1
+ {"version":3,"file":"tabsState.d.ts","sourceRoot":"","sources":["../../../../src/model/store/tabs/tabsState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAExE,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,0DAA0D,CAAC;AAC7G,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,KAAK,+BAA+B,EAAE,MAAM,+DAA+D,CAAC;AACrH,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAE7C,eAAO,MAAM,YAAY,UAAW,UAAU,KAAG,SAAS,GAAG,SAK5D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,UAAW,UAAU,uBAAuB,MAAM,KAAG,SAAS,GAAG,SAQ3F,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,UACjB,UAAU,uBACI,MAAM,sCACS,OAAO,6BAChB,OAAO,KACnC,SAAS,EAUX,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IAExB,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C,sBAAsB,CAAC,EAAE,4BAA4B,CAAC;IACtD,yBAAyB,CAAC,EAAE,+BAA+B,CAAC;IAC5D,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,kBAAkB,CAAC,EAAE,4BAA4B,CAAC;IAClD,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAE9B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,wBAAwB,EAAE,CAAC;CACzD;AAED,eAAO,MAAM,gBAAgB,EAAE,UAI9B,CAAC"}
@@ -29,6 +29,26 @@ export const getTabOrActive = (state, tabLocalIdentifier) => {
29
29
  }
30
30
  return getActiveTab(state);
31
31
  };
32
+ /**
33
+ * Tabs a filter or parameter mutation should write.
34
+ *
35
+ * With `persistFiltersAcrossTabs` off, that is the tab addressed by `tabLocalIdentifier`
36
+ * or the active tab. With the flag on, that is every tab, source first. Working-selection
37
+ * edits stay on the source tab so half-applied values are not mirrored.
38
+ *
39
+ * @internal
40
+ */
41
+ export const getTabsToMutate = (state, tabLocalIdentifier, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange) => {
42
+ const source = getTabOrActive(state, tabLocalIdentifier);
43
+ if (!source) {
44
+ return [];
45
+ }
46
+ if (isWorkingSelectionChange || !enablePersistentFiltersAcrossTabs) {
47
+ return [source];
48
+ }
49
+ const others = (state.tabs ?? []).filter((tab) => tab.localIdentifier !== source.localIdentifier);
50
+ return [source, ...others];
51
+ };
32
52
  export const tabsInitialState = {
33
53
  tabs: undefined,
34
54
  activeTabLocalIdentifier: undefined,
@@ -18,9 +18,11 @@ export interface IDashboardCopySourceTab {
18
18
  *
19
19
  * @remarks
20
20
  * The backend accepts a copy that keeps a drill to a dashboard the user cannot open or an ignored
21
- * drill-down hierarchy, so neither counts. It refuses a copy whose filters use a label, metric or fact
22
- * the user may not read: an attribute filter's label, a measure value filter's metric, or a metric or
23
- * fact limiting a filter's values. A merely deleted object does not count.
21
+ * drill-down hierarchy, so neither counts. It refuses a copy whose filters use a label, attribute, metric
22
+ * or fact the user may not read: an attribute filter's label, a measure value filter's metric or
23
+ * "For attribute" item, or a metric, fact or "Count of" attribute limiting a filter's values. A filter
24
+ * restricted only through its parent filter does not count, as it names the parent by local identifier
25
+ * only. A merely deleted object does not count.
24
26
  *
25
27
  * @internal
26
28
  */
@@ -1 +1 @@
1
- {"version":3,"file":"copyBlockingReferences.d.ts","sourceRoot":"","sources":["../../../../src/model/store/unavailableObjects/copyBlockingReferences.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,+BAA+B,EACpC,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EAkBhB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,KAAK,uBAAuB,EAAmC,MAAM,4BAA4B,CAAC;AAK3G,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,MAAM,CAAC,EAAE,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;IACnD,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,sBAAsB,EAAE,+BAA+B,EAAE,CAAC;CAC7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACpC,IAAI,EAAE,uBAAuB,EAAE,EAC/B,QAAQ,EAAE,QAAQ,EAAE,EACpB,kBAAkB,EAAE,8BAA8B,EAAE,GACrD,OAAO,CA8ET;AAED;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,EAAE,iBAAiB,CAAC,OAAO,CAqBtE,CAAC"}
1
+ {"version":3,"file":"copyBlockingReferences.d.ts","sourceRoot":"","sources":["../../../../src/model/store/unavailableObjects/copyBlockingReferences.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,+BAA+B,EACpC,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EAchB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,KAAK,uBAAuB,EAAmC,MAAM,4BAA4B,CAAC;AAQ3G,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,MAAM,CAAC,EAAE,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;IACnD,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,sBAAsB,EAAE,+BAA+B,EAAE,CAAC;CAC7D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACpC,IAAI,EAAE,uBAAuB,EAAE,EAC/B,QAAQ,EAAE,QAAQ,EAAE,EACpB,kBAAkB,EAAE,8BAA8B,EAAE,GACrD,OAAO,CAgET;AAED;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,EAAE,iBAAiB,CAAC,OAAO,CAqBtE,CAAC"}
@@ -1,23 +1,25 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { createSelector } from "@reduxjs/toolkit";
3
- import { areObjRefsEqual, dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemValidateElementsBy, insightRef, isComputedAttributeRef, isDashboardAttributeFilterItem, isDashboardAttributeFilterReference, isDashboardMeasureValueFilter, isDashboardMeasureValueFilterReference, isDrillToDashboard, isInsightWidget, isKpiWidget, isRichTextWidget, isVisualizationSwitcherWidget, } from "@gooddata/sdk-model";
3
+ import { areObjRefsEqual, insightRef, isComputedAttributeRef, isDashboardAttributeFilterReference, isDashboardMeasureValueFilterReference, isDrillToDashboard, isInsightWidget, isKpiWidget, isRichTextWidget, isVisualizationSwitcherWidget, } from "@gooddata/sdk-model";
4
4
  import { collectReferences } from "@gooddata/sdk-ui-kit";
5
5
  import { isExtendedDashboardLayoutWidget } from "../../types/layoutTypes.js";
6
- import { isDashboardObjectRestricted } from "../filtering/restrictedFilterUtils.js";
6
+ import { isDashboardFilterUsingRestrictedObject, isDashboardObjectRestricted, } from "../filtering/restrictedFilterUtils.js";
7
7
  import { selectInsights } from "../insights/insightsSelectors.js";
8
8
  import { selectIsInViewMode } from "../renderMode/renderModeSelectors.js";
9
9
  import { selectTabs } from "../tabs/tabsSelectors.js";
10
10
  import { isDrillRestricted } from "../widgetDrills/drillRestrictionUtils.js";
11
- import { newRestrictedLimitingItemsMap, selectUnavailableObjects } from "./unavailableObjectsSelectors.js";
11
+ import { selectUnavailableObjects } from "./unavailableObjectsSelectors.js";
12
12
  /**
13
13
  * Tells whether a copy of the dashboard would reference an object the current user may not read in a
14
14
  * place the backend checks when it creates the copy, so it would refuse the copy.
15
15
  *
16
16
  * @remarks
17
17
  * The backend accepts a copy that keeps a drill to a dashboard the user cannot open or an ignored
18
- * drill-down hierarchy, so neither counts. It refuses a copy whose filters use a label, metric or fact
19
- * the user may not read: an attribute filter's label, a measure value filter's metric, or a metric or
20
- * fact limiting a filter's values. A merely deleted object does not count.
18
+ * drill-down hierarchy, so neither counts. It refuses a copy whose filters use a label, attribute, metric
19
+ * or fact the user may not read: an attribute filter's label, a measure value filter's metric or
20
+ * "For attribute" item, or a metric, fact or "Count of" attribute limiting a filter's values. A filter
21
+ * restricted only through its parent filter does not count, as it names the parent by local identifier
22
+ * only. A merely deleted object does not count.
21
23
  *
22
24
  * @internal
23
25
  */
@@ -55,20 +57,11 @@ export function hasCopyBlockingReference(tabs, insights, unavailableObjects) {
55
57
  }
56
58
  return false;
57
59
  };
58
- const restrictedLimitingItems = newRestrictedLimitingItemsMap(unavailableObjects);
59
- const isFilterForbidden = (filter) => {
60
- if (isDashboardMeasureValueFilter(filter)) {
61
- return isForbidden(filter.dashboardMeasureValueFilter.measure, "measure");
62
- }
63
- return (isDashboardAttributeFilterItem(filter) &&
64
- (isLabelForbidden(dashboardAttributeFilterItemDisplayForm(filter)) ||
65
- (dashboardAttributeFilterItemValidateElementsBy(filter) ?? []).some((item) => restrictedLimitingItems.has(item))));
66
- };
67
60
  const isSectionForbidden = (section) => isTextForbidden(section.header?.description) ||
68
61
  section.items.some((item) => item.widget !== undefined && isWidgetForbidden(item.widget));
69
62
  return tabs.some((tab) => (tab.layout?.sections ?? []).some(isSectionForbidden) ||
70
63
  tab.attributeFilterConfigs.some((config) => isLabelForbidden(config.displayAsLabel)) ||
71
- tab.filters.some(isFilterForbidden));
64
+ tab.filters.some((filter) => isDashboardFilterUsingRestrictedObject(filter, unavailableObjects)));
72
65
  }
73
66
  /**
74
67
  * Selects whether "Save as new" would fail because the dashboard, as it stands now, references objects