@gooddata/sdk-ui-dashboard 11.35.0-alpha.6 → 11.35.0-alpha.7

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 (34) hide show
  1. package/esm/__version.d.ts +1 -1
  2. package/esm/__version.js +1 -1
  3. package/esm/_staging/dashboard/dashboardFilterContext.js +18 -1
  4. package/esm/_staging/sharedHooks/useFiltersNamings.d.ts +1 -1
  5. package/esm/_staging/sharedHooks/useFiltersNamings.js +32 -5
  6. package/esm/index.d.ts +1 -1
  7. package/esm/index.js +1 -1
  8. package/esm/kdaDialog/dialog/hooks/useChangeAnalysis.js +21 -5
  9. package/esm/kdaDialog/internalTypes.d.ts +6 -1
  10. package/esm/kdaDialog/providers/Kda.js +4 -1
  11. package/esm/kdaDialog/providers/KdaState.js +1 -0
  12. package/esm/kdaDialog/types.d.ts +13 -2
  13. package/esm/model/commandHandlers/drill/keyDriverAnalysisHandler.js +8 -3
  14. package/esm/model/store/_infra/generators.d.ts +1 -0
  15. package/esm/model/store/_infra/generators.js +4 -1
  16. package/esm/model/store/filtering/dashboardFilterSelectors.d.ts +1 -1
  17. package/esm/model/store/filtering/dashboardFilterSelectors.js +8 -5
  18. package/esm/model/store/tabs/filterContext/filterContextReducers.js +2 -2
  19. package/esm/model/utils/widgetFilters.d.ts +1 -1
  20. package/esm/model/utils/widgetFilters.js +1 -1
  21. package/esm/presentation/automationFilters/components/AutomationFiltersSelect.js +8 -2
  22. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilter.d.ts +10 -0
  23. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilter.js +66 -0
  24. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilterContext.d.ts +25 -0
  25. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilterContext.js +28 -0
  26. package/esm/presentation/automationFilters/useAutomationFilters.d.ts +4 -1
  27. package/esm/presentation/automationFilters/useAutomationFilters.js +35 -4
  28. package/esm/presentation/automationFilters/utils.d.ts +2 -1
  29. package/esm/presentation/automationFilters/utils.js +34 -5
  30. package/esm/presentation/filterBar/measureValueFilter/DefaultDashboardMeasureValueFilter.js +8 -42
  31. package/esm/presentation/filterBar/measureValueFilter/useDashboardMeasureValueFilterData.d.ts +86 -0
  32. package/esm/presentation/filterBar/measureValueFilter/useDashboardMeasureValueFilterData.js +105 -0
  33. package/esm/sdk-ui-dashboard.d.ts +17 -6
  34. package/package.json +20 -20
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { createContext, useContext } from "react";
4
+ const AutomationMeasureValueFilterContext = createContext(null);
5
+ /**
6
+ * @internal
7
+ */
8
+ export const useAutomationMeasureValueFilterContext = () => {
9
+ const context = useContext(AutomationMeasureValueFilterContext);
10
+ if (!context) {
11
+ throw new Error("AutomationMeasureValueFilterContext not found");
12
+ }
13
+ return context;
14
+ };
15
+ /**
16
+ * @internal
17
+ */
18
+ export function AutomationMeasureValueFilterProvider({ children, onChange, onDelete, isLocked, filter, deleteAriaLabel, deleteTooltipContent, lockedTooltipContent, }) {
19
+ return (_jsx(AutomationMeasureValueFilterContext.Provider, { value: {
20
+ onChange,
21
+ onDelete,
22
+ isLocked,
23
+ filter,
24
+ deleteAriaLabel,
25
+ deleteTooltipContent,
26
+ lockedTooltipContent,
27
+ }, children: children }));
28
+ }
@@ -1,5 +1,5 @@
1
1
  import { type MutableRefObject } from "react";
2
- import { type FilterContextItem, type ICatalogAttribute, type ICatalogDateDataset, type IDashboardAttributeFilterConfig, type IDashboardDateFilterConfigItem, type ObjRef } from "@gooddata/sdk-model";
2
+ import { type FilterContextItem, type ICatalogAttribute, type ICatalogDateDataset, type ICatalogMeasure, type IDashboardAttributeFilterConfig, type IDashboardDateFilterConfigItem, type ObjRef } from "@gooddata/sdk-model";
3
3
  import { type IAutomationFiltersTab } from "../../model/store/filtering/dashboardFilterSelectors.js";
4
4
  /**
5
5
  * Processed filter data for a single tab, ready for UI rendering.
@@ -17,6 +17,8 @@ export interface IProcessedAutomationFiltersTab {
17
17
  attributes: ICatalogAttribute[];
18
18
  /** Catalog date datasets available for Add filter dropdown */
19
19
  dateDatasets: ICatalogDateDataset[];
20
+ /** Catalog measures available for Add filter dropdown (for re-adding removed MVFs) */
21
+ measures: ICatalogMeasure[];
20
22
  /** Non-selected filters (available but not yet selected) */
21
23
  nonSelectedFilters: FilterContextItem[];
22
24
  /** Attribute filter configs for this tab */
@@ -39,6 +41,7 @@ export declare const useAutomationFilters: ({ availableFilters, selectedFilters,
39
41
  visibleFilters: FilterContextItem[];
40
42
  attributes: ICatalogAttribute[];
41
43
  dateDatasets: ICatalogDateDataset[];
44
+ measures: ICatalogMeasure[];
42
45
  attributeConfigs: IDashboardAttributeFilterConfig[];
43
46
  dateConfigs: IDashboardDateFilterConfigItem[];
44
47
  filterAnnouncement: string;
@@ -1,16 +1,17 @@
1
1
  // (C) 2025-2026 GoodData Corporation
2
2
  import { useCallback, useMemo, useRef, useState } from "react";
3
3
  import { useIntl } from "react-intl";
4
- import { areObjRefsEqual, dashboardAttributeFilterItemDisplayForm, isDashboardAttributeFilterItem, isDashboardDateFilter, } from "@gooddata/sdk-model";
4
+ import { areObjRefsEqual, dashboardAttributeFilterItemDisplayForm, isDashboardAttributeFilterItem, isDashboardDateFilter, isDashboardMeasureValueFilter, } from "@gooddata/sdk-model";
5
5
  import { useDashboardSelector } from "../../model/react/DashboardStoreProvider.js";
6
- import { selectCatalogAttributes, selectCatalogDateDatasets, } from "../../model/store/catalog/catalogSelectors.js";
6
+ import { selectCatalogAttributes, selectCatalogDateDatasets, selectCatalogMeasures, } from "../../model/store/catalog/catalogSelectors.js";
7
7
  import { selectEnableNewScheduledExport } from "../../model/store/config/configSelectors.js";
8
8
  import { selectAutomationCommonDateFilterId, selectDashboardLockedFilters, } from "../../model/store/filtering/dashboardFilterSelectors.js";
9
9
  import { selectPersistedDashboardFilterContextDateFilterConfig } from "../../model/store/meta/metaSelectors.js";
10
10
  import { selectAttributeFilterConfigsOverrides, selectAttributeFilterConfigsOverridesByTab, } from "../../model/store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
11
11
  import { selectDateFilterConfigOverridesByTab } from "../../model/store/tabs/dateFilterConfig/dateFilterConfigSelectors.js";
12
12
  import { selectDateFilterConfigsOverrides, selectDateFilterConfigsOverridesByTab, } from "../../model/store/tabs/dateFilterConfigs/dateFilterConfigsSelectors.js";
13
- import { areFiltersMatchedByIdentifier, getCatalogAttributesByFilters, getCatalogDateDatasetsByFilters, getFilterByCatalogItemRef, getFilterLocalIdentifier, getFilterTitle, getNonHiddenFilters, getNonSelectedFilters, } from "./utils.js";
13
+ import { selectMeasureValueFilterConfigsOverrides, selectMeasureValueFilterConfigsOverridesByTab, } from "../../model/store/tabs/measureValueFilterConfigs/measureValueFilterConfigsSelectors.js";
14
+ import { areFiltersMatchedByIdentifier, getCatalogAttributesByFilters, getCatalogDateDatasetsByFilters, getCatalogMeasuresByFilters, getFilterByCatalogItemRef, getFilterLocalIdentifier, getFilterTitle, getNonHiddenFilters, getNonSelectedFilters, } from "./utils.js";
14
15
  /**
15
16
  * Computes visible filters by removing hidden filters.
16
17
  */
@@ -29,6 +30,12 @@ function computeAddDropdownAttributes(nonSelectedFilters, context) {
29
30
  function computeAddDropdownDateDatasets(nonSelectedFilters, context) {
30
31
  return getCatalogDateDatasetsByFilters(nonSelectedFilters, context.allDateDatasets, context.dateConfigs);
31
32
  }
33
+ /**
34
+ * Computes catalog measures available for the Add filter dropdown.
35
+ */
36
+ function computeAddDropdownMeasures(nonSelectedFilters, context) {
37
+ return getCatalogMeasuresByFilters(nonSelectedFilters, context.allMeasures, context.mvfConfigs);
38
+ }
32
39
  /**
33
40
  * Logic for handling inner filters component logic.
34
41
  */
@@ -36,8 +43,10 @@ export const useAutomationFilters = ({ availableFilters, selectedFilters, onFilt
36
43
  const intl = useIntl();
37
44
  const allAttributes = useDashboardSelector(selectCatalogAttributes);
38
45
  const allDateDatasets = useDashboardSelector(selectCatalogDateDatasets);
46
+ const allMeasures = useDashboardSelector(selectCatalogMeasures);
39
47
  const attributeConfigs = useDashboardSelector(selectAttributeFilterConfigsOverrides);
40
48
  const dateConfigs = useDashboardSelector(selectDateFilterConfigsOverrides);
49
+ const mvfConfigs = useDashboardSelector(selectMeasureValueFilterConfigsOverrides);
41
50
  const dateFilterConfig = useDashboardSelector(selectPersistedDashboardFilterContextDateFilterConfig);
42
51
  const commonDateFilterId = useDashboardSelector(selectAutomationCommonDateFilterId);
43
52
  const lockedFilters = useDashboardSelector(selectDashboardLockedFilters);
@@ -50,15 +59,19 @@ export const useAutomationFilters = ({ availableFilters, selectedFilters, onFilt
50
59
  const processingContext = useMemo(() => ({
51
60
  allAttributes,
52
61
  allDateDatasets,
62
+ allMeasures,
53
63
  attributeConfigs,
54
64
  dateConfigs,
65
+ mvfConfigs,
55
66
  isCommonDateFilterHidden,
56
67
  disableDateFilters,
57
68
  }), [
58
69
  allAttributes,
59
70
  allDateDatasets,
71
+ allMeasures,
60
72
  attributeConfigs,
61
73
  dateConfigs,
74
+ mvfConfigs,
62
75
  isCommonDateFilterHidden,
63
76
  disableDateFilters,
64
77
  ]);
@@ -66,6 +79,7 @@ export const useAutomationFilters = ({ availableFilters, selectedFilters, onFilt
66
79
  const nonSelectedFilters = useMemo(() => getNonSelectedFilters(availableFilters, selectedFilters), [availableFilters, selectedFilters]);
67
80
  const attributes = useMemo(() => computeAddDropdownAttributes(nonSelectedFilters, processingContext), [nonSelectedFilters, processingContext]);
68
81
  const dateDatasets = useMemo(() => computeAddDropdownDateDatasets(nonSelectedFilters, processingContext), [nonSelectedFilters, processingContext]);
82
+ const measures = useMemo(() => computeAddDropdownMeasures(nonSelectedFilters, processingContext), [nonSelectedFilters, processingContext]);
69
83
  const focusAddFilterButton = useCallback(() => {
70
84
  //focus add button, use requestAnimationFrame to wait for rerender
71
85
  requestAnimationFrame(() => {
@@ -130,7 +144,11 @@ export const useAutomationFilters = ({ availableFilters, selectedFilters, onFilt
130
144
  const selectedDateDataSets = dateDatasets.filter((ds) => areObjRefsEqual(ds.dataSet.ref, catalogItemRef));
131
145
  const attributeFilter = selectedAttributeDisplayForms.reduce((acc, displayFormRef) => acc || getFilterByCatalogItemRef(displayFormRef, nonSelectedFilters), undefined);
132
146
  const dateFilter = selectedDateDataSets.reduce((acc, dateDataSet) => acc || getFilterByCatalogItemRef(dateDataSet.dataSet.ref, nonSelectedFilters), undefined);
133
- const filter = attributeFilter || dateFilter;
147
+ // For MVF: the dropdown emits the metric's catalog ref directly; look it up by ref.
148
+ const measureFilter = getFilterByCatalogItemRef(catalogItemRef, nonSelectedFilters);
149
+ const filter = attributeFilter ||
150
+ dateFilter ||
151
+ (measureFilter && isDashboardMeasureValueFilter(measureFilter) ? measureFilter : undefined);
134
152
  if (filter) {
135
153
  const filterTitle = getFilterTitle(filter, allAttributes, allDateDatasets, intl);
136
154
  const message = intl.formatMessage({ id: "automationFilters.announcement.filterAdded" }, { title: filterTitle });
@@ -166,6 +184,7 @@ export const useAutomationFilters = ({ availableFilters, selectedFilters, onFilt
166
184
  visibleFilters,
167
185
  attributes,
168
186
  dateDatasets,
187
+ measures,
169
188
  attributeConfigs,
170
189
  dateConfigs,
171
190
  filterAnnouncement,
@@ -186,6 +205,7 @@ export const useAutomationFilters = ({ availableFilters, selectedFilters, onFilt
186
205
  export const useAutomationFiltersByTab = ({ filtersByTab, editedFiltersByTab, onFiltersByTabChange, onStoreFiltersChange, disableDateFilters = false, }) => {
187
206
  const allAttributes = useDashboardSelector(selectCatalogAttributes);
188
207
  const allDateDatasets = useDashboardSelector(selectCatalogDateDatasets);
208
+ const allMeasures = useDashboardSelector(selectCatalogMeasures);
189
209
  const commonDateFilterId = useDashboardSelector(selectAutomationCommonDateFilterId);
190
210
  const [filterAnnouncement] = useState("");
191
211
  const addFilterButtonRef = useRef(null);
@@ -194,6 +214,7 @@ export const useAutomationFiltersByTab = ({ filtersByTab, editedFiltersByTab, on
194
214
  const attributeConfigsByTab = useDashboardSelector(selectAttributeFilterConfigsOverridesByTab);
195
215
  const dateConfigsByTab = useDashboardSelector(selectDateFilterConfigsOverridesByTab);
196
216
  const dateFilterConfigByTab = useDashboardSelector(selectDateFilterConfigOverridesByTab);
217
+ const mvfConfigsByTab = useDashboardSelector(selectMeasureValueFilterConfigsOverridesByTab);
197
218
  const processedFiltersByTab = useMemo(() => {
198
219
  if (!filtersByTab || filtersByTab.length === 0) {
199
220
  return undefined;
@@ -203,14 +224,17 @@ export const useAutomationFiltersByTab = ({ filtersByTab, editedFiltersByTab, on
203
224
  // Get configs specific to this tab
204
225
  const attributeConfigs = attributeConfigsByTab[tabId] ?? [];
205
226
  const dateConfigs = dateConfigsByTab[tabId] ?? [];
227
+ const mvfConfigs = mvfConfigsByTab[tabId] ?? [];
206
228
  const dateFilterConfig = dateFilterConfigByTab[tabId];
207
229
  const isCommonDateFilterHidden = dateFilterConfig?.mode === "hidden";
208
230
  // Create processing context for this specific tab
209
231
  const processingContext = {
210
232
  allAttributes,
211
233
  allDateDatasets,
234
+ allMeasures,
212
235
  attributeConfigs,
213
236
  dateConfigs,
237
+ mvfConfigs,
214
238
  isCommonDateFilterHidden,
215
239
  disableDateFilters,
216
240
  };
@@ -224,6 +248,7 @@ export const useAutomationFiltersByTab = ({ filtersByTab, editedFiltersByTab, on
224
248
  // Compute catalog items for Add dropdown
225
249
  const attributes = computeAddDropdownAttributes(nonSelectedFilters, processingContext);
226
250
  const dateDatasets = computeAddDropdownDateDatasets(nonSelectedFilters, processingContext);
251
+ const measures = computeAddDropdownMeasures(nonSelectedFilters, processingContext);
227
252
  return {
228
253
  tabId: tab.tabId,
229
254
  tabTitle: tab.tabTitle,
@@ -231,6 +256,7 @@ export const useAutomationFiltersByTab = ({ filtersByTab, editedFiltersByTab, on
231
256
  lockedFilters: tab.lockedFilters,
232
257
  attributes,
233
258
  dateDatasets,
259
+ measures,
234
260
  nonSelectedFilters,
235
261
  attributeConfigs,
236
262
  dateConfigs,
@@ -241,9 +267,11 @@ export const useAutomationFiltersByTab = ({ filtersByTab, editedFiltersByTab, on
241
267
  editedFiltersByTab,
242
268
  allAttributes,
243
269
  allDateDatasets,
270
+ allMeasures,
244
271
  attributeConfigsByTab,
245
272
  dateConfigsByTab,
246
273
  dateFilterConfigByTab,
274
+ mvfConfigsByTab,
247
275
  disableDateFilters,
248
276
  ]);
249
277
  // Handlers for per-tab filter operations (similar to original hook)
@@ -303,6 +331,9 @@ export const useAutomationFiltersByTab = ({ filtersByTab, editedFiltersByTab, on
303
331
  else if (isDashboardDateFilter(f)) {
304
332
  return selectedDateDataSets.some((ds) => areObjRefsEqual(f.dateFilter.dataSet, ds.dataSet.ref));
305
333
  }
334
+ else if (isDashboardMeasureValueFilter(f)) {
335
+ return areObjRefsEqual(f.dashboardMeasureValueFilter.measure, displayForm);
336
+ }
306
337
  return false;
307
338
  });
308
339
  if (availableFilter) {
@@ -1,11 +1,12 @@
1
1
  import { type IntlShape } from "react-intl";
2
- import { type FilterContextItem, type IAutomationVisibleFilter, type ICatalogAttribute, type ICatalogDateDataset, type IDashboardAttributeFilterConfig, type IDashboardDateFilterConfigItem, type IDateFilter, type IFilter, type IInsight, type ObjRef } from "@gooddata/sdk-model";
2
+ import { type FilterContextItem, type IAutomationVisibleFilter, type ICatalogAttribute, type ICatalogDateDataset, type ICatalogMeasure, type IDashboardAttributeFilterConfig, type IDashboardDateFilterConfigItem, type IDashboardMeasureValueFilterConfig, type IDateFilter, type IFilter, type IInsight, type ObjRef } from "@gooddata/sdk-model";
3
3
  import type { ExtendedDashboardWidget } from "../../model/types/layoutTypes.js";
4
4
  export declare const getFilterLocalIdentifier: (filter: FilterContextItem) => string | undefined;
5
5
  export declare const validateAllFilterLocalIdentifiers: (filters: FilterContextItem[]) => boolean;
6
6
  export declare const areFiltersMatchedByIdentifier: (filter1: FilterContextItem, filter2: FilterContextItem) => boolean;
7
7
  export declare const getNonSelectedFilters: (allFilters: FilterContextItem[], selectedFilters: FilterContextItem[]) => FilterContextItem[];
8
8
  export declare const getCatalogAttributesByFilters: (filters: FilterContextItem[], attributes: ICatalogAttribute[], attributeConfigs: IDashboardAttributeFilterConfig[]) => ICatalogAttribute[];
9
+ export declare const getCatalogMeasuresByFilters: (filters: FilterContextItem[], measures: ICatalogMeasure[], mvfConfigs: IDashboardMeasureValueFilterConfig[]) => ICatalogMeasure[];
9
10
  export declare const getCatalogDateDatasetsByFilters: (filters: FilterContextItem[], dateDataset: ICatalogDateDataset[], dateConfigs: IDashboardDateFilterConfigItem[]) => ICatalogDateDataset[];
10
11
  export declare const getFilterByCatalogItemRef: (ref: ObjRef, filters: FilterContextItem[]) => FilterContextItem | undefined;
11
12
  export declare const getVisibleFiltersByFilters: (selectedFilters: FilterContextItem[] | undefined, visibleFiltersMetadata: IAutomationVisibleFilter[] | undefined, storeFilters?: boolean | undefined) => IAutomationVisibleFilter[] | undefined;
@@ -1,6 +1,6 @@
1
1
  // (C) 2025-2026 GoodData Corporation
2
2
  import { compact, isEqual } from "lodash-es";
3
- import { absoluteDateFilterValues, areObjRefsEqual, attributeLocalId, dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemLocalIdentifier, dashboardFilterLocalIdentifier, dashboardFilterObjRef, filterAttributeElements, filterLocalIdentifier, filterObjRef, getAttributeElementsItems, insightAttributes, isAbsoluteDateFilter, isAllTimeDateFilter, isAllValuesAttributeFilter, isAllValuesDashboardAttributeFilter, isArbitraryAttributeFilter, isAttributeFilter, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMatchAttributeFilter, isDashboardMeasureValueFilter, isDateFilter, isInsightWidget, isLocalIdRef, isMatchAttributeFilter, isMeasureValueFilter, isNegativeAttributeFilter, isNoopAllTimeDashboardDateFilter, isPositiveAttributeFilter, isRelativeDateFilter, mergeFilters, relativeDateFilterValues, } from "@gooddata/sdk-model";
3
+ import { absoluteDateFilterValues, areObjRefsEqual, attributeLocalId, dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemLocalIdentifier, dashboardFilterLocalIdentifier, dashboardFilterObjRef, filterAttributeElements, filterLocalIdentifier, filterObjRef, getAttributeElementsItems, hasMeasureValueFilterConditions, insightAttributes, isAbsoluteDateFilter, isAllDashboardMeasureValueFilter, isAllTimeDateFilter, isAllValuesAttributeFilter, isAllValuesDashboardAttributeFilter, isArbitraryAttributeFilter, isAttributeFilter, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMatchAttributeFilter, isDashboardMeasureValueFilter, isDateFilter, isInsightWidget, isLocalIdRef, isMatchAttributeFilter, isMeasureValueFilter, isNegativeAttributeFilter, isNoopAllTimeDashboardDateFilter, isPositiveAttributeFilter, isRelativeDateFilter, mergeFilters, relativeDateFilterValues, } from "@gooddata/sdk-model";
4
4
  import { filterContextItemsToDashboardFiltersByWidget } from "../../converters/filterConverters.js";
5
5
  import { removeIgnoredWidgetFilters } from "../../model/utils/widgetFilters.js";
6
6
  export const getFilterLocalIdentifier = (filter) => {
@@ -38,6 +38,21 @@ export const getCatalogAttributesByFilters = (filters, attributes, attributeConf
38
38
  });
39
39
  });
40
40
  };
41
+ export const getCatalogMeasuresByFilters = (filters, measures, mvfConfigs) => {
42
+ const ignoredLocalIdentifiers = mvfConfigs
43
+ .filter((config) => config.mode === "hidden")
44
+ .map((config) => config.localIdentifier);
45
+ return measures.filter((measure) => {
46
+ return filters.some((filter) => {
47
+ if (isDashboardMeasureValueFilter(filter)) {
48
+ const localIdentifier = filter.dashboardMeasureValueFilter.localIdentifier;
49
+ return (!ignoredLocalIdentifiers.includes(localIdentifier) &&
50
+ areObjRefsEqual(filter.dashboardMeasureValueFilter.measure, measure.measure.ref));
51
+ }
52
+ return false;
53
+ });
54
+ });
55
+ };
41
56
  export const getCatalogDateDatasetsByFilters = (filters, dateDataset, dateConfigs) => {
42
57
  const ignoredDateDatasets = dateConfigs
43
58
  .filter((config) => {
@@ -61,6 +76,9 @@ export const getFilterByCatalogItemRef = (ref, filters) => {
61
76
  else if (isDashboardDateFilter(filter)) {
62
77
  return areObjRefsEqual(filter.dateFilter.dataSet, ref);
63
78
  }
79
+ else if (isDashboardMeasureValueFilter(filter)) {
80
+ return areObjRefsEqual(filter.dashboardMeasureValueFilter.measure, ref);
81
+ }
64
82
  return false;
65
83
  });
66
84
  };
@@ -69,9 +87,9 @@ export const getVisibleFiltersByFilters = (selectedFilters, visibleFiltersMetada
69
87
  return undefined;
70
88
  }
71
89
  const filters = (selectedFilters ?? [])
72
- // Strip noop "All values" attribute filters they have no effect on execution
73
- // and should not be stored in visible filters metadata.
74
- .filter((filter) => !isAllValuesDashboardAttributeFilter(filter))
90
+ // Strip noop "All values" attribute filters and "All" measure value filters
91
+ // they have no effect on execution and should not be stored in visible filters metadata.
92
+ .filter((filter) => !isAllValuesDashboardAttributeFilter(filter) && !isAllDashboardMeasureValueFilter(filter))
75
93
  .map((selectedFilter) => {
76
94
  const selectedLocalIdentifier = getFilterLocalIdentifier(selectedFilter);
77
95
  const targetFilter = (visibleFiltersMetadata ?? []).find((visibleFilter) => {
@@ -176,6 +194,10 @@ export const getAppliedWidgetFilters = (selectedAutomationFilters, dashboardHidd
176
194
  if (isDateFilter(filter)) {
177
195
  return !isNoopAllTimeDateFilterFixed(filter);
178
196
  }
197
+ // Strip noop "All" measure value filters (no conditions).
198
+ if (isMeasureValueFilter(filter)) {
199
+ return hasMeasureValueFilterConditions(filter);
200
+ }
179
201
  // Strip noop "All values" attribute filters (negative filter with empty exclusion list).
180
202
  return !isAllValuesAttributeFilter(filter);
181
203
  });
@@ -196,7 +218,11 @@ export const getAppliedDashboardFilters = (selectedAutomationFilters, dashboardH
196
218
  if (isDashboardDateFilter(filter)) {
197
219
  return !isNoopAllTimeDashboardDateFilter(filter);
198
220
  }
199
- // Strip noop "All values" attribute filters (negative selection with empty element list).
221
+ // Strip noop "All" measure value filters (no/empty conditions).
222
+ if (isDashboardMeasureValueFilter(filter)) {
223
+ return !isAllDashboardMeasureValueFilter(filter);
224
+ }
225
+ // Strip noop "All values" attribute filters.
200
226
  return !isAllValuesDashboardAttributeFilter(filter);
201
227
  });
202
228
  };
@@ -373,5 +399,8 @@ export const getFilterTitle = (filter, allAttributes, allDateDatasets, intl) =>
373
399
  const dateDataset = allDateDatasets.find((ds) => areObjRefsEqual(ds.dataSet.ref, filter.dateFilter.dataSet));
374
400
  return dateDataset?.dataSet.title || "";
375
401
  }
402
+ if (isDashboardMeasureValueFilter(filter)) {
403
+ return filter.dashboardMeasureValueFilter.title || "";
404
+ }
376
405
  return "";
377
406
  };
@@ -3,16 +3,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useMemo, useRef, useState } from "react";
4
4
  import cx from "classnames";
5
5
  import { useIntl } from "react-intl";
6
- import { DashboardAttributeFilterConfigModeValues, areObjRefsEqual, objRefToString, } from "@gooddata/sdk-model";
7
- import { MeasureValueFilter, getMeasureValueFilterConditionLabel, } from "@gooddata/sdk-ui-filters";
6
+ import { DashboardAttributeFilterConfigModeValues, } from "@gooddata/sdk-model";
7
+ import { MeasureValueFilter, } from "@gooddata/sdk-ui-filters";
8
8
  import { Bubble, BubbleHoverTrigger, UiControlButton } from "@gooddata/sdk-ui-kit";
9
9
  import { setDashboardMeasureValueFilterConfigMode } from "../../../model/commands/dashboard.js";
10
10
  import { setMeasureValueFilterTitle } from "../../../model/commands/filters.js";
11
11
  import { useDashboardSelector } from "../../../model/react/DashboardStoreProvider.js";
12
12
  import { useDashboardCommandProcessing } from "../../../model/react/useDashboardCommandProcessing.js";
13
13
  import { selectBackendCapabilities } from "../../../model/store/backendCapabilities/backendCapabilitiesSelectors.js";
14
- import { selectCatalogMeasures } from "../../../model/store/catalog/catalogSelectors.js";
15
- import { selectIsApplyFiltersAllAtOnceEnabledAndSet, selectLocale, selectSeparators, } from "../../../model/store/config/configSelectors.js";
14
+ import { selectIsApplyFiltersAllAtOnceEnabledAndSet } from "../../../model/store/config/configSelectors.js";
16
15
  import { selectIsInEditMode } from "../../../model/store/renderMode/renderModeSelectors.js";
17
16
  import { selectWorkingFilterContextMeasureValueFilterByLocalId } from "../../../model/store/tabs/filterContext/filterContextSelectors.js";
18
17
  import { selectMeasureValueFilterConfigsModeMap } from "../../../model/store/tabs/measureValueFilterConfigs/measureValueFilterConfigsSelectors.js";
@@ -20,22 +19,8 @@ import { useAttributeFilterConfigTexts } from "../attributeFilter/useAttributeFi
20
19
  import { getVisibilityIcon } from "../utils.js";
21
20
  import { CustomConfigureMeasureValueFilterDropdownActions, CustomMeasureValueFilterDropdownActions, } from "./CustomDropdownActions.js";
22
21
  import { MeasureValueFilterConfiguration } from "./MeasureValueFilterConfiguration.js";
23
- const PERCENT_FORMAT_REGEX = /%/;
22
+ import { getSharedDashboardMvfProps, normalizeMeasureValueFilterConditions, useDashboardMeasureValueFilterData, } from "./useDashboardMeasureValueFilterData.js";
24
23
  const DEFAULT_VISIBILITY_BUBBLE_ALIGN_POINTS = [{ align: "bc tl", offset: { x: 0, y: 5 } }];
25
- function isPercentageFormat(format) {
26
- return !!format && PERCENT_FORMAT_REGEX.test(format);
27
- }
28
- function findCatalogMetric(measure, measures) {
29
- return measures.find((m) => areObjRefsEqual(m.measure.ref, measure));
30
- }
31
- function normalizeMeasureValueFilterConditions(updated) {
32
- const body = updated?.measureValueFilter;
33
- return body?.conditions && body.conditions.length > 0
34
- ? body.conditions
35
- : body?.condition
36
- ? [body.condition]
37
- : undefined;
38
- }
39
24
  function MeasureValueFilterVisibilityIcon({ visibilityIcon, disabled, }) {
40
25
  if (!visibilityIcon) {
41
26
  return null;
@@ -53,23 +38,15 @@ function MeasureValueFilterVisibilityIcon({ visibilityIcon, disabled, }) {
53
38
  export function DefaultDashboardMeasureValueFilter(props) {
54
39
  const { filter, readonly, autoOpen, onMeasureValueFilterChanged, onMeasureValueFilterClose } = props;
55
40
  const intl = useIntl();
56
- const measures = useDashboardSelector(selectCatalogMeasures);
57
- const separators = useDashboardSelector(selectSeparators);
58
- const locale = useDashboardSelector(selectLocale);
59
41
  const isEditMode = useDashboardSelector(selectIsInEditMode);
60
42
  const isApplyAllAtOnceEnabledAndSet = useDashboardSelector(selectIsApplyFiltersAllAtOnceEnabledAndSet);
61
43
  const capabilities = useDashboardSelector(selectBackendCapabilities);
62
44
  const measureValueFilterConfigsModeMap = useDashboardSelector(selectMeasureValueFilterConfigsModeMap);
63
45
  const { cancelText, closeText, saveText, applyText, titleText, resetTitleText, modeCategoryTitleText } = useAttributeFilterConfigTexts();
64
- const { measure, localIdentifier, title: customTitle } = filter.dashboardMeasureValueFilter;
65
- const workingFilter = useDashboardSelector(selectWorkingFilterContextMeasureValueFilterByLocalId(localIdentifier));
46
+ const workingFilter = useDashboardSelector(selectWorkingFilterContextMeasureValueFilterByLocalId(filter.dashboardMeasureValueFilter.localIdentifier));
66
47
  const filterToDisplay = isApplyAllAtOnceEnabledAndSet ? (workingFilter ?? filter) : filter;
67
- const conditions = filterToDisplay.dashboardMeasureValueFilter.conditions;
68
- const catalogMetric = useMemo(() => findCatalogMetric(measure, measures), [measure, measures]);
69
- const defaultMetricTitle = catalogMetric?.measure.title ?? objRefToString(measure);
70
- const metricTitle = customTitle ?? defaultMetricTitle;
71
- const format = catalogMetric?.measure.format;
72
- const usePercentage = isPercentageFormat(format);
48
+ const mvfData = useDashboardMeasureValueFilterData(filter, filterToDisplay);
49
+ const { localIdentifier, customTitle, defaultMetricTitle, metricTitle, conditionLabel } = mvfData;
73
50
  const mode = measureValueFilterConfigsModeMap.get(localIdentifier) ??
74
51
  DashboardAttributeFilterConfigModeValues.ACTIVE;
75
52
  const visibilityIcon = getVisibilityIcon(mode, isEditMode, !!capabilities.supportsHiddenAndLockedFiltersOnUI, intl);
@@ -86,17 +63,6 @@ export function DefaultDashboardMeasureValueFilter(props) {
86
63
  successEvent: "GDC.DASH/EVT.MEASURE_VALUE_FILTER_CONFIG.MODE_CHANGED",
87
64
  errorEvent: "GDC.DASH/EVT.COMMAND.FAILED",
88
65
  });
89
- const conditionLabel = useMemo(() => getMeasureValueFilterConditionLabel(intl, conditions, {
90
- usePercentage,
91
- separators,
92
- }), [intl, conditions, usePercentage, separators]);
93
- const dropdownFilter = useMemo(() => ({
94
- measureValueFilter: {
95
- measure,
96
- localIdentifier,
97
- ...(conditions && conditions.length > 0 ? { conditions } : {}),
98
- },
99
- }), [measure, localIdentifier, conditions]);
100
66
  const handleClose = useCallback(() => {
101
67
  setIsConfigurationOpen(false);
102
68
  onMeasureValueFilterClose?.();
@@ -189,5 +155,5 @@ export function DefaultDashboardMeasureValueFilter(props) {
189
155
  }
190
156
  return DashboardMeasureValueFilterDropdownButton;
191
157
  }, [conditionLabel, intl, isEditMode, readonly, visibilityIcon]);
192
- return (_jsx(MeasureValueFilter, { onApply: handleApply, onChange: isApplyAllAtOnceEnabledAndSet ? handleChange : undefined, withoutApply: isApplyAllAtOnceEnabledAndSet, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, DropdownButtonComponent: DropdownButtonComponent, onCancel: handleClose, filter: dropdownFilter, measureIdentifier: localIdentifier, buttonTitle: metricTitle, measureTitle: metricTitle, usePercentage: usePercentage, format: format, useShortFormat: true, displayTreatNullAsZeroOption: true, separators: separators, locale: locale, enableOperatorSelection: true, enableMultipleConditions: true, isDimensionalityEnabled: false, isFilterSummaryEnabled: true, autoOpen: autoOpen }));
158
+ return (_jsx(MeasureValueFilter, { ...getSharedDashboardMvfProps(mvfData), onApply: handleApply, onChange: isApplyAllAtOnceEnabledAndSet ? handleChange : undefined, withoutApply: isApplyAllAtOnceEnabledAndSet, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, DropdownButtonComponent: DropdownButtonComponent, onCancel: handleClose, autoOpen: autoOpen }));
193
159
  }
@@ -0,0 +1,86 @@
1
+ import { type IntlShape } from "react-intl";
2
+ import { type ICatalogMeasure, type IDashboardMeasureValueFilter, type IMeasureValueFilter, type ISeparators, type MeasureValueFilterCondition, type ObjRef } from "@gooddata/sdk-model";
3
+ /**
4
+ * Normalizes the output of the SDK `<MeasureValueFilter />` onApply callback into a single
5
+ * `conditions` array. The SDK can return either `condition` (single comparison/range) or
6
+ * `conditions` (multi-condition OR); the dashboard model only uses `conditions`. Returns
7
+ * undefined when the filter is "All" (no condition selected).
8
+ *
9
+ * @internal
10
+ */
11
+ export declare function normalizeMeasureValueFilterConditions(updated: IMeasureValueFilter | null): MeasureValueFilterCondition[] | undefined;
12
+ /**
13
+ * Derived data shared by every dashboard MVF host (filter bar, automation/scheduling dialog,
14
+ * any future location). Resolves the catalog metric, computes title/format/percentage/conditions,
15
+ * builds the execution-level MVF shape the SDK dropdown expects, and produces the condition
16
+ * label shown on the chip.
17
+ *
18
+ * @internal
19
+ */
20
+ export interface IDashboardMeasureValueFilterData {
21
+ measure: ObjRef;
22
+ localIdentifier: string;
23
+ /** Custom title from the filter, if any. */
24
+ customTitle: string | undefined;
25
+ /** Catalog metric the filter references; undefined if not in the catalog. */
26
+ catalogMetric: ICatalogMeasure | undefined;
27
+ /** Title fallback when no custom title is set. */
28
+ defaultMetricTitle: string;
29
+ /** Effective title — custom title or catalog title or stringified objRef. */
30
+ metricTitle: string;
31
+ /** Catalog metric format pattern, if any. */
32
+ format: string | undefined;
33
+ /** Whether the format expresses a percentage. */
34
+ usePercentage: boolean;
35
+ /** Conditions from the (possibly working) filter the UI should reflect. */
36
+ conditions: MeasureValueFilterCondition[] | undefined;
37
+ /** Human-readable condition summary shown on the chip ("> 100 OR ≥ 50%"). */
38
+ conditionLabel: string;
39
+ /** Execution-level MVF that the SDK <MeasureValueFilter /> takes as `filter`. */
40
+ dropdownFilter: IMeasureValueFilter;
41
+ separators: ISeparators;
42
+ locale: string;
43
+ intl: IntlShape;
44
+ }
45
+ /**
46
+ * Hook that resolves the shared data needed to render a dashboard MVF dropdown.
47
+ *
48
+ * The host owns the choice of which filter to reflect in the UI: the persisted filter
49
+ * (`filter`), or the working/staged filter when "Apply together" mode is active. Pass
50
+ * the chosen one as `filterToDisplay`; if omitted, `filter` itself is used.
51
+ *
52
+ * @internal
53
+ */
54
+ export declare function useDashboardMeasureValueFilterData(filter: IDashboardMeasureValueFilter, filterToDisplay?: IDashboardMeasureValueFilter): IDashboardMeasureValueFilterData;
55
+ /**
56
+ * Props that every dashboard MVF host passes to the SDK `<MeasureValueFilter />` unchanged.
57
+ * Host-specific bits — `DropdownButtonComponent`, `onApply`, `onChange`, `onCancel`,
58
+ * `withoutApply`, `BodyComponent`, `DropdownActionsComponent`, `autoOpen` — are added
59
+ * by the host on top of this bundle.
60
+ *
61
+ * @internal
62
+ */
63
+ export interface ISharedDashboardMvfProps {
64
+ filter: IMeasureValueFilter;
65
+ measureIdentifier: string;
66
+ buttonTitle: string;
67
+ measureTitle: string;
68
+ usePercentage: boolean;
69
+ format: string | undefined;
70
+ useShortFormat: true;
71
+ displayTreatNullAsZeroOption: true;
72
+ separators: ISeparators;
73
+ locale: string;
74
+ enableOperatorSelection: true;
75
+ enableMultipleConditions: true;
76
+ isDimensionalityEnabled: false;
77
+ isFilterSummaryEnabled: true;
78
+ isHeaderEnabled: true;
79
+ }
80
+ /**
81
+ * Build the shared `<MeasureValueFilter />` prop bundle from the data hook output.
82
+ * Spread this into the SDK component and add host-specific props after.
83
+ *
84
+ * @internal
85
+ */
86
+ export declare function getSharedDashboardMvfProps(data: IDashboardMeasureValueFilterData): ISharedDashboardMvfProps;
@@ -0,0 +1,105 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { useMemo } from "react";
3
+ import { useIntl } from "react-intl";
4
+ import { areObjRefsEqual, objRefToString, } from "@gooddata/sdk-model";
5
+ import { getMeasureValueFilterConditionLabel } from "@gooddata/sdk-ui-filters";
6
+ import { useDashboardSelector } from "../../../model/react/DashboardStoreProvider.js";
7
+ import { selectCatalogMeasures } from "../../../model/store/catalog/catalogSelectors.js";
8
+ import { selectLocale, selectSeparators } from "../../../model/store/config/configSelectors.js";
9
+ const PERCENT_FORMAT_REGEX = /%/;
10
+ function isPercentageFormat(format) {
11
+ return !!format && PERCENT_FORMAT_REGEX.test(format);
12
+ }
13
+ /**
14
+ * Normalizes the output of the SDK `<MeasureValueFilter />` onApply callback into a single
15
+ * `conditions` array. The SDK can return either `condition` (single comparison/range) or
16
+ * `conditions` (multi-condition OR); the dashboard model only uses `conditions`. Returns
17
+ * undefined when the filter is "All" (no condition selected).
18
+ *
19
+ * @internal
20
+ */
21
+ export function normalizeMeasureValueFilterConditions(updated) {
22
+ const body = updated?.measureValueFilter;
23
+ if (body?.conditions && body.conditions.length > 0) {
24
+ return body.conditions;
25
+ }
26
+ if (body?.condition) {
27
+ return [body.condition];
28
+ }
29
+ return undefined;
30
+ }
31
+ function findCatalogMetric(measure, measures) {
32
+ return measures.find((m) => areObjRefsEqual(m.measure.ref, measure));
33
+ }
34
+ /**
35
+ * Hook that resolves the shared data needed to render a dashboard MVF dropdown.
36
+ *
37
+ * The host owns the choice of which filter to reflect in the UI: the persisted filter
38
+ * (`filter`), or the working/staged filter when "Apply together" mode is active. Pass
39
+ * the chosen one as `filterToDisplay`; if omitted, `filter` itself is used.
40
+ *
41
+ * @internal
42
+ */
43
+ export function useDashboardMeasureValueFilterData(filter, filterToDisplay) {
44
+ const measures = useDashboardSelector(selectCatalogMeasures);
45
+ const separators = useDashboardSelector(selectSeparators);
46
+ const locale = useDashboardSelector(selectLocale);
47
+ const intl = useIntl();
48
+ const { measure, localIdentifier, title: customTitle } = filter.dashboardMeasureValueFilter;
49
+ const effective = filterToDisplay ?? filter;
50
+ const conditions = effective.dashboardMeasureValueFilter.conditions;
51
+ const catalogMetric = useMemo(() => findCatalogMetric(measure, measures), [measure, measures]);
52
+ const defaultMetricTitle = catalogMetric?.measure.title ?? objRefToString(measure);
53
+ const metricTitle = customTitle ?? defaultMetricTitle;
54
+ const format = catalogMetric?.measure.format;
55
+ const usePercentage = isPercentageFormat(format);
56
+ const conditionLabel = useMemo(() => getMeasureValueFilterConditionLabel(intl, conditions, { usePercentage, separators }), [intl, conditions, usePercentage, separators]);
57
+ const dropdownFilter = useMemo(() => ({
58
+ measureValueFilter: {
59
+ measure,
60
+ localIdentifier,
61
+ ...(conditions && conditions.length > 0 ? { conditions } : {}),
62
+ },
63
+ }), [measure, localIdentifier, conditions]);
64
+ return {
65
+ measure,
66
+ localIdentifier,
67
+ customTitle,
68
+ catalogMetric,
69
+ defaultMetricTitle,
70
+ metricTitle,
71
+ format,
72
+ usePercentage,
73
+ conditions,
74
+ conditionLabel,
75
+ dropdownFilter,
76
+ separators,
77
+ locale,
78
+ intl,
79
+ };
80
+ }
81
+ /**
82
+ * Build the shared `<MeasureValueFilter />` prop bundle from the data hook output.
83
+ * Spread this into the SDK component and add host-specific props after.
84
+ *
85
+ * @internal
86
+ */
87
+ export function getSharedDashboardMvfProps(data) {
88
+ return {
89
+ filter: data.dropdownFilter,
90
+ measureIdentifier: data.localIdentifier,
91
+ buttonTitle: data.metricTitle,
92
+ measureTitle: data.metricTitle,
93
+ usePercentage: data.usePercentage,
94
+ format: data.format,
95
+ useShortFormat: true,
96
+ displayTreatNullAsZeroOption: true,
97
+ separators: data.separators,
98
+ locale: data.locale,
99
+ enableOperatorSelection: true,
100
+ enableMultipleConditions: true,
101
+ isDimensionalityEnabled: false,
102
+ isFilterSummaryEnabled: true,
103
+ isHeaderEnabled: true,
104
+ };
105
+ }
@@ -4966,6 +4966,11 @@ export declare type FluidLayoutCustomizationFn = (layout: IDashboardLayout<Exten
4966
4966
  */
4967
4967
  export declare function formatKeyDriverAnalysisDateRange(range: DeepReadonly<[IKdaDataPoint, IKdaDataPoint]> | undefined, splitter: string): string;
4968
4968
 
4969
+ /**
4970
+ * @internal
4971
+ */
4972
+ export declare function getAttributeFilters(filters: FilterContextItem[]): DashboardAttributeFilterItem[];
4973
+
4969
4974
  /**
4970
4975
  * @internal
4971
4976
  * Gets author from capabilities and user
@@ -13945,9 +13950,20 @@ export declare interface IKdaDefinition {
13945
13950
  */
13946
13951
  metrics?: IMeasure[];
13947
13952
  /**
13948
- * Filters to apply
13953
+ * Attribute filters to apply.
13954
+ *
13955
+ * Attribute filters drive both the KDA "segment-by attribute" UI in the dialog and the
13956
+ * scope of the change analysis computation.
13949
13957
  */
13950
13958
  filters?: DashboardAttributeFilterItem[];
13959
+ /**
13960
+ * Measure value filters to apply to the change analysis computation.
13961
+ *
13962
+ * They are not editable in the dialog UI (KDA segments by attribute, not by metric value),
13963
+ * but the dashboard MVFs are propagated to the backend so the computation respects the
13964
+ * same metric-driven scope as the rest of the dashboard.
13965
+ */
13966
+ measureValueFilters?: IDashboardMeasureValueFilter[];
13951
13967
  /**
13952
13968
  * Date attribute
13953
13969
  */
@@ -21309,11 +21325,6 @@ export declare function removeAttributeFilters(filterLocalIds: string[], correla
21309
21325
  */
21310
21326
  export declare function removeDateFilter(dataSet: ObjRef, correlationId?: string): IRemoveDateFilters;
21311
21327
 
21312
- /**
21313
- * @internal
21314
- */
21315
- export declare function removeDateFilters(filters: FilterContextItem[]): DashboardAttributeFilterItem[];
21316
-
21317
21328
  /**
21318
21329
  * Creates the RemoveDrillDownForInsightWidget command. Dispatching the created command will remove insight widget's
21319
21330
  * drill definition for the provided measure.