@gooddata/sdk-ui-dashboard 11.35.0-alpha.5 → 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 (65) hide show
  1. package/NOTICE +8 -8
  2. package/esm/__version.d.ts +1 -1
  3. package/esm/__version.js +1 -1
  4. package/esm/_staging/dashboard/dashboardFilterContext.js +18 -1
  5. package/esm/_staging/sharedHooks/useFiltersNamings.d.ts +1 -1
  6. package/esm/_staging/sharedHooks/useFiltersNamings.js +32 -5
  7. package/esm/index.d.ts +4 -5
  8. package/esm/index.js +2 -3
  9. package/esm/kdaDialog/dialog/hooks/useChangeAnalysis.js +21 -5
  10. package/esm/kdaDialog/internalTypes.d.ts +6 -1
  11. package/esm/kdaDialog/providers/Kda.js +4 -1
  12. package/esm/kdaDialog/providers/KdaState.js +1 -0
  13. package/esm/kdaDialog/types.d.ts +13 -2
  14. package/esm/model/commandHandlers/dashboard/common/parameterHydration.d.ts +11 -2
  15. package/esm/model/commandHandlers/dashboard/common/parameterHydration.js +21 -0
  16. package/esm/model/commandHandlers/dashboard/common/stateInitializers.js +44 -79
  17. package/esm/model/commandHandlers/dashboard/saveAsDashboardHandler.js +10 -5
  18. package/esm/model/commandHandlers/dashboard/saveDashboardHandler.js +11 -7
  19. package/esm/model/commandHandlers/drill/keyDriverAnalysisHandler.js +8 -3
  20. package/esm/model/store/_infra/generators.d.ts +1 -0
  21. package/esm/model/store/_infra/generators.js +4 -1
  22. package/esm/model/store/dashboardStore.d.ts +0 -2
  23. package/esm/model/store/dashboardStore.js +0 -2
  24. package/esm/model/store/filtering/dashboardFilterSelectors.d.ts +1 -1
  25. package/esm/model/store/filtering/dashboardFilterSelectors.js +8 -5
  26. package/esm/model/store/meta/metaSelectors.js +4 -3
  27. package/esm/model/store/tabs/filterContext/filterContextReducers.js +2 -2
  28. package/esm/model/store/tabs/index.d.ts +12 -0
  29. package/esm/model/store/tabs/index.js +2 -0
  30. package/esm/model/store/{parameters → tabs/parameters}/parametersReducers.d.ts +3 -7
  31. package/esm/model/store/tabs/parameters/parametersReducers.js +46 -0
  32. package/esm/model/store/tabs/parameters/parametersSelectors.d.ts +72 -0
  33. package/esm/model/store/tabs/parameters/parametersSelectors.js +228 -0
  34. package/esm/model/store/{parameters → tabs/parameters}/parametersState.d.ts +11 -1
  35. package/esm/model/store/tabs/parameters/parametersState.js +20 -0
  36. package/esm/model/store/tabs/tabsState.d.ts +2 -0
  37. package/esm/model/store/types.d.ts +0 -6
  38. package/esm/model/utils/widgetFilters.d.ts +1 -1
  39. package/esm/model/utils/widgetFilters.js +1 -1
  40. package/esm/presentation/automationFilters/components/AutomationFiltersSelect.js +8 -2
  41. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilter.d.ts +10 -0
  42. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilter.js +66 -0
  43. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilterContext.d.ts +25 -0
  44. package/esm/presentation/automationFilters/components/AutomationMeasureValueFilterContext.js +28 -0
  45. package/esm/presentation/automationFilters/useAutomationFilters.d.ts +4 -1
  46. package/esm/presentation/automationFilters/useAutomationFilters.js +35 -4
  47. package/esm/presentation/automationFilters/utils.d.ts +2 -1
  48. package/esm/presentation/automationFilters/utils.js +34 -5
  49. package/esm/presentation/dragAndDrop/draggableParameterFilter/DefaultParameterDraggingComponent.js +1 -1
  50. package/esm/presentation/dragAndDrop/useFilterDeleteDrop.js +2 -2
  51. package/esm/presentation/filterBar/filterBar/DefaultFilterBar.js +1 -1
  52. package/esm/presentation/filterBar/measureValueFilter/DefaultDashboardMeasureValueFilter.js +8 -42
  53. package/esm/presentation/filterBar/measureValueFilter/useDashboardMeasureValueFilterData.d.ts +86 -0
  54. package/esm/presentation/filterBar/measureValueFilter/useDashboardMeasureValueFilterData.js +105 -0
  55. package/esm/presentation/filterBar/parameterFilter/DashboardParameterFilter.js +3 -3
  56. package/esm/presentation/filterBar/parameterFilter/DashboardParameterPicker.js +3 -3
  57. package/esm/presentation/widget/insight/ViewModeDashboardInsight/Insight/DashboardInsight.js +1 -1
  58. package/esm/sdk-ui-dashboard.d.ts +44 -32
  59. package/package.json +21 -21
  60. package/esm/model/store/parameters/index.d.ts +0 -12
  61. package/esm/model/store/parameters/index.js +0 -14
  62. package/esm/model/store/parameters/parametersReducers.js +0 -36
  63. package/esm/model/store/parameters/parametersSelectors.d.ts +0 -67
  64. package/esm/model/store/parameters/parametersSelectors.js +0 -122
  65. package/esm/model/store/parameters/parametersState.js +0 -4
@@ -0,0 +1,228 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { createSelector } from "@reduxjs/toolkit";
3
+ import { isEqual } from "lodash-es";
4
+ import { areObjRefsEqual, insightParameters, isDashboardLayout, isInsightWidget, isNumberParameterDefinition, isVisualizationSwitcherWidget, objRefToString, } from "@gooddata/sdk-model";
5
+ import { createMemoizedSelector } from "../../_infra/selectors.js";
6
+ import { selectCatalogParameters, selectCatalogParametersIsLoaded } from "../../catalog/catalogSelectors.js";
7
+ import { selectEnableParameters } from "../../config/configSelectors.js";
8
+ import { selectInsightsMap } from "../../insights/insightsSelectors.js";
9
+ import { selectActiveTab, selectTabs } from "../tabsSelectors.js";
10
+ import { DEFAULT_TAB_ID } from "../tabsState.js";
11
+ import { parametersInitialState, pickTabParametersSource, } from "./parametersState.js";
12
+ const EMPTY_PARAMETERS = [];
13
+ const EMPTY_TABS = [];
14
+ const selectParametersState = createSelector(selectActiveTab, (activeTab) => activeTab?.parameters ?? parametersInitialState);
15
+ const selectPersistedParametersFromMeta = (state) => state.meta?.persistedDashboard?.parameters ?? EMPTY_PARAMETERS;
16
+ const selectPersistedDashboardTabsRaw = (state) => state.meta?.persistedDashboard?.tabs ?? EMPTY_TABS;
17
+ /**
18
+ * Returns the persisted-shape parameter entries currently held by the active tab.
19
+ *
20
+ * @alpha
21
+ */
22
+ export const selectDashboardParameters = createSelector(selectParametersState, (state) => state.parameters.map((entry) => entry.parameter));
23
+ /**
24
+ * Returns currently active parameter references on the active tab.
25
+ *
26
+ * @alpha
27
+ */
28
+ export const selectActiveParameterRefKeys = createSelector(selectDashboardParameters, (parameters) => new Set(parameters.map((parameter) => objRefToString(parameter.ref))));
29
+ /**
30
+ * Returns the full per-parameter entries (persisted shape + ephemeral `runtimeOverride`) for the
31
+ * active tab.
32
+ *
33
+ * @internal
34
+ */
35
+ export const selectDashboardParameterEntries = createSelector(selectParametersState, (state) => state.parameters);
36
+ /**
37
+ * Returns a selector that yields the entry held by the active tab for a given parameter ref,
38
+ * or `undefined` if no such entry exists.
39
+ *
40
+ * @alpha
41
+ */
42
+ export const selectDashboardParameterEntryByRef = createMemoizedSelector((ref) => createSelector(selectParametersState, (state) => state.parameters.find((item) => areObjRefsEqual(item.parameter.ref, ref))));
43
+ /**
44
+ * Returns a selector that yields the current `runtimeOverride` for a given parameter ref on the
45
+ * active tab, or `undefined` if the active tab does not hold an entry for that ref.
46
+ *
47
+ * @alpha
48
+ */
49
+ export const selectParameterRuntimeOverrideByRef = createMemoizedSelector((ref) => createSelector(selectDashboardParameterEntryByRef(ref), (entry) => entry?.runtimeOverride));
50
+ /**
51
+ * Computes the dashboard parameters keyed by tab `localIdentifier` in the shape that would be
52
+ * persisted on save right now.
53
+ *
54
+ * @remarks
55
+ * Smart persistence applies independently per tab: `value` is dropped when equal to the
56
+ * workspace default, `label` is dropped when equal to the parameter title, all per tab.
57
+ * Non-resolved entries (catalog not loaded, gated off, ref missing) are emitted verbatim from
58
+ * the previously persisted entry on the same tab when available. V1 fallback applies when no
59
+ * tab in the persisted dashboard carries `parameters` — the persisted root array is used as the
60
+ * persistence source for every tab.
61
+ *
62
+ * @internal
63
+ */
64
+ export const selectSmartPersistedTabsParameters = createSelector(selectTabs, selectCatalogParameters, selectCatalogParametersIsLoaded, selectPersistedDashboardTabsRaw, selectPersistedParametersFromMeta, (tabs, workspaceParameters, isCatalogLoaded, persistedTabs, rootPersistedParameters) => {
65
+ const result = {};
66
+ if (!tabs) {
67
+ return result;
68
+ }
69
+ const workspaceByRef = new Map(workspaceParameters.map((wp) => [objRefToString(wp.ref), wp]));
70
+ const persistedByTabAndRef = buildPersistedByTabAndRef(persistedTabs, rootPersistedParameters);
71
+ for (const tab of tabs) {
72
+ const entries = tab.parameters?.parameters ?? [];
73
+ const persistedForTab = persistedByTabAndRef.get(tab.localIdentifier) ?? new Map();
74
+ result[tab.localIdentifier] = entries.map((entry) => {
75
+ const refKey = objRefToString(entry.parameter.ref);
76
+ const workspaceParameter = isCatalogLoaded ? workspaceByRef.get(refKey) : undefined;
77
+ if (!workspaceParameter) {
78
+ return persistedForTab.get(refKey) ?? entry.parameter;
79
+ }
80
+ return smartPersistResolvedEntry(entry, workspaceParameter);
81
+ });
82
+ }
83
+ return result;
84
+ });
85
+ /**
86
+ * Returns persisted parameters from `meta.persistedDashboard`, keyed by tab `localIdentifier`.
87
+ * Honors V1 → per-tab migration: when no tab in the persisted dashboard carries `parameters`,
88
+ * the persisted root `parameters` is used as fallback for every tab. For legacy single-tab
89
+ * dashboards (no `tabs[]` in persistedDashboard), the synthetic `DEFAULT_TAB_ID` tab carries
90
+ * the persisted root parameters.
91
+ *
92
+ * @internal
93
+ */
94
+ const selectPersistedTabsParametersFromMeta = createSelector(selectPersistedDashboardTabsRaw, selectPersistedParametersFromMeta, (persistedTabs, rootPersistedParameters) => {
95
+ const result = {};
96
+ if (persistedTabs.length === 0) {
97
+ result[DEFAULT_TAB_ID] = rootPersistedParameters;
98
+ return result;
99
+ }
100
+ for (const tab of persistedTabs) {
101
+ result[tab.localIdentifier] =
102
+ pickTabParametersSource(tab, persistedTabs, rootPersistedParameters) ?? EMPTY_PARAMETERS;
103
+ }
104
+ return result;
105
+ });
106
+ /**
107
+ * Returns true if the dashboard parameters that would be persisted differ from the persisted
108
+ * version on any tab.
109
+ *
110
+ * @alpha
111
+ */
112
+ export const selectIsParametersChanged = createSelector(selectSmartPersistedTabsParameters, selectPersistedTabsParametersFromMeta, (smartPersistedByTab, persistedByTab) => {
113
+ const allTabIds = new Set([...Object.keys(smartPersistedByTab), ...Object.keys(persistedByTab)]);
114
+ for (const tabId of allTabIds) {
115
+ const smart = smartPersistedByTab[tabId] ?? EMPTY_PARAMETERS;
116
+ const persisted = persistedByTab[tabId] ?? EMPTY_PARAMETERS;
117
+ if (!isEqual(smart, persisted)) {
118
+ return true;
119
+ }
120
+ }
121
+ return false;
122
+ });
123
+ /**
124
+ * Returns the parameter values to inject into the widget's `IExecutionConfig.parameterValues`.
125
+ *
126
+ * @remarks
127
+ * The widget's owning tab is resolved from layout, then the result is the intersection of that
128
+ * tab's parameter entries and the parameters referenced by the widget's insight (per
129
+ * `insightParameters`). Dashboard parameters not referenced by the widget's insight are excluded
130
+ * so that adding/removing unrelated parameters does not invalidate the widget's `defFingerprint`.
131
+ * Returns an empty array when `enableParameters` is off so persisted parameter values cannot
132
+ * silently affect execution while the UI is hidden.
133
+ *
134
+ * @alpha
135
+ */
136
+ export const selectEffectiveParameterValuesForWidget = createMemoizedSelector((ref) => createSelector(selectParameterExecutionContextByWidgetRef(ref), selectInsightsMap, selectEnableParameters, (context, insights, isEnabled) => {
137
+ if (!isEnabled || !context) {
138
+ return [];
139
+ }
140
+ const insight = insights.get(context.widget.insight);
141
+ if (!insight) {
142
+ return [];
143
+ }
144
+ const entries = context.tab.parameters?.parameters ?? parametersInitialState.parameters;
145
+ const referencedRefs = new Set(insightParameters(insight).map((parameter) => objRefToString(parameter.ref)));
146
+ const result = [];
147
+ for (const entry of entries) {
148
+ if (entry.runtimeOverride === undefined) {
149
+ continue;
150
+ }
151
+ if (referencedRefs.has(objRefToString(entry.parameter.ref))) {
152
+ result.push({ ref: entry.parameter.ref, value: entry.runtimeOverride });
153
+ }
154
+ }
155
+ return result;
156
+ }));
157
+ const selectParameterExecutionContextByWidgetRef = createMemoizedSelector((ref) => createSelector(selectTabs, (tabs) => findParameterExecutionContext(tabs, ref)));
158
+ function findParameterExecutionContext(tabs, ref) {
159
+ if (!ref || !tabs) {
160
+ return undefined;
161
+ }
162
+ for (const tab of tabs) {
163
+ const layout = tab.layout?.layout;
164
+ if (!layout) {
165
+ continue;
166
+ }
167
+ const widget = findInsightWidgetInLayout(layout, ref);
168
+ if (widget) {
169
+ return { tab, widget };
170
+ }
171
+ }
172
+ return undefined;
173
+ }
174
+ function findInsightWidgetInLayout(layout, ref) {
175
+ for (const section of layout.sections) {
176
+ for (const item of section.items) {
177
+ const widget = item.widget;
178
+ if (!widget) {
179
+ continue;
180
+ }
181
+ if (isInsightWidget(widget) && areObjRefsEqual(widget.ref, ref)) {
182
+ return widget;
183
+ }
184
+ if (isDashboardLayout(widget)) {
185
+ const nestedWidget = findInsightWidgetInLayout(widget, ref);
186
+ if (nestedWidget) {
187
+ return nestedWidget;
188
+ }
189
+ }
190
+ if (isVisualizationSwitcherWidget(widget)) {
191
+ const visualization = widget.visualizations.find((visualization) => areObjRefsEqual(visualization.ref, ref));
192
+ if (visualization) {
193
+ return visualization;
194
+ }
195
+ }
196
+ }
197
+ }
198
+ return undefined;
199
+ }
200
+ function buildPersistedByTabAndRef(persistedTabs, rootPersistedParameters) {
201
+ const result = new Map();
202
+ for (const tab of persistedTabs) {
203
+ const sourceParameters = pickTabParametersSource(tab, persistedTabs, rootPersistedParameters) ?? EMPTY_PARAMETERS;
204
+ result.set(tab.localIdentifier, new Map(sourceParameters.map((parameter) => [objRefToString(parameter.ref), parameter])));
205
+ }
206
+ return result;
207
+ }
208
+ function smartPersistResolvedEntry(entry, workspaceParameter) {
209
+ const workspaceDefault = isNumberParameterDefinition(workspaceParameter.definition)
210
+ ? workspaceParameter.definition.defaultValue
211
+ : undefined;
212
+ const result = {
213
+ ref: entry.parameter.ref,
214
+ parameterType: entry.parameter.parameterType,
215
+ mode: entry.parameter.mode,
216
+ ...labelOverride(entry, workspaceParameter),
217
+ };
218
+ if (entry.runtimeOverride === undefined || entry.runtimeOverride === workspaceDefault) {
219
+ return result;
220
+ }
221
+ return { ...result, value: entry.runtimeOverride };
222
+ }
223
+ function labelOverride(entry, workspaceParameter) {
224
+ if (entry.parameter.label && entry.parameter.label !== workspaceParameter.title) {
225
+ return { label: entry.parameter.label };
226
+ }
227
+ return {};
228
+ }
@@ -1,4 +1,4 @@
1
- import { type IDashboardParameter } from "@gooddata/sdk-model";
1
+ import { type IDashboardParameter, type IDashboardTab } from "@gooddata/sdk-model";
2
2
  /**
3
3
  * Per-parameter state tracked by the dashboard store.
4
4
  *
@@ -26,3 +26,13 @@ export interface IParametersState {
26
26
  parameters: IDashboardParameterEntry[];
27
27
  }
28
28
  export declare const parametersInitialState: IParametersState;
29
+ /**
30
+ * Picks a tab's persisted parameter source under the V1 → per-tab migration rule.
31
+ *
32
+ * - If the tab has its own `parameters` (including `[]`) → return it.
33
+ * - Else if every tab's `parameters === undefined` AND root `parameters` is defined → root array.
34
+ * - Else → `undefined`.
35
+ *
36
+ * @internal
37
+ */
38
+ export declare function pickTabParametersSource(tab: IDashboardTab, allTabs: ReadonlyArray<IDashboardTab>, rootParameters: IDashboardParameter[] | undefined): IDashboardParameter[] | undefined;
@@ -0,0 +1,20 @@
1
+ // (C) 2026 GoodData Corporation
2
+ export const parametersInitialState = {
3
+ parameters: [],
4
+ };
5
+ /**
6
+ * Picks a tab's persisted parameter source under the V1 → per-tab migration rule.
7
+ *
8
+ * - If the tab has its own `parameters` (including `[]`) → return it.
9
+ * - Else if every tab's `parameters === undefined` AND root `parameters` is defined → root array.
10
+ * - Else → `undefined`.
11
+ *
12
+ * @internal
13
+ */
14
+ export function pickTabParametersSource(tab, allTabs, rootParameters) {
15
+ if (tab.parameters !== undefined) {
16
+ return tab.parameters;
17
+ }
18
+ const everyTabUndefined = allTabs.length > 0 && allTabs.every((other) => other.parameters === undefined);
19
+ return everyTabUndefined ? rootParameters : undefined;
20
+ }
@@ -6,6 +6,7 @@ import { type IDateFilterConfigsState } from "./dateFilterConfigs/dateFilterConf
6
6
  import type { FilterContextState } from "./filterContext/filterContextState.js";
7
7
  import type { ILayoutState } from "./layout/layoutState.js";
8
8
  import { type IMeasureValueFilterConfigsState } from "./measureValueFilterConfigs/measureValueFilterConfigsState.js";
9
+ import { type IParametersState } from "./parameters/parametersState.js";
9
10
  /**
10
11
  * Identifier used for dashboards without explicit tabs support and the first tab created automatically.
11
12
  *
@@ -36,6 +37,7 @@ export interface ITabState {
36
37
  filterContext?: FilterContextState;
37
38
  filterGroupsConfig?: IDashboardFilterGroupsConfig;
38
39
  layout?: ILayoutState;
40
+ parameters?: IParametersState;
39
41
  /**
40
42
  * UI-only flag indicating the tab is currently being renamed.
41
43
  * @internal
@@ -17,7 +17,6 @@ import { type IFilterViewsState } from "./filterViews/filterViewsState.js";
17
17
  import { type ILoadingState } from "./loading/loadingState.js";
18
18
  import { type IDashboardMetaState } from "./meta/metaState.js";
19
19
  import { type INotificationChannelsState } from "./notificationChannels/notificationChannelsState.js";
20
- import { type IParametersState } from "./parameters/parametersState.js";
21
20
  import { type PermissionsState } from "./permissions/permissionsState.js";
22
21
  import { type IRenderModeState } from "./renderMode/renderModeState.js";
23
22
  import { type SavingState } from "./saving/savingState.js";
@@ -75,11 +74,6 @@ export type DashboardState = {
75
74
  users: IUsersState;
76
75
  /** @alpha */
77
76
  notificationChannels: INotificationChannelsState;
78
- /**
79
- * Dashboard-level parameter overrides slice.
80
- * @alpha
81
- */
82
- parameters: IParametersState;
83
77
  /**
84
78
  * Internal state for dashboard summary AI workflow.
85
79
  *
@@ -7,4 +7,4 @@ export declare function removeIgnoredWidgetFilters(filters: FilterContextItem[],
7
7
  /**
8
8
  * @internal
9
9
  */
10
- export declare function removeDateFilters(filters: FilterContextItem[]): DashboardAttributeFilterItem[];
10
+ export declare function getAttributeFilters(filters: FilterContextItem[]): DashboardAttributeFilterItem[];
@@ -27,6 +27,6 @@ export function removeIgnoredWidgetFilters(filters, widget) {
27
27
  /**
28
28
  * @internal
29
29
  */
30
- export function removeDateFilters(filters) {
30
+ export function getAttributeFilters(filters) {
31
31
  return filters.filter(isDashboardAttributeFilterItem);
32
32
  }
@@ -9,6 +9,7 @@ import { AttributesDropdown } from "../../filterBar/attributeFilter/addAttribute
9
9
  import { useAutomationFilters, useAutomationFiltersByTab } from "../useAutomationFilters.js";
10
10
  import { AutomationAttributeFilter } from "./AutomationAttributeFilter.js";
11
11
  import { AutomationDateFilter } from "./AutomationDateFilter.js";
12
+ import { AutomationMeasureValueFilter } from "./AutomationMeasureValueFilter.js";
12
13
  const COLLAPSED_FILTERS_COUNT = 2;
13
14
  function AutomationCheckboxOrNote({ isDashboardAutomation, storeFilters, handleStoreFiltersChange, handleKeyDown, automationFilterSelectTooltipId, }) {
14
15
  const intl = useIntl();
@@ -51,6 +52,7 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
51
52
  const filters = shouldRenderByTab ? [] : flatFiltersData.visibleFilters;
52
53
  const attributes = shouldRenderByTab ? [] : flatFiltersData.attributes;
53
54
  const dateDatasets = shouldRenderByTab ? [] : flatFiltersData.dateDatasets;
55
+ const measures = shouldRenderByTab ? [] : flatFiltersData.measures;
54
56
  const handleChangeFilter = shouldRenderByTab
55
57
  ? () => { } // Not used in tab mode
56
58
  : flatFiltersData.handleChangeFilter;
@@ -109,7 +111,7 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
109
111
  // Add button for each tab section
110
112
  addFilterButton: _jsx(AttributesDropdown, { id: `${AUTOMATION_FILTERS_DIALOG_ID}-${tab.tabId}`, onClose: () => { }, onSelect: (value) => {
111
113
  tabFiltersData.handleTabFilterAdd(tab.tabId, value, tab.attributes, tab.dateDatasets);
112
- }, attributes: tab.attributes, dateDatasets: tab.dateDatasets, openOnInit: false, overlayPositionType: overlayPositionType, className: "gd-automation-filters__dropdown s-automation-filters-add-filter-dropdown", getCustomItemTitle: (item) => getCatalogItemCustomTitle(item, availableFilters, tab.dateConfigs), accessibilityConfig: {
114
+ }, attributes: tab.attributes, dateDatasets: tab.dateDatasets, measures: tab.measures, openOnInit: false, overlayPositionType: overlayPositionType, className: "gd-automation-filters__dropdown s-automation-filters-add-filter-dropdown", getCustomItemTitle: (item) => getCatalogItemCustomTitle(item, availableFilters, tab.dateConfigs), accessibilityConfig: {
113
115
  ariaLabelledBy: AUTOMATION_FILTERS_DIALOG_TITLE_ID,
114
116
  searchAriaLabel: searchAriaLabel,
115
117
  }, DropdownButtonComponent: ({ buttonRef, isOpen, onClick }) => (_jsx(UiTooltip, { arrowPlacement: "left", triggerBy: ["hover", "focus"], content: tabTooltipText, anchor: _jsx(ButtonDisabledFocusableWrapper, { isDisabled: isTabAddButtonDisabled, ariaLabel: tabTooltipText, onRefSet: (element) => setAddFilterButtonRefs(element, buttonRef), children: _jsx(UiIconButton, { icon: "plus", label: tabTooltipText, onClick: onClick, variant: "tertiary", isDisabled: isTabAddButtonDisabled, ref: (element) => {
@@ -136,7 +138,7 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
136
138
  }), isExpanded || !isExpandable ? (_jsx(AttributesDropdown, { id: AUTOMATION_FILTERS_DIALOG_ID, onClose: () => { }, onSelect: (value) => {
137
139
  handleAddFilter(value, attributes, dateDatasets);
138
140
  setIsExpanded(true);
139
- }, attributes: attributes, dateDatasets: dateDatasets, openOnInit: false, overlayPositionType: overlayPositionType, className: "gd-automation-filters__dropdown s-automation-filters-add-filter-dropdown", getCustomItemTitle: (item) => getCatalogItemCustomTitle(item, availableFilters, dateConfigs), accessibilityConfig: {
141
+ }, attributes: attributes, dateDatasets: dateDatasets, measures: measures, openOnInit: false, overlayPositionType: overlayPositionType, className: "gd-automation-filters__dropdown s-automation-filters-add-filter-dropdown", getCustomItemTitle: (item) => getCatalogItemCustomTitle(item, availableFilters, dateConfigs), accessibilityConfig: {
140
142
  ariaLabelledBy: AUTOMATION_FILTERS_DIALOG_TITLE_ID,
141
143
  searchAriaLabel: searchAriaLabel,
142
144
  }, DropdownButtonComponent: ({ buttonRef, isOpen, onClick }) => (_jsx(UiTooltip, { arrowPlacement: "left", triggerBy: ["hover", "focus"], content: tooltipText, anchor: _jsx(ButtonDisabledFocusableWrapper, { isDisabled: isAddButtonDisabled, ariaLabel: tooltipText, onRefSet: (element) => setAddFilterButtonRefs(element, buttonRef), children: _jsx(UiIconButton, { icon: "plus", label: tooltipText, onClick: onClick, variant: "tertiary", isDisabled: isAddButtonDisabled, ref: (element) => {
@@ -170,6 +172,10 @@ function AutomationFilter({ filter, attributeConfigs, onChange, onDelete, isComm
170
172
  const isLocked = lockedFilters.some((f) => dashboardFilterLocalIdentifier(f) === dashboardFilterLocalIdentifier(filter));
171
173
  return (_jsx(AutomationDateFilter, { filter: filter, onChange: onChange, onDelete: onDelete, isLocked: isLocked, isCommonDateFilter: isCommonDateFilter, overlayPositionType: overlayPositionType, readonly: isReadOnly, tabId: tabId }, filter.dateFilter.localIdentifier));
172
174
  }
175
+ else if (isDashboardMeasureValueFilter(filter)) {
176
+ const isLocked = lockedFilters.some((f) => dashboardFilterLocalIdentifier(f) === dashboardFilterLocalIdentifier(filter));
177
+ return (_jsx(AutomationMeasureValueFilter, { filter: filter, onChange: onChange, onDelete: onDelete, isLocked: isLocked, overlayPositionType: overlayPositionType, readonly: isReadOnly }, filter.dashboardMeasureValueFilter.localIdentifier));
178
+ }
173
179
  return null;
174
180
  }
175
181
  /**
@@ -0,0 +1,10 @@
1
+ import { type FilterContextItem, type IDashboardMeasureValueFilter } from "@gooddata/sdk-model";
2
+ import { type OverlayPositionType } from "@gooddata/sdk-ui-kit";
3
+ export declare function AutomationMeasureValueFilter({ filter, onChange, onDelete, isLocked, overlayPositionType, readonly }: {
4
+ filter: IDashboardMeasureValueFilter;
5
+ onChange: (filter: FilterContextItem) => void;
6
+ onDelete: (filter: FilterContextItem) => void;
7
+ isLocked?: boolean;
8
+ overlayPositionType?: OverlayPositionType;
9
+ readonly?: boolean;
10
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,66 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { useCallback } from "react";
4
+ import { useIntl } from "react-intl";
5
+ import { MeasureValueFilter } from "@gooddata/sdk-ui-filters";
6
+ import { UiChip, UiTooltip, isActionKey, useIdPrefixed, } from "@gooddata/sdk-ui-kit";
7
+ import { getSharedDashboardMvfProps, normalizeMeasureValueFilterConditions, useDashboardMeasureValueFilterData, } from "../../filterBar/measureValueFilter/useDashboardMeasureValueFilterData.js";
8
+ import { AutomationMeasureValueFilterProvider, useAutomationMeasureValueFilterContext, } from "./AutomationMeasureValueFilterContext.js";
9
+ export function AutomationMeasureValueFilter({ filter, onChange, onDelete, isLocked, overlayPositionType, readonly, }) {
10
+ const intl = useIntl();
11
+ const deleteAriaLabel = intl.formatMessage({ id: "dialogs.automation.filters.deleteAriaLabel" });
12
+ const deleteTooltipContent = intl.formatMessage({ id: "dialogs.automation.filters.deleteTooltip" });
13
+ const lockedTooltipContent = intl.formatMessage({ id: "dialogs.automation.filters.lockedTooltip" });
14
+ // Automation/scheduling has no "Apply together" working filter — just reflect the persisted filter.
15
+ const mvfData = useDashboardMeasureValueFilterData(filter);
16
+ const { conditionLabel } = mvfData;
17
+ const handleApply = useCallback((updated) => {
18
+ const newConditions = normalizeMeasureValueFilterConditions(updated);
19
+ const next = {
20
+ dashboardMeasureValueFilter: {
21
+ ...filter.dashboardMeasureValueFilter,
22
+ ...(newConditions && newConditions.length > 0
23
+ ? { conditions: newConditions }
24
+ : { conditions: undefined }),
25
+ },
26
+ };
27
+ onChange(next);
28
+ }, [filter, onChange]);
29
+ return (_jsx(AutomationMeasureValueFilterProvider, { filter: filter, onChange: onChange, onDelete: onDelete, isLocked: isLocked, deleteAriaLabel: deleteAriaLabel, deleteTooltipContent: deleteTooltipContent, lockedTooltipContent: lockedTooltipContent, children: _jsx(MeasureValueFilter, { ...getSharedDashboardMvfProps(mvfData), onApply: handleApply, DropdownButtonComponent: (props) => (_jsx(AutomationMeasureValueFilterButton, { ...props, conditionLabel: conditionLabel, overlayPositionType: overlayPositionType, readonly: readonly })) }) }));
30
+ }
31
+ function AutomationMeasureValueFilterButton({ isActive, buttonTitle, onClick, conditionLabel, readonly, }) {
32
+ const { isLocked, onDelete, filter, deleteAriaLabel, deleteTooltipContent, lockedTooltipContent } = useAutomationMeasureValueFilterContext();
33
+ const label = `${buttonTitle}: ${conditionLabel}`;
34
+ const mvfTooltipId = useIdPrefixed("mvf-filter-tooltip");
35
+ const mvfDeleteTooltipId = useIdPrefixed("mvf-filter-delete-tooltip");
36
+ const tooltipContent = (_jsxs(_Fragment, { children: [label, isLocked ? _jsx("div", { children: lockedTooltipContent }) : null] }));
37
+ // The whole-panel disabled state is handled by a visual overlay in
38
+ // AutomationFiltersSelect (`gd-automation-filters__overlay`), so the chip should not
39
+ // claim a locked state from it. The lock icon only reflects the filter's own config.
40
+ const isDeletable = !isLocked && !readonly;
41
+ return (_jsx(UiChip, { label: label, iconBefore: "metric", isActive: isActive, isLocked: isLocked, isDeletable: isDeletable, onClick: onClick, onDelete: () => onDelete?.(filter), onKeyDown: (event) => {
42
+ // In case the button is locked and not disabled we need to explicitly
43
+ // stop the event propagation to prevent dropdown from opening
44
+ if (isLocked && isActionKey(event)) {
45
+ event.stopPropagation();
46
+ }
47
+ }, onDeleteKeyDown: (event) => {
48
+ // Do not propagate event to parent as MVF dropdown would always open
49
+ if (isActionKey(event)) {
50
+ event.stopPropagation();
51
+ }
52
+ }, accessibilityConfig: {
53
+ isExpanded: isActive,
54
+ popupType: "dialog",
55
+ ariaDescribedBy: mvfTooltipId,
56
+ deleteAriaLabel: buttonTitle ? `${deleteAriaLabel} ${buttonTitle}` : deleteAriaLabel,
57
+ deleteAriaDescribedBy: mvfDeleteTooltipId,
58
+ }, renderChipContent: (content) => (_jsx(UiTooltip, { id: mvfTooltipId, arrowPlacement: "top-start", content: tooltipContent, triggerBy: ["hover", "focus"], anchor: content, anchorWrapperStyles: {
59
+ display: "flex",
60
+ width: "100%",
61
+ height: "100%",
62
+ minWidth: 0,
63
+ } })), renderDeleteButton: (button) => (_jsx(UiTooltip, { id: mvfDeleteTooltipId, arrowPlacement: "top-start", content: deleteTooltipContent, triggerBy: ["hover", "focus"], anchor: button, anchorWrapperStyles: {
64
+ height: "100%",
65
+ } })) }));
66
+ }
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from "react";
2
+ import { type FilterContextItem, type IDashboardMeasureValueFilter } from "@gooddata/sdk-model";
3
+ /**
4
+ * @internal
5
+ */
6
+ export interface IAutomationMeasureValueFilterContext {
7
+ onChange: (filter: FilterContextItem) => void;
8
+ onDelete: (filter: FilterContextItem) => void;
9
+ filter: IDashboardMeasureValueFilter;
10
+ isLocked?: boolean;
11
+ deleteAriaLabel?: string;
12
+ deleteTooltipContent?: string;
13
+ lockedTooltipContent?: string;
14
+ }
15
+ /**
16
+ * @internal
17
+ */
18
+ export declare const useAutomationMeasureValueFilterContext: () => IAutomationMeasureValueFilterContext;
19
+ export interface IAutomationMeasureValueFilterProviderProps extends IAutomationMeasureValueFilterContext {
20
+ children: ReactNode;
21
+ }
22
+ /**
23
+ * @internal
24
+ */
25
+ export declare function AutomationMeasureValueFilterProvider({ children, onChange, onDelete, isLocked, filter, deleteAriaLabel, deleteTooltipContent, lockedTooltipContent }: IAutomationMeasureValueFilterProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -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;