@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
@@ -1,10 +1,11 @@
1
1
  // (C) 2021-2026 GoodData Corporation
2
2
  import { invariant } from "ts-invariant";
3
- import { areObjRefsEqual, attributeElementsIsEmpty, dashboardAttributeFilterItemFilterElementsBy, dashboardAttributeFilterItemFilterElementsByDate, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemValidateElementsBy, dashboardFilterLocalIdentifier, isAttributeElementsByRef, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMatchAttributeFilter, isDashboardMeasureValueFilter, newAllTimeDashboardDateFilter, } from "@gooddata/sdk-model";
3
+ import { areObjRefsEqual, attributeElementsIsEmpty, dashboardAttributeFilterItemLocalIdentifier, dashboardFilterLocalIdentifier, isAllTimeDashboardDateFilter, isAttributeElementsByRef, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMatchAttributeFilter, isDashboardMeasureValueFilter, newAllTimeDashboardDateFilter, } from "@gooddata/sdk-model";
4
4
  import { generateFilterLocalIdentifier, generateMeasureValueFilterLocalIdentifier, } from "../../_infra/generators.js";
5
- import { getActiveTab, getTabOrActive } from "../tabsState.js";
5
+ import { getActiveTab, getTabOrActive, getTabsToMutate, } from "../tabsState.js";
6
6
  import { filterContextInitialState } from "./filterContextState.js";
7
7
  import { applyFilterContext, initializeFilterContext } from "./filterContextUtils.js";
8
+ import { findCounterpartFilter, findOriginalFilter, isCrossFilteringVirtualFilter, preserveFilterDependencies, retargetAttributeFilterItem, } from "./filterSync.js";
8
9
  const setFilterContext = (state, action) => {
9
10
  const activeTab = getActiveTab(state);
10
11
  if (!activeTab) {
@@ -91,18 +92,90 @@ const setPreloadedAttributesWithReferences = (state, action) => {
91
92
  state.attributesWithReferences = action.payload;
92
93
  };
93
94
  const upsertDateFilter = (state, action) => {
94
- const targetTab = getTabOrActive(state, action.payload.tabLocalIdentifier);
95
- if (!targetTab) {
95
+ const { tabLocalIdentifier, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange } = action.payload;
96
+ const tabs = getTabsToMutate(state, tabLocalIdentifier, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange);
97
+ const source = tabs[0];
98
+ if (!source) {
96
99
  return;
97
100
  }
101
+ upsertDateFilterOnTab(source, action.payload, true);
102
+ for (const tab of tabs.slice(1)) {
103
+ const payload = action.payload.restoreTargetOriginal
104
+ ? restoredDateFilterPayload(tab, action.payload)
105
+ : action.payload;
106
+ if (!payload) {
107
+ continue;
108
+ }
109
+ upsertDateFilterOnTab(tab, payload, false);
110
+ }
111
+ };
112
+ /**
113
+ * Saved date filter for a non-source tab during reset.
114
+ * A common date filter with no saved original is all time, which is stored as the filter's absence.
115
+ * A dimension date filter with no counterpart, or no saved original, is left unchanged.
116
+ */
117
+ function restoredDateFilterPayload(tab, payload) {
118
+ const applied = findAppliedDateFilter(tab, payload.dataSet);
119
+ if (!applied && payload.dataSet) {
120
+ return undefined;
121
+ }
122
+ const original = findOriginalFilter(tab, applied ?? {
123
+ dateFilter: { type: "relative", granularity: "GDC.time.date" },
124
+ });
125
+ if (isDashboardDateFilter(original)) {
126
+ return toUpsertDateFilterPayload(original, payload);
127
+ }
128
+ if (!payload.dataSet) {
129
+ return { type: "allTime" };
130
+ }
131
+ return undefined;
132
+ }
133
+ function findAppliedDateFilter(tab, dataSet) {
134
+ const filters = tab.filterContext?.filterContextDefinition?.filters;
135
+ if (!filters) {
136
+ return undefined;
137
+ }
138
+ if (dataSet) {
139
+ return filters.find((item) => isDashboardDateFilter(item) && areObjRefsEqual(item.dateFilter.dataSet, dataSet));
140
+ }
141
+ return filters.find(isDashboardCommonDateFilter);
142
+ }
143
+ function toUpsertDateFilterPayload(filter, source) {
144
+ const { dataSet, localIdentifier, emptyValueHandling, boundedFilter, type, granularity, from, to } = filter.dateFilter;
145
+ const shared = {
146
+ ...(dataSet ? { dataSet } : {}),
147
+ ...(localIdentifier ? { localIdentifier } : {}),
148
+ ...(emptyValueHandling ? { emptyValueHandling } : {}),
149
+ ...(source.isWorkingSelectionChange ? { isWorkingSelectionChange: true } : {}),
150
+ ...(source.tabLocalIdentifier ? { tabLocalIdentifier: source.tabLocalIdentifier } : {}),
151
+ };
152
+ if (isAllTimeDashboardDateFilter(filter)) {
153
+ return { type: "allTime", ...shared };
154
+ }
155
+ return {
156
+ type,
157
+ granularity,
158
+ from,
159
+ to,
160
+ ...(boundedFilter ? { boundedFilter } : {}),
161
+ ...shared,
162
+ };
163
+ }
164
+ function upsertDateFilterOnTab(targetTab, payload, isSource) {
98
165
  if (!targetTab.filterContext) {
166
+ if (!isSource) {
167
+ return;
168
+ }
99
169
  targetTab.filterContext = { ...filterContextInitialState };
100
170
  }
101
- const filterContextDefinition = action.payload.isWorkingSelectionChange
171
+ const filterContextDefinition = payload.isWorkingSelectionChange
102
172
  ? targetTab.filterContext.workingFilterContextDefinition
103
173
  : targetTab.filterContext.filterContextDefinition;
174
+ if (!isSource && !filterContextDefinition?.filters) {
175
+ return;
176
+ }
104
177
  invariant(filterContextDefinition?.filters, "Attempt to edit uninitialized filter context");
105
- const dateDataSet = action.payload.dataSet;
178
+ const dateDataSet = payload.dataSet;
106
179
  let existingFilterIndex = -1;
107
180
  if (dateDataSet) {
108
181
  existingFilterIndex = filterContextDefinition.filters.findIndex((item) => isDashboardDateFilter(item) && areObjRefsEqual(item.dateFilter.dataSet, dateDataSet));
@@ -110,30 +183,39 @@ const upsertDateFilter = (state, action) => {
110
183
  else {
111
184
  existingFilterIndex = filterContextDefinition.filters.findIndex((item) => isDashboardCommonDateFilter(item));
112
185
  }
113
- if (action.payload.isWorkingSelectionChange && action.payload.type === "allTime") {
186
+ if (!isSource && existingFilterIndex < 0 && dateDataSet) {
187
+ return;
188
+ }
189
+ if (payload.isWorkingSelectionChange && payload.type === "allTime") {
114
190
  // we need to allways set working filter to distinguish between "not changed" and "all time" filter
115
191
  if (existingFilterIndex >= 0) {
116
192
  const dateFilter = filterContextDefinition.filters[existingFilterIndex];
117
193
  if (isDashboardDateFilter(dateFilter)) {
118
- filterContextDefinition.filters[existingFilterIndex] = newAllTimeDashboardDateFilter(dateFilter.dateFilter.dataSet, dateFilter.dateFilter.localIdentifier, action.payload.emptyValueHandling);
194
+ filterContextDefinition.filters[existingFilterIndex] = newAllTimeDashboardDateFilter(dateFilter.dateFilter.dataSet, dateFilter.dateFilter.localIdentifier, payload.emptyValueHandling);
119
195
  }
120
196
  }
121
197
  else {
122
- filterContextDefinition.filters.unshift(newAllTimeDashboardDateFilter(action.payload.dataSet, action.payload.localIdentifier, action.payload.emptyValueHandling));
198
+ filterContextDefinition.filters.unshift(newAllTimeDashboardDateFilter(payload.dataSet, payload.localIdentifier, payload.emptyValueHandling));
123
199
  }
124
200
  }
125
- else if (action.payload.type === "allTime") {
201
+ else if (payload.type === "allTime") {
126
202
  if (existingFilterIndex >= 0) {
127
203
  if (dateDataSet) {
128
204
  const dateFilter = filterContextDefinition.filters[existingFilterIndex];
129
205
  if (isDashboardDateFilter(dateFilter)) {
130
- filterContextDefinition.filters[existingFilterIndex] = newAllTimeDashboardDateFilter(dateFilter.dateFilter.dataSet, dateFilter.dateFilter.localIdentifier, action.payload.emptyValueHandling);
206
+ filterContextDefinition.filters[existingFilterIndex] = newAllTimeDashboardDateFilter(dateFilter.dateFilter.dataSet, dateFilter.dateFilter.localIdentifier, payload.emptyValueHandling);
131
207
  }
132
208
  }
133
209
  else {
134
- if (action.payload.emptyValueHandling) {
210
+ const existing = filterContextDefinition.filters[existingFilterIndex];
211
+ const preservedLocalIdentifier = isSource
212
+ ? payload.localIdentifier
213
+ : isDashboardDateFilter(existing)
214
+ ? existing.dateFilter.localIdentifier
215
+ : undefined;
216
+ if (payload.emptyValueHandling) {
135
217
  // Preserve all-time common DF when it carries additional config (e.g. emptyValueHandling).
136
- filterContextDefinition.filters[existingFilterIndex] = newAllTimeDashboardDateFilter(undefined, action.payload.localIdentifier, action.payload.emptyValueHandling);
218
+ filterContextDefinition.filters[existingFilterIndex] = newAllTimeDashboardDateFilter(undefined, preservedLocalIdentifier, payload.emptyValueHandling);
137
219
  }
138
220
  else {
139
221
  // if allTime common DF remove the date filter altogether
@@ -141,20 +223,20 @@ const upsertDateFilter = (state, action) => {
141
223
  }
142
224
  }
143
225
  }
144
- else if (!dateDataSet && action.payload.emptyValueHandling) {
226
+ else if (!dateDataSet && payload.emptyValueHandling) {
145
227
  // No common date filter existed (all time was represented by absence); create it to preserve extra config.
146
- filterContextDefinition.filters.unshift(newAllTimeDashboardDateFilter(undefined, action.payload.localIdentifier, action.payload.emptyValueHandling));
228
+ filterContextDefinition.filters.unshift(newAllTimeDashboardDateFilter(undefined, payload.localIdentifier, payload.emptyValueHandling));
147
229
  }
148
230
  }
149
231
  else if (existingFilterIndex >= 0) {
150
- const { type, granularity, from, to, localIdentifier, boundedFilter, emptyValueHandling } = action.payload;
232
+ const { type, granularity, from, to, localIdentifier, boundedFilter, emptyValueHandling } = payload;
151
233
  const dateFilter = filterContextDefinition.filters[existingFilterIndex];
152
234
  if (isDashboardDateFilter(dateFilter)) {
153
235
  dateFilter.dateFilter.type = type;
154
236
  dateFilter.dateFilter.granularity = granularity;
155
237
  dateFilter.dateFilter.from = from;
156
238
  dateFilter.dateFilter.to = to;
157
- if (localIdentifier) {
239
+ if (localIdentifier && isSource) {
158
240
  dateFilter.dateFilter.localIdentifier = localIdentifier;
159
241
  }
160
242
  if (boundedFilter) {
@@ -171,8 +253,8 @@ const upsertDateFilter = (state, action) => {
171
253
  }
172
254
  }
173
255
  }
174
- else {
175
- const { type, granularity, from, to, dataSet, localIdentifier, boundedFilter, emptyValueHandling } = action.payload;
256
+ else if (isSource || !dateDataSet) {
257
+ const { type, granularity, from, to, dataSet, localIdentifier, boundedFilter, emptyValueHandling } = payload;
176
258
  filterContextDefinition.filters.unshift({
177
259
  dateFilter: {
178
260
  granularity,
@@ -186,21 +268,71 @@ const upsertDateFilter = (state, action) => {
186
268
  },
187
269
  });
188
270
  }
189
- };
271
+ }
190
272
  const updateAttributeFilterSelection = (state, action) => {
191
- const targetTab = getTabOrActive(state, action.payload.tabLocalIdentifier);
192
- if (!targetTab) {
273
+ const { tabLocalIdentifier, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange, crossFilteringLocalIdsByTab, } = action.payload;
274
+ const tabs = getTabsToMutate(state, tabLocalIdentifier, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange);
275
+ const source = tabs[0];
276
+ if (!source) {
277
+ return;
278
+ }
279
+ const sourceFilter = findAppliedFilterByLocalId(source, action.payload.filterLocalId);
280
+ updateAttributeFilterSelectionOnTab(source, action.payload, action.payload.filterLocalId, true);
281
+ if (tabs.length === 1 ||
282
+ !sourceFilter ||
283
+ !isDashboardAttributeFilter(sourceFilter) ||
284
+ isCrossFilteringVirtualFilter(source, action.payload.filterLocalId, crossFilteringLocalIdsByTab)) {
193
285
  return;
194
286
  }
287
+ for (const tab of tabs.slice(1)) {
288
+ const match = findCounterpartFilter(source, sourceFilter, tab, state.attributesWithReferences, {
289
+ allowListToText: false,
290
+ crossFilteringLocalIdsByTab,
291
+ });
292
+ if (!match || !isDashboardAttributeFilter(match)) {
293
+ continue;
294
+ }
295
+ const targetLocalId = match.attributeFilter.localIdentifier;
296
+ if (!targetLocalId) {
297
+ continue;
298
+ }
299
+ const original = action.payload.restoreTargetOriginal ? findOriginalFilter(tab, match) : undefined;
300
+ if (action.payload.restoreTargetOriginal && !isDashboardAttributeFilter(original)) {
301
+ continue;
302
+ }
303
+ updateAttributeFilterSelectionOnTab(tab, {
304
+ ...action.payload,
305
+ filterLocalId: targetLocalId,
306
+ ...(isDashboardAttributeFilter(original)
307
+ ? {
308
+ elements: original.attributeFilter.attributeElements,
309
+ negativeSelection: original.attributeFilter.negativeSelection,
310
+ }
311
+ : {}),
312
+ enableImmediateAttributeFilterDisplayAsLabelMigration: false,
313
+ isResultOfMigration: false,
314
+ }, targetLocalId, false);
315
+ }
316
+ };
317
+ function updateAttributeFilterSelectionOnTab(targetTab, payload, filterLocalId, isSource) {
195
318
  if (!targetTab.filterContext) {
319
+ if (!isSource) {
320
+ return;
321
+ }
196
322
  targetTab.filterContext = { ...filterContextInitialState };
197
323
  }
198
- const { elements, filterLocalId, negativeSelection, isWorkingSelectionChange, enableImmediateAttributeFilterDisplayAsLabelMigration, isResultOfMigration, isSelectionInvalid, } = action.payload;
324
+ const { elements, negativeSelection, isWorkingSelectionChange, enableImmediateAttributeFilterDisplayAsLabelMigration, isResultOfMigration, isSelectionInvalid, } = payload;
199
325
  const filterContextDefinition = isWorkingSelectionChange
200
326
  ? targetTab.filterContext.workingFilterContextDefinition
201
327
  : targetTab.filterContext.filterContextDefinition;
328
+ if (!isSource && !filterContextDefinition?.filters) {
329
+ return;
330
+ }
202
331
  invariant(filterContextDefinition?.filters, "Attempt to edit uninitialized filter context");
203
332
  const existingFilterIndex = filterContextDefinition.filters.findIndex((item) => isDashboardAttributeFilter(item) && item.attributeFilter.localIdentifier === filterLocalId);
333
+ if (!isSource && existingFilterIndex < 0) {
334
+ return;
335
+ }
204
336
  invariant(existingFilterIndex >= 0 || isWorkingSelectionChange, "Attempt to update non-existing filter");
205
337
  if (isWorkingSelectionChange && existingFilterIndex >= 0) {
206
338
  filterContextDefinition.filters[existingFilterIndex] = {
@@ -241,6 +373,9 @@ const updateAttributeFilterSelection = (state, action) => {
241
373
  };
242
374
  }
243
375
  }
376
+ if (!targetTab.filterContext.filtersWithInvalidSelection) {
377
+ targetTab.filterContext.filtersWithInvalidSelection = [];
378
+ }
244
379
  // Handle isSelectionInvalid flag to update filtersWithInvalidSelection array
245
380
  if (isSelectionInvalid) {
246
381
  // Add filterLocalId to the array if not already present
@@ -253,58 +388,65 @@ const updateAttributeFilterSelection = (state, action) => {
253
388
  targetTab.filterContext.filtersWithInvalidSelection =
254
389
  targetTab.filterContext.filtersWithInvalidSelection.filter((id) => id !== filterLocalId);
255
390
  }
256
- };
257
- /**
258
- * Carries over filterElementsBy, filterElementsByDate, and validateElementsBy from the existing filter
259
- * to the replacement filter, so that parent filter dependencies are not lost
260
- * when filter selection/params change.
261
- */
262
- function preserveFilterDependencies(existing, replacement) {
263
- const filterElementsBy = dashboardAttributeFilterItemFilterElementsBy(existing);
264
- const filterElementsByDate = dashboardAttributeFilterItemFilterElementsByDate(existing);
265
- const validateElementsBy = dashboardAttributeFilterItemValidateElementsBy(existing);
266
- if (!filterElementsBy?.length && !filterElementsByDate?.length && !validateElementsBy?.length) {
267
- return replacement;
268
- }
269
- const preserved = {
270
- ...(filterElementsBy?.length ? { filterElementsBy } : {}),
271
- ...(filterElementsByDate?.length ? { filterElementsByDate } : {}),
272
- ...(validateElementsBy?.length ? { validateElementsBy } : {}),
273
- };
274
- if (isDashboardAttributeFilter(replacement)) {
275
- return {
276
- attributeFilter: {
277
- ...replacement.attributeFilter,
278
- ...preserved,
279
- },
280
- };
281
- }
282
- if (isDashboardArbitraryAttributeFilter(replacement)) {
283
- return {
284
- arbitraryAttributeFilter: {
285
- ...replacement.arbitraryAttributeFilter,
286
- ...preserved,
287
- },
288
- };
289
- }
290
- // Match filters do not support parent dependencies
291
- return replacement;
391
+ }
392
+ function findAppliedFilterByLocalId(tab, localIdentifier) {
393
+ return tab.filterContext?.filterContextDefinition?.filters.find((item) => dashboardFilterLocalIdentifier(item) === localIdentifier);
292
394
  }
293
395
  const replaceAttributeFilterItem = (state, action) => {
294
- const targetTab = getTabOrActive(state, action.payload.tabLocalIdentifier);
295
- if (!targetTab) {
396
+ const { tabLocalIdentifier, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange, crossFilteringLocalIdsByTab, } = action.payload;
397
+ const tabs = getTabsToMutate(state, tabLocalIdentifier, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange);
398
+ const source = tabs[0];
399
+ if (!source) {
296
400
  return;
297
401
  }
402
+ const sourceFilter = findAppliedFilterByLocalId(source, action.payload.filterLocalId);
403
+ replaceAttributeFilterItemOnTab(source, action.payload, action.payload.filter, true);
404
+ if (tabs.length === 1 ||
405
+ !sourceFilter ||
406
+ !isDashboardAttributeFilterItem(sourceFilter) ||
407
+ isCrossFilteringVirtualFilter(source, action.payload.filterLocalId, crossFilteringLocalIdsByTab)) {
408
+ return;
409
+ }
410
+ for (const tab of tabs.slice(1)) {
411
+ const match = findCounterpartFilter(source, sourceFilter, tab, state.attributesWithReferences, {
412
+ allowListToText: true,
413
+ crossFilteringLocalIdsByTab,
414
+ });
415
+ if (!match || !isDashboardAttributeFilterItem(match)) {
416
+ continue;
417
+ }
418
+ const targetLocalId = dashboardAttributeFilterItemLocalIdentifier(match);
419
+ if (!targetLocalId) {
420
+ continue;
421
+ }
422
+ const original = action.payload.restoreTargetOriginal ? findOriginalFilter(tab, match) : undefined;
423
+ if (action.payload.restoreTargetOriginal && !isDashboardAttributeFilterItem(original)) {
424
+ continue;
425
+ }
426
+ const replacement = isDashboardAttributeFilterItem(original) ? original : action.payload.filter;
427
+ replaceAttributeFilterItemOnTab(tab, { ...action.payload, filterLocalId: targetLocalId }, retargetAttributeFilterItem(replacement, targetLocalId), false);
428
+ }
429
+ };
430
+ function replaceAttributeFilterItemOnTab(targetTab, payload, filter, isSource) {
298
431
  if (!targetTab.filterContext) {
432
+ if (!isSource) {
433
+ return;
434
+ }
299
435
  targetTab.filterContext = { ...filterContextInitialState };
300
436
  }
301
- const { filterLocalId, filter, isWorkingSelectionChange, isSelectionInvalid } = action.payload;
437
+ const { filterLocalId, isWorkingSelectionChange, isSelectionInvalid } = payload;
302
438
  const filterContextDefinition = isWorkingSelectionChange
303
439
  ? targetTab.filterContext.workingFilterContextDefinition
304
440
  : targetTab.filterContext.filterContextDefinition;
441
+ if (!isSource && !filterContextDefinition?.filters) {
442
+ return;
443
+ }
305
444
  invariant(filterContextDefinition?.filters, "Attempt to edit uninitialized filter context");
306
445
  const existingFilterIndex = filterContextDefinition.filters.findIndex((item) => isDashboardAttributeFilterItem(item) &&
307
446
  dashboardAttributeFilterItemLocalIdentifier(item) === filterLocalId);
447
+ if (!isSource && existingFilterIndex < 0) {
448
+ return;
449
+ }
308
450
  // Preserve parent filter relationships from the existing filter when replacing.
309
451
  // The replacement filter typically only carries selection data, not dependency configuration.
310
452
  const replacementFilter = existingFilterIndex >= 0
@@ -323,6 +465,9 @@ const replaceAttributeFilterItem = (state, action) => {
323
465
  else if (existingFilterIndex >= 0) {
324
466
  filters[existingFilterIndex] = replacementFilter;
325
467
  }
468
+ if (!targetTab.filterContext.filtersWithInvalidSelection) {
469
+ targetTab.filterContext.filtersWithInvalidSelection = [];
470
+ }
326
471
  // Handle isSelectionInvalid flag to update filtersWithInvalidSelection array
327
472
  if (isSelectionInvalid) {
328
473
  if (!targetTab.filterContext.filtersWithInvalidSelection.includes(filterLocalId)) {
@@ -333,7 +478,7 @@ const replaceAttributeFilterItem = (state, action) => {
333
478
  targetTab.filterContext.filtersWithInvalidSelection =
334
479
  targetTab.filterContext.filtersWithInvalidSelection.filter((id) => id !== filterLocalId);
335
480
  }
336
- };
481
+ }
337
482
  const addAttributeFilter = (state, action) => {
338
483
  const activeTab = getActiveTab(state);
339
484
  if (!activeTab) {
@@ -496,26 +641,71 @@ const setAttributeFilterDependentDateFilters = (state, action) => {
496
641
  // Match filters do not support filterElementsByDate
497
642
  };
498
643
  const clearAttributeFiltersSelection = (state, action) => {
499
- const targetTab = getTabOrActive(state, action.payload.tabLocalIdentifier);
500
- if (!targetTab) {
644
+ const { tabLocalIdentifier, enablePersistentFiltersAcrossTabs, filterLocalIds, crossFilteringLocalIdsByTab, } = action.payload;
645
+ const tabs = getTabsToMutate(state, tabLocalIdentifier, enablePersistentFiltersAcrossTabs);
646
+ const source = tabs[0];
647
+ if (!source) {
501
648
  return;
502
649
  }
503
- if (!targetTab.filterContext) {
504
- targetTab.filterContext = { ...filterContextInitialState };
650
+ if (!source.filterContext) {
651
+ source.filterContext = { ...filterContextInitialState };
505
652
  }
506
- const { filterLocalIds } = action.payload;
653
+ const sourceFilters = filterLocalIds.map((filterLocalId) => ({
654
+ filterLocalId,
655
+ filter: findAppliedFilterByLocalId(source, filterLocalId),
656
+ }));
507
657
  filterLocalIds.forEach((filterLocalId) => {
508
- invariant(targetTab.filterContext?.filterContextDefinition, "Attempt to edit uninitialized filter context");
509
- const currentFilterIndex = targetTab.filterContext.filterContextDefinition.filters.findIndex((item) => isDashboardAttributeFilter(item) && item.attributeFilter.localIdentifier === filterLocalId);
510
- invariant(currentFilterIndex >= 0, "Attempt to clear selection of a non-existing filter");
511
- const currentFilter = targetTab.filterContext.filterContextDefinition.filters[currentFilterIndex];
512
- currentFilter.attributeFilter.negativeSelection =
513
- currentFilter.attributeFilter.selectionMode !== "single";
514
- currentFilter.attributeFilter.attributeElements = isAttributeElementsByRef(currentFilter.attributeFilter.attributeElements)
515
- ? { uris: [] }
516
- : { values: [] };
658
+ clearAttributeFilterSelectionOnTab(source, filterLocalId, true);
517
659
  });
660
+ for (const tab of tabs.slice(1)) {
661
+ for (const { filterLocalId, filter } of sourceFilters) {
662
+ if (!filter ||
663
+ !isDashboardAttributeFilter(filter) ||
664
+ isCrossFilteringVirtualFilter(source, filterLocalId, crossFilteringLocalIdsByTab)) {
665
+ continue;
666
+ }
667
+ const match = findCounterpartFilter(source, filter, tab, state.attributesWithReferences, {
668
+ allowListToText: false,
669
+ crossFilteringLocalIdsByTab,
670
+ });
671
+ if (!match || !isDashboardAttributeFilter(match) || !match.attributeFilter.localIdentifier) {
672
+ continue;
673
+ }
674
+ const targetLocalId = match.attributeFilter.localIdentifier;
675
+ if (action.payload.restoreTargetOriginal) {
676
+ const original = findOriginalFilter(tab, match);
677
+ if (!isDashboardAttributeFilter(original)) {
678
+ continue;
679
+ }
680
+ updateAttributeFilterSelectionOnTab(tab, {
681
+ filterLocalId: targetLocalId,
682
+ elements: original.attributeFilter.attributeElements,
683
+ negativeSelection: original.attributeFilter.negativeSelection,
684
+ }, targetLocalId, false);
685
+ continue;
686
+ }
687
+ clearAttributeFilterSelectionOnTab(tab, targetLocalId, false);
688
+ }
689
+ }
518
690
  };
691
+ function clearAttributeFilterSelectionOnTab(tab, filterLocalId, isSource) {
692
+ const filters = tab.filterContext?.filterContextDefinition?.filters;
693
+ if (!isSource && !filters) {
694
+ return;
695
+ }
696
+ invariant(tab.filterContext?.filterContextDefinition, "Attempt to edit uninitialized filter context");
697
+ const currentFilterIndex = tab.filterContext.filterContextDefinition.filters.findIndex((item) => isDashboardAttributeFilter(item) && item.attributeFilter.localIdentifier === filterLocalId);
698
+ if (!isSource && currentFilterIndex < 0) {
699
+ return;
700
+ }
701
+ invariant(currentFilterIndex >= 0, "Attempt to clear selection of a non-existing filter");
702
+ const currentFilter = tab.filterContext.filterContextDefinition.filters[currentFilterIndex];
703
+ currentFilter.attributeFilter.negativeSelection =
704
+ currentFilter.attributeFilter.selectionMode !== "single";
705
+ currentFilter.attributeFilter.attributeElements = isAttributeElementsByRef(currentFilter.attributeFilter.attributeElements)
706
+ ? { uris: [] }
707
+ : { values: [] };
708
+ }
519
709
  /**
520
710
  * Changes the display form for the filter given by its local identifier.
521
711
  */
@@ -662,7 +852,9 @@ const addDateFilter = (state, action) => {
662
852
  //
663
853
  //
664
854
  const addMeasureValueFilter = (state, action) => {
665
- const activeTab = getActiveTab(state);
855
+ // A new measure value filter has no counterpart yet. Creating one on other tabs would be a
856
+ // phantom filter, so the add stays on the source tab even when sync is on.
857
+ const activeTab = getTabsToMutate(state)[0];
666
858
  if (!activeTab) {
667
859
  return;
668
860
  }
@@ -820,11 +1012,9 @@ export function clearFilterContextWorkingValues(tab) {
820
1012
  tab.filterContext.workingFilterContextDefinition = { filters: [] };
821
1013
  }
822
1014
  const resetWorkingFilterSelection = (state) => {
823
- const activeTab = getActiveTab(state);
824
- if (!activeTab) {
825
- return;
1015
+ for (const tab of getTabsToMutate(state)) {
1016
+ clearFilterContextWorkingValues(tab);
826
1017
  }
827
- clearFilterContextWorkingValues(activeTab);
828
1018
  };
829
1019
  //
830
1020
  //
@@ -840,21 +1030,59 @@ const setDefaultFilterOverrides = (state, action) => {
840
1030
  activeTab.filterContext.defaultFilterOverrides = action.payload;
841
1031
  };
842
1032
  const changeMeasureValueFilterCondition = (state, action) => {
843
- const activeTab = getActiveTab(state);
844
- if (!activeTab) {
1033
+ const { localIdentifier, isWorkingSelectionChange, enablePersistentFiltersAcrossTabs } = action.payload;
1034
+ const tabs = getTabsToMutate(state, undefined, enablePersistentFiltersAcrossTabs, isWorkingSelectionChange);
1035
+ const source = tabs[0];
1036
+ if (!source) {
845
1037
  return;
846
1038
  }
847
- if (!activeTab.filterContext) {
848
- activeTab.filterContext = { ...filterContextInitialState };
1039
+ const sourceFilter = findAppliedFilterByLocalId(source, localIdentifier);
1040
+ changeMeasureValueFilterConditionOnTab(source, action.payload, localIdentifier, true);
1041
+ if (!sourceFilter || !isDashboardMeasureValueFilter(sourceFilter)) {
1042
+ return;
1043
+ }
1044
+ for (const tab of tabs.slice(1)) {
1045
+ const match = findCounterpartFilter(source, sourceFilter, tab, state.attributesWithReferences);
1046
+ const targetLocalId = match ? dashboardFilterLocalIdentifier(match) : undefined;
1047
+ if (!targetLocalId || !match) {
1048
+ continue;
1049
+ }
1050
+ if (action.payload.restoreTargetOriginal) {
1051
+ const original = findOriginalFilter(tab, match);
1052
+ if (!isDashboardMeasureValueFilter(original)) {
1053
+ continue;
1054
+ }
1055
+ changeMeasureValueFilterConditionOnTab(tab, {
1056
+ ...action.payload,
1057
+ localIdentifier: targetLocalId,
1058
+ conditions: original.dashboardMeasureValueFilter.conditions,
1059
+ }, targetLocalId, false);
1060
+ continue;
1061
+ }
1062
+ changeMeasureValueFilterConditionOnTab(tab, action.payload, targetLocalId, false);
849
1063
  }
850
- const { localIdentifier, conditions, isWorkingSelectionChange } = action.payload;
1064
+ };
1065
+ function changeMeasureValueFilterConditionOnTab(tab, payload, localIdentifier, isSource) {
1066
+ if (!tab.filterContext) {
1067
+ if (!isSource) {
1068
+ return;
1069
+ }
1070
+ tab.filterContext = { ...filterContextInitialState };
1071
+ }
1072
+ const { conditions, isWorkingSelectionChange } = payload;
851
1073
  const filterContextDefinition = isWorkingSelectionChange
852
- ? activeTab.filterContext?.workingFilterContextDefinition
853
- : activeTab.filterContext?.filterContextDefinition;
1074
+ ? tab.filterContext?.workingFilterContextDefinition
1075
+ : tab.filterContext?.filterContextDefinition;
1076
+ if (!isSource && !filterContextDefinition?.filters) {
1077
+ return;
1078
+ }
854
1079
  invariant(filterContextDefinition?.filters, "Attempt to edit uninitialized filter context");
855
1080
  const filters = filterContextDefinition.filters;
856
1081
  const index = filters.findIndex((item) => isDashboardMeasureValueFilter(item) && dashboardFilterLocalIdentifier(item) === localIdentifier);
857
1082
  const hasConditions = !!conditions && conditions.length > 0;
1083
+ if (!isSource && index < 0) {
1084
+ return;
1085
+ }
858
1086
  invariant(index >= 0 || isWorkingSelectionChange, `Attempt to change condition of measure value filter ${localIdentifier} that does not exist`);
859
1087
  if (isWorkingSelectionChange && index < 0) {
860
1088
  const workingFilter = {
@@ -863,7 +1091,7 @@ const changeMeasureValueFilterCondition = (state, action) => {
863
1091
  conditions: hasConditions ? conditions : undefined,
864
1092
  },
865
1093
  };
866
- activeTab.filterContext.workingFilterContextDefinition.filters.push(workingFilter);
1094
+ tab.filterContext.workingFilterContextDefinition.filters.push(workingFilter);
867
1095
  return;
868
1096
  }
869
1097
  const filter = filters[index];
@@ -873,28 +1101,60 @@ const changeMeasureValueFilterCondition = (state, action) => {
873
1101
  conditions: hasConditions ? conditions : undefined,
874
1102
  },
875
1103
  };
876
- };
1104
+ }
877
1105
  const setMeasureValueFilterDimensionality = (state, action) => {
878
- const activeTab = getActiveTab(state);
879
- if (!activeTab) {
1106
+ const { localIdentifier, restoreTargetOriginal, enablePersistentFiltersAcrossTabs } = action.payload;
1107
+ const tabs = getTabsToMutate(state, undefined, enablePersistentFiltersAcrossTabs);
1108
+ const source = tabs[0];
1109
+ if (!source) {
880
1110
  return;
881
1111
  }
882
- invariant(activeTab.filterContext?.filterContextDefinition?.filters, "Attempt to edit uninitialized filter context");
883
- const filters = activeTab.filterContext.filterContextDefinition.filters;
884
- const index = filters.findIndex((item) => isDashboardMeasureValueFilter(item) &&
885
- dashboardFilterLocalIdentifier(item) === action.payload.localIdentifier);
886
- invariant(index >= 0, `Attempt to change dimensionality of measure value filter ${action.payload.localIdentifier} that does not exist`);
1112
+ const sourceFilter = findAppliedFilterByLocalId(source, localIdentifier);
1113
+ setMeasureValueFilterDimensionalityOnTab(source, action.payload, localIdentifier, true);
1114
+ if (!sourceFilter || !isDashboardMeasureValueFilter(sourceFilter)) {
1115
+ return;
1116
+ }
1117
+ for (const tab of tabs.slice(1)) {
1118
+ const match = findCounterpartFilter(source, sourceFilter, tab, state.attributesWithReferences);
1119
+ const targetLocalId = match ? dashboardFilterLocalIdentifier(match) : undefined;
1120
+ if (!targetLocalId || !match) {
1121
+ continue;
1122
+ }
1123
+ if (restoreTargetOriginal) {
1124
+ const original = findOriginalFilter(tab, match);
1125
+ if (!isDashboardMeasureValueFilter(original)) {
1126
+ continue;
1127
+ }
1128
+ setMeasureValueFilterDimensionalityOnTab(tab, {
1129
+ ...action.payload,
1130
+ localIdentifier: targetLocalId,
1131
+ dimensionality: original.dashboardMeasureValueFilter.dimensionality,
1132
+ }, targetLocalId, false);
1133
+ continue;
1134
+ }
1135
+ setMeasureValueFilterDimensionalityOnTab(tab, action.payload, targetLocalId, false);
1136
+ }
1137
+ };
1138
+ function setMeasureValueFilterDimensionalityOnTab(tab, payload, localIdentifier, isSource) {
1139
+ const filters = tab.filterContext?.filterContextDefinition?.filters;
1140
+ if (!isSource && !filters) {
1141
+ return;
1142
+ }
1143
+ invariant(filters, "Attempt to edit uninitialized filter context");
1144
+ const index = filters.findIndex((item) => isDashboardMeasureValueFilter(item) && dashboardFilterLocalIdentifier(item) === localIdentifier);
1145
+ if (!isSource && index < 0) {
1146
+ return;
1147
+ }
1148
+ invariant(index >= 0, `Attempt to change dimensionality of measure value filter ${localIdentifier} that does not exist`);
887
1149
  const filter = filters[index];
888
- const dimensionality = action.payload.dimensionality && action.payload.dimensionality.length > 0
889
- ? action.payload.dimensionality
890
- : undefined;
1150
+ const dimensionality = payload.dimensionality && payload.dimensionality.length > 0 ? payload.dimensionality : undefined;
891
1151
  filters[index] = {
892
1152
  dashboardMeasureValueFilter: {
893
1153
  ...filter.dashboardMeasureValueFilter,
894
1154
  dimensionality,
895
1155
  },
896
1156
  };
897
- };
1157
+ }
898
1158
  const changeMeasureValueFilterTitle = (state, action) => {
899
1159
  const activeTab = getActiveTab(state);
900
1160
  if (!activeTab) {