@gooddata/sdk-ui-dashboard 11.36.0-alpha.1 → 11.36.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 (55) hide show
  1. package/NOTICE +3 -3
  2. package/esm/__version.d.ts +1 -1
  3. package/esm/__version.js +1 -1
  4. package/esm/_staging/drills/drillingUtils.d.ts +9 -0
  5. package/esm/_staging/drills/drillingUtils.js +19 -1
  6. package/esm/index.d.ts +2 -2
  7. package/esm/index.js +1 -1
  8. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/index.js +9 -2
  9. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/loadMeasureParameterDependencies.d.ts +12 -0
  10. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/loadMeasureParameterDependencies.js +98 -0
  11. package/esm/model/commandHandlers/dashboard/initializeDashboardHandler/resolveDashboardConfig.js +1 -0
  12. package/esm/model/commandHandlers/drill/common/mergeFilters.d.ts +3 -3
  13. package/esm/model/commandHandlers/drill/common/mergeFilters.js +26 -1
  14. package/esm/model/commandHandlers/drill/common/sourceDrillFilters.d.ts +2 -1
  15. package/esm/model/commandHandlers/drill/common/sourceDrillFilters.js +25 -5
  16. package/esm/model/commandHandlers/drill/drillToDashboardHandler.js +17 -16
  17. package/esm/model/commandHandlers/filterContext/changeFilterContextSelectionHandler.js +7 -4
  18. package/esm/model/commandHandlers/filterContext/measureValueFilter/removeMeasureValueFilterHandler.js +7 -0
  19. package/esm/model/commandHandlers/scheduledEmail/initializeAutomationsHandler.js +7 -1
  20. package/esm/model/react/useWidgetFilters.js +4 -1
  21. package/esm/model/store/catalog/catalogReducers.d.ts +5 -1
  22. package/esm/model/store/catalog/catalogReducers.js +4 -0
  23. package/esm/model/store/catalog/catalogSelectors.d.ts +15 -2
  24. package/esm/model/store/catalog/catalogSelectors.js +13 -0
  25. package/esm/model/store/catalog/catalogState.d.ts +20 -1
  26. package/esm/model/store/catalog/catalogState.js +1 -0
  27. package/esm/model/store/catalog/index.d.ts +1 -0
  28. package/esm/model/store/tabs/index.d.ts +6 -0
  29. package/esm/model/store/tabs/layout/layoutReducers.d.ts +4 -0
  30. package/esm/model/store/tabs/layout/layoutReducers.js +25 -0
  31. package/esm/model/store/tabs/parameters/parametersSelectors.d.ts +19 -6
  32. package/esm/model/store/tabs/parameters/parametersSelectors.js +69 -16
  33. package/esm/presentation/filterBar/parameterFilter/DashboardParameterFilter.js +3 -2
  34. package/esm/presentation/localization/bundles/en-US.localization-bundle.d.ts +8 -0
  35. package/esm/presentation/localization/bundles/en-US.localization-bundle.js +8 -0
  36. package/esm/presentation/widget/common/configuration/FilterConfiguration.js +4 -2
  37. package/esm/presentation/widget/common/configuration/useMeasureValueFilterCompatibility.d.ts +6 -2
  38. package/esm/presentation/widget/common/configuration/useMeasureValueFilterCompatibility.js +95 -22
  39. package/esm/presentation/widget/insight/ViewModeDashboardInsight/InsightDrillDialog/useExcludedDrillDefinitionFilters.js +2 -2
  40. package/esm/presentation/widget/insight/configuration/DrillFilters/TargetDashboardFiltersContext.d.ts +2 -1
  41. package/esm/presentation/widget/insight/configuration/DrillFilters/TargetDashboardFiltersContext.js +3 -1
  42. package/esm/presentation/widget/insight/configuration/DrillFilters/drillFiltersConfigUtils.d.ts +4 -1
  43. package/esm/presentation/widget/insight/configuration/DrillFilters/drillFiltersConfigUtils.js +15 -1
  44. package/esm/presentation/widget/insight/configuration/DrillFilters/messages.d.ts +6 -0
  45. package/esm/presentation/widget/insight/configuration/DrillFilters/messages.js +6 -0
  46. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapDashboardFilterToOption.d.ts +6 -2
  47. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapDashboardFilterToOption.js +18 -3
  48. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapSourceInsightFilterToOption.d.ts +4 -2
  49. package/esm/presentation/widget/insight/configuration/DrillFilters/optionMappings/mapSourceInsightFilterToOption.js +10 -6
  50. package/esm/presentation/widget/insight/configuration/DrillFilters/types.d.ts +2 -1
  51. package/esm/presentation/widget/insight/configuration/DrillFilters/useDrillFiltersConfig.js +78 -6
  52. package/esm/presentation/widget/insight/configuration/DrillFilters/useFetchTargetDashboardFilters.d.ts +2 -1
  53. package/esm/presentation/widget/insight/configuration/DrillFilters/useFetchTargetDashboardFilters.js +4 -1
  54. package/esm/sdk-ui-dashboard.d.ts +53 -6
  55. package/package.json +20 -20
@@ -1,8 +1,8 @@
1
- import { type IAttributeDisplayFormMetadataObject, type ICatalogAttribute, type ICatalogAttributeHierarchy, type ICatalogDateAttribute, type ICatalogDateAttributeHierarchy, type ICatalogDateDataset, type ICatalogFact, type ICatalogMeasure, type IDateHierarchyTemplate, type IParameterMetadataObject, type ObjRef } from "@gooddata/sdk-model";
1
+ import { type IAttributeDisplayFormMetadataObject, type ICatalogAttribute, type ICatalogAttributeHierarchy, type ICatalogDateAttribute, type ICatalogDateAttributeHierarchy, type ICatalogDateDataset, type ICatalogFact, type ICatalogMeasure, type IDateHierarchyTemplate, type IParameterMetadataObject, type IdentifierRef, type ObjRef } from "@gooddata/sdk-model";
2
2
  import { type CatalogDateAttributeWithDataset } from "../../../_staging/catalog/dateAttributeWithDatasetMap.js";
3
3
  import { type ObjRefMap } from "../../../_staging/metadata/objRefMap.js";
4
4
  import { type DashboardSelector } from "../types.js";
5
- import { type CatalogParametersStatus } from "./catalogState.js";
5
+ import { type CatalogMeasureParametersStatus, type CatalogParametersStatus } from "./catalogState.js";
6
6
  /**
7
7
  * @public
8
8
  */
@@ -32,6 +32,19 @@ export declare const selectCatalogParametersStatus: DashboardSelector<CatalogPar
32
32
  * @alpha
33
33
  */
34
34
  export declare const selectCatalogParametersIsLoaded: DashboardSelector<boolean>;
35
+ /**
36
+ * Returns the dashboard-wide map from metric ref string to the parameter refs the metric depends on.
37
+ * The map is populated during dashboard initialization from the workspace references service.
38
+ *
39
+ * @alpha
40
+ */
41
+ export declare const selectCatalogMeasureParameters: DashboardSelector<Record<string, IdentifierRef[]>>;
42
+ /**
43
+ * Returns the load status of the dashboard-wide metric → parameter dependency map.
44
+ *
45
+ * @alpha
46
+ */
47
+ export declare const selectCatalogMeasureParametersStatus: DashboardSelector<CatalogMeasureParametersStatus>;
35
48
  /**
36
49
  * @alpha
37
50
  */
@@ -39,6 +39,19 @@ export const selectCatalogParametersStatus = createSelector(selectSelf, (state)
39
39
  * @alpha
40
40
  */
41
41
  export const selectCatalogParametersIsLoaded = createSelector(selectCatalogParametersStatus, (status) => status === "loaded");
42
+ /**
43
+ * Returns the dashboard-wide map from metric ref string to the parameter refs the metric depends on.
44
+ * The map is populated during dashboard initialization from the workspace references service.
45
+ *
46
+ * @alpha
47
+ */
48
+ export const selectCatalogMeasureParameters = createSelector(selectSelf, (state) => state.measureParameters.byMetric);
49
+ /**
50
+ * Returns the load status of the dashboard-wide metric → parameter dependency map.
51
+ *
52
+ * @alpha
53
+ */
54
+ export const selectCatalogMeasureParametersStatus = createSelector(selectSelf, (state) => state.measureParameters.status);
42
55
  /**
43
56
  * @alpha
44
57
  */
@@ -1,4 +1,4 @@
1
- import { type ICatalogAttribute, type ICatalogAttributeHierarchy, type ICatalogDateDataset, type ICatalogFact, type ICatalogMeasure, type IDateHierarchyTemplate, type IParameterMetadataObject } from "@gooddata/sdk-model";
1
+ import { type ICatalogAttribute, type ICatalogAttributeHierarchy, type ICatalogDateDataset, type ICatalogFact, type ICatalogMeasure, type IDateHierarchyTemplate, type IParameterMetadataObject, type IdentifierRef } from "@gooddata/sdk-model";
2
2
  /**
3
3
  * Status of catalog parameters loading.
4
4
  *
@@ -14,6 +14,23 @@ export interface ICatalogParametersState {
14
14
  status: CatalogParametersStatus;
15
15
  parameters: IParameterMetadataObject[];
16
16
  }
17
+ /**
18
+ * Status of the dashboard-wide metric → parameter dependency map.
19
+ *
20
+ * @alpha
21
+ */
22
+ export type CatalogMeasureParametersStatus = "uninitialized" | "loading" | "loaded" | "failed";
23
+ /**
24
+ * Maps each referenced metric (keyed by `objRefToString(metricRef)`) to the parameter refs the
25
+ * metric depends on, as reported by the workspace references service. Drives runtime parameter
26
+ * applicability for widget execution.
27
+ *
28
+ * @alpha
29
+ */
30
+ export interface ICatalogMeasureParametersState {
31
+ status: CatalogMeasureParametersStatus;
32
+ byMetric: Record<string, IdentifierRef[]>;
33
+ }
17
34
  /**
18
35
  * @public
19
36
  */
@@ -32,5 +49,7 @@ export type CatalogState = {
32
49
  dateHierarchyTemplates?: IDateHierarchyTemplate[];
33
50
  /** @alpha */
34
51
  parameters: ICatalogParametersState;
52
+ /** @alpha */
53
+ measureParameters: ICatalogMeasureParametersState;
35
54
  };
36
55
  export declare const catalogInitialState: CatalogState;
@@ -7,4 +7,5 @@ export const catalogInitialState = {
7
7
  attributeHierarchies: undefined,
8
8
  dateHierarchyTemplates: undefined,
9
9
  parameters: { status: "uninitialized", parameters: [] },
10
+ measureParameters: { status: "uninitialized", byMetric: {} },
10
11
  };
@@ -11,4 +11,5 @@ export declare const catalogActions: {
11
11
  updateAttributeHierarchy: import("@reduxjs/toolkit").ActionCreatorWithPayload<import("@gooddata/sdk-model").ICatalogAttributeHierarchy, "catalog/updateAttributeHierarchy">;
12
12
  deleteAttributeHierarchy: import("@reduxjs/toolkit").ActionCreatorWithPayload<import("@gooddata/sdk-model").ICatalogAttributeHierarchy, "catalog/deleteAttributeHierarchy">;
13
13
  setCatalogParameters: import("@reduxjs/toolkit").ActionCreatorWithPayload<import("./catalogState.js").ICatalogParametersState, "catalog/setCatalogParameters">;
14
+ setCatalogMeasureParameters: import("@reduxjs/toolkit").ActionCreatorWithPayload<import("./catalogState.js").ICatalogMeasureParametersState, "catalog/setCatalogMeasureParameters">;
14
15
  };
@@ -278,6 +278,12 @@ export declare const tabsActions: import("@reduxjs/toolkit").CaseReducerActions<
278
278
  };
279
279
  type: string;
280
280
  }) => void | import("./tabsState.js").ITabsState | import("immer").WritableDraft<import("./tabsState.js").ITabsState>;
281
+ readonly removeIgnoredMeasureValueFilter: (state: import("immer").WritableDraft<import("./tabsState.js").ITabsState>, action: {
282
+ payload: {
283
+ measureRefs: import("@gooddata/sdk-model").ObjRef[];
284
+ };
285
+ type: string;
286
+ }) => void | import("./tabsState.js").ITabsState | import("immer").WritableDraft<import("./tabsState.js").ITabsState>;
281
287
  readonly addSection: (state: import("immer").WritableDraft<import("./tabsState.js").ITabsState>, action: {
282
288
  payload: import("../_infra/undoEnhancer.js").IUndoPayload<import("../../commands/base.js").IDashboardCommand> & {
283
289
  section: import("../../types/layoutTypes.js").ExtendedDashboardLayoutSection;
@@ -129,6 +129,9 @@ type ReplaceWidgetDateDataset = {
129
129
  type RemoveIgnoredDateFilter = {
130
130
  dateDataSets: ObjRef[];
131
131
  };
132
+ type RemoveIgnoredMeasureValueFilter = {
133
+ measureRefs: ObjRef[];
134
+ };
132
135
  type ReplaceKpiWidgetMeasure = {
133
136
  ref: ObjRef;
134
137
  measureRef: ObjRef;
@@ -177,6 +180,7 @@ export declare const layoutReducers: {
177
180
  updateWidgetIdentitiesForTab: LayoutReducer<UpdateWidgetIdentitiesForTabPayload>;
178
181
  removeIgnoredAttributeFilter: LayoutReducer<RemoveIgnoredAttributeFilter>;
179
182
  removeIgnoredDateFilter: LayoutReducer<RemoveIgnoredDateFilter>;
183
+ removeIgnoredMeasureValueFilter: LayoutReducer<RemoveIgnoredMeasureValueFilter>;
180
184
  addSection: AdaptedLayoutReducer<import("../../_infra/undoEnhancer.js").IUndoPayload<import("../../../commands/base.js").IDashboardCommand> & AddSectionActionPayload>;
181
185
  removeSection: AdaptedLayoutReducer<import("../../_infra/undoEnhancer.js").IUndoPayload<import("../../../commands/base.js").IDashboardCommand> & RemoveSectionActionPayload>;
182
186
  moveSection: AdaptedLayoutReducer<import("../../_infra/undoEnhancer.js").IUndoPayload<import("../../../commands/base.js").IDashboardCommand> & MoveSectionActionPayload>;
@@ -523,6 +523,30 @@ const removeIgnoredDateFilter = (state, action) => {
523
523
  invariant(layoutState?.layout);
524
524
  removeIgnoredDateFilterFromLayout(layoutState.layout, action.payload.dateDataSets);
525
525
  };
526
+ const removeIgnoredMeasureValueFilterFromLayout = (layout, measureRefs) => {
527
+ layout.sections.forEach((section) => {
528
+ section.items.forEach((item) => {
529
+ const widget = item.widget;
530
+ if (isInsightWidget(widget) || isKpiWidget(widget)) {
531
+ widget.ignoreDashboardFilters = widget.ignoreDashboardFilters.filter((filter) => {
532
+ if (isDashboardAttributeFilterReference(filter) ||
533
+ isDashboardDateFilterReference(filter)) {
534
+ return true;
535
+ }
536
+ return (measureRefs.find((removed) => areObjRefsEqual(removed, filter.measure)) === undefined);
537
+ });
538
+ }
539
+ else if (isDashboardLayout(widget)) {
540
+ removeIgnoredMeasureValueFilterFromLayout(widget, measureRefs);
541
+ }
542
+ });
543
+ });
544
+ };
545
+ const removeIgnoredMeasureValueFilter = (state, action) => {
546
+ const layoutState = getActiveTabLayout(state);
547
+ invariant(layoutState?.layout);
548
+ removeIgnoredMeasureValueFilterFromLayout(layoutState.layout, action.payload.measureRefs);
549
+ };
526
550
  const replaceKpiWidgetMeasureCore = (state, action) => {
527
551
  invariant(state.layout);
528
552
  const { ref, measureRef } = action.payload;
@@ -628,6 +652,7 @@ export const layoutReducers = {
628
652
  updateWidgetIdentitiesForTab,
629
653
  removeIgnoredAttributeFilter,
630
654
  removeIgnoredDateFilter,
655
+ removeIgnoredMeasureValueFilter,
631
656
  addSection,
632
657
  removeSection,
633
658
  moveSection,
@@ -34,6 +34,15 @@ export declare const selectDashboardParameterEntryByRef: (ref: ObjRef) => Dashbo
34
34
  * @alpha
35
35
  */
36
36
  export declare const selectParameterRuntimeOverrideByRef: (ref: ObjRef) => DashboardSelector<number | undefined>;
37
+ /**
38
+ * Reset value for a parameter chip's dropdown, bound to the kit dropdown's `resetValue` prop.
39
+ *
40
+ * Returns `undefined` (Reset hidden) in edit mode when `parameter.value` is unset or already
41
+ * equals the workspace default — both would be unpin no-ops on next save.
42
+ *
43
+ * @alpha
44
+ */
45
+ export declare const selectParameterResetValueByRef: (ref: ObjRef) => DashboardSelector<number | undefined>;
37
46
  /**
38
47
  * Computes the dashboard parameters keyed by tab `localIdentifier` in the shape that would be
39
48
  * persisted on save right now.
@@ -60,12 +69,16 @@ export declare const selectIsParametersChanged: DashboardSelector<boolean>;
60
69
  * Returns the parameter values to inject into the widget's `IExecutionConfig.parameterValues`.
61
70
  *
62
71
  * @remarks
63
- * The widget's owning tab is resolved from layout, then the result is the intersection of that
64
- * tab's parameter entries and the parameters referenced by the widget's insight (per
65
- * `insightParameters`). Dashboard parameters not referenced by the widget's insight are excluded
66
- * so that adding/removing unrelated parameters does not invalidate the widget's `defFingerprint`.
67
- * Returns an empty array when `enableParameters` is off so persisted parameter values cannot
68
- * silently affect execution while the UI is hidden.
72
+ * For each parameter referenced by the widget's metrics (via the dashboard-wide MAQL
73
+ * metric parameter dependency map), the value is resolved in this order:
74
+ *
75
+ * 1. dashboard chip on the widget's tab with `runtimeOverride !== undefined`,
76
+ * 2. else `insight.parameters` entry for that ref (AD-authored per-insight override),
77
+ * 3. else nothing (backend uses workspace default).
78
+ *
79
+ * Parameters not referenced by the widget's metrics are excluded so that adding/removing
80
+ * unrelated parameters does not invalidate the widget's `defFingerprint`. Returns `[]` when
81
+ * `enableParameters` is off or while the dependency map has not been loaded.
69
82
  *
70
83
  * @alpha
71
84
  */
@@ -1,15 +1,17 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { createSelector } from "@reduxjs/toolkit";
3
3
  import { isEqual } from "lodash-es";
4
- import { areObjRefsEqual, insightParameters, isDashboardLayout, isInsightWidget, isNumberParameterDefinition, isVisualizationSwitcherWidget, objRefToString, } from "@gooddata/sdk-model";
4
+ import { areObjRefsEqual, insightMeasures, insightParameters, isDashboardLayout, isInsightWidget, isMeasureDefinition, isNumberParameterDefinition, isVisualizationSwitcherWidget, objRefToString, } from "@gooddata/sdk-model";
5
5
  import { createMemoizedSelector } from "../../_infra/selectors.js";
6
- import { selectCatalogParameters, selectCatalogParametersIsLoaded } from "../../catalog/catalogSelectors.js";
6
+ import { selectCatalogMeasureParameters, selectCatalogMeasureParametersStatus, selectCatalogParameterByRef, selectCatalogParameters, selectCatalogParametersIsLoaded, } from "../../catalog/catalogSelectors.js";
7
7
  import { selectEnableParameters } from "../../config/configSelectors.js";
8
8
  import { selectInsightsMap } from "../../insights/insightsSelectors.js";
9
+ import { selectIsInEditMode } from "../../renderMode/renderModeSelectors.js";
9
10
  import { selectActiveTab, selectTabs } from "../tabsSelectors.js";
10
11
  import { DEFAULT_TAB_ID } from "../tabsState.js";
11
12
  import { parametersInitialState, pickTabParametersSource, } from "./parametersState.js";
12
13
  const EMPTY_PARAMETERS = [];
14
+ const EMPTY_PARAMETER_VALUES = [];
13
15
  const EMPTY_TABS = [];
14
16
  const selectParametersState = createSelector(selectActiveTab, (activeTab) => activeTab?.parameters ?? parametersInitialState);
15
17
  const selectPersistedParametersFromMeta = (state) => state.meta?.persistedDashboard?.parameters ?? EMPTY_PARAMETERS;
@@ -47,6 +49,31 @@ export const selectDashboardParameterEntryByRef = createMemoizedSelector((ref) =
47
49
  * @alpha
48
50
  */
49
51
  export const selectParameterRuntimeOverrideByRef = createMemoizedSelector((ref) => createSelector(selectDashboardParameterEntryByRef(ref), (entry) => entry?.runtimeOverride));
52
+ /**
53
+ * Reset value for a parameter chip's dropdown, bound to the kit dropdown's `resetValue` prop.
54
+ *
55
+ * Returns `undefined` (Reset hidden) in edit mode when `parameter.value` is unset or already
56
+ * equals the workspace default — both would be unpin no-ops on next save.
57
+ *
58
+ * @alpha
59
+ */
60
+ export const selectParameterResetValueByRef = createMemoizedSelector((ref) => createSelector(selectDashboardParameterEntryByRef(ref), selectCatalogParameterByRef(ref), selectIsInEditMode, (entry, workspaceParameter, isInEditMode) => {
61
+ if (!entry) {
62
+ return undefined;
63
+ }
64
+ if (!workspaceParameter || !isNumberParameterDefinition(workspaceParameter.definition)) {
65
+ return undefined;
66
+ }
67
+ const workspaceDefault = workspaceParameter.definition.defaultValue;
68
+ const dashboardOverride = entry.parameter.value;
69
+ if (isInEditMode) {
70
+ if (dashboardOverride === undefined || dashboardOverride === workspaceDefault) {
71
+ return undefined;
72
+ }
73
+ return workspaceDefault;
74
+ }
75
+ return dashboardOverride ?? workspaceDefault;
76
+ }));
50
77
  /**
51
78
  * Computes the dashboard parameters keyed by tab `localIdentifier` in the shape that would be
52
79
  * persisted on save right now.
@@ -124,35 +151,61 @@ export const selectIsParametersChanged = createSelector(selectSmartPersistedTabs
124
151
  * Returns the parameter values to inject into the widget's `IExecutionConfig.parameterValues`.
125
152
  *
126
153
  * @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.
154
+ * For each parameter referenced by the widget's metrics (via the dashboard-wide MAQL
155
+ * metric parameter dependency map), the value is resolved in this order:
156
+ *
157
+ * 1. dashboard chip on the widget's tab with `runtimeOverride !== undefined`,
158
+ * 2. else `insight.parameters` entry for that ref (AD-authored per-insight override),
159
+ * 3. else nothing (backend uses workspace default).
160
+ *
161
+ * Parameters not referenced by the widget's metrics are excluded so that adding/removing
162
+ * unrelated parameters does not invalidate the widget's `defFingerprint`. Returns `[]` when
163
+ * `enableParameters` is off or while the dependency map has not been loaded.
133
164
  *
134
165
  * @alpha
135
166
  */
136
- export const selectEffectiveParameterValuesForWidget = createMemoizedSelector((ref) => createSelector(selectParameterExecutionContextByWidgetRef(ref), selectInsightsMap, selectEnableParameters, (context, insights, isEnabled) => {
137
- if (!isEnabled || !context) {
138
- return [];
167
+ export const selectEffectiveParameterValuesForWidget = createMemoizedSelector((ref) => createSelector(selectParameterExecutionContextByWidgetRef(ref), selectInsightsMap, selectEnableParameters, selectCatalogMeasureParameters, selectCatalogMeasureParametersStatus, (context, insights, isEnabled, measureParameters, measureParametersStatus) => {
168
+ if (!isEnabled || !context || measureParametersStatus !== "loaded") {
169
+ return EMPTY_PARAMETER_VALUES;
139
170
  }
140
171
  const insight = insights.get(context.widget.insight);
141
172
  if (!insight) {
142
- return [];
173
+ return EMPTY_PARAMETER_VALUES;
174
+ }
175
+ const referencedRefs = new Set();
176
+ for (const measure of insightMeasures(insight)) {
177
+ const def = measure.measure.definition;
178
+ if (!isMeasureDefinition(def)) {
179
+ continue;
180
+ }
181
+ const parameterRefs = measureParameters[objRefToString(def.measureDefinition.item)] ?? [];
182
+ for (const parameterRef of parameterRefs) {
183
+ referencedRefs.add(objRefToString(parameterRef));
184
+ }
143
185
  }
144
186
  const entries = context.tab.parameters?.parameters ?? parametersInitialState.parameters;
145
- const referencedRefs = new Set(insightParameters(insight).map((parameter) => objRefToString(parameter.ref)));
146
187
  const result = [];
188
+ const seen = new Set();
147
189
  for (const entry of entries) {
148
190
  if (entry.runtimeOverride === undefined) {
149
191
  continue;
150
192
  }
151
- if (referencedRefs.has(objRefToString(entry.parameter.ref))) {
152
- result.push({ ref: entry.parameter.ref, value: entry.runtimeOverride });
193
+ const refKey = objRefToString(entry.parameter.ref);
194
+ if (!referencedRefs.has(refKey)) {
195
+ continue;
153
196
  }
197
+ result.push({ ref: entry.parameter.ref, value: entry.runtimeOverride });
198
+ seen.add(refKey);
154
199
  }
155
- return result;
200
+ for (const insightParameter of insightParameters(insight)) {
201
+ const refKey = objRefToString(insightParameter.ref);
202
+ if (!referencedRefs.has(refKey) || seen.has(refKey)) {
203
+ continue;
204
+ }
205
+ result.push(insightParameter);
206
+ seen.add(refKey);
207
+ }
208
+ return result.length === 0 ? EMPTY_PARAMETER_VALUES : result;
156
209
  }));
157
210
  const selectParameterExecutionContextByWidgetRef = createMemoizedSelector((ref) => createSelector(selectTabs, (tabs) => findParameterExecutionContext(tabs, ref)));
158
211
  function findParameterExecutionContext(tabs, ref) {
@@ -5,7 +5,7 @@ import { useDashboardDispatch, useDashboardSelector } from "../../../model/react
5
5
  import { selectCatalogParameterByRef } from "../../../model/store/catalog/catalogSelectors.js";
6
6
  import { selectIsInEditMode } from "../../../model/store/renderMode/renderModeSelectors.js";
7
7
  import { tabsActions } from "../../../model/store/tabs/index.js";
8
- import { selectParameterRuntimeOverrideByRef } from "../../../model/store/tabs/parameters/parametersSelectors.js";
8
+ import { selectParameterResetValueByRef, selectParameterRuntimeOverrideByRef, } from "../../../model/store/tabs/parameters/parametersSelectors.js";
9
9
  import { DraggableChipSource } from "../../dragAndDrop/DraggableChipSource.js";
10
10
  /**
11
11
  * Renders a chip for a single dashboard parameter.
@@ -16,6 +16,7 @@ export function DashboardParameterFilter({ parameter }) {
16
16
  const dispatch = useDashboardDispatch();
17
17
  const workspaceParameter = useDashboardSelector(selectCatalogParameterByRef(parameter.ref));
18
18
  const runtimeOverride = useDashboardSelector(selectParameterRuntimeOverrideByRef(parameter.ref));
19
+ const resetValue = useDashboardSelector(selectParameterResetValueByRef(parameter.ref));
19
20
  const isInEditMode = useDashboardSelector(selectIsInEditMode);
20
21
  if (parameter.mode === DashboardParameterModeValues.HIDDEN || runtimeOverride === undefined) {
21
22
  return null;
@@ -29,7 +30,7 @@ export function DashboardParameterFilter({ parameter }) {
29
30
  if (parameter.mode === DashboardParameterModeValues.READONLY) {
30
31
  return (_jsx(DraggableChipSource, { dragItem: dragItem, canDrag: isInEditMode, children: _jsx(ParameterControlButton, { name: name, value: runtimeOverride, isActive: false, isDraggable: isInEditMode, "data-testid": `dashboard-parameter-${objRefToString(parameter.ref)}` }) }));
31
32
  }
32
- return (_jsx(DraggableChipSource, { dragItem: dragItem, canDrag: isInEditMode, children: _jsx(Dropdown, { renderButton: ({ isOpen, toggleDropdown }) => (_jsx(ParameterControlButton, { name: name, value: runtimeOverride, isActive: isOpen, isDraggable: isInEditMode, onClick: () => toggleDropdown(), "data-testid": `dashboard-parameter-${objRefToString(parameter.ref)}` })), renderBody: ({ closeDropdown }) => (_jsx(ParameterControlDropdown, { name: name, value: runtimeOverride, constraints: constraints, onApply: (value) => {
33
+ return (_jsx(DraggableChipSource, { dragItem: dragItem, canDrag: isInEditMode, children: _jsx(Dropdown, { renderButton: ({ isOpen, toggleDropdown }) => (_jsx(ParameterControlButton, { name: name, value: runtimeOverride, isActive: isOpen, isDraggable: isInEditMode, onClick: () => toggleDropdown(), "data-testid": `dashboard-parameter-${objRefToString(parameter.ref)}` })), renderBody: ({ closeDropdown }) => (_jsx(ParameterControlDropdown, { name: name, value: runtimeOverride, resetValue: resetValue, constraints: constraints, onApply: (value) => {
33
34
  dispatch(tabsActions.setParameterRuntimeValue({ ref: parameter.ref, value }));
34
35
  closeDropdown();
35
36
  }, onCancel: closeDropdown })) }) }));
@@ -1931,6 +1931,14 @@ export declare const en_US: {
1931
1931
  text: string;
1932
1932
  crowdinContext: string;
1933
1933
  };
1934
+ "configurationPanel.drillConfig.filterSelection.drillToDashboardDuplicateMetricFilter.tooltip": {
1935
+ text: string;
1936
+ crowdinContext: string;
1937
+ };
1938
+ "configurationPanel.drillConfig.filterSelection.drillToInsightMeasureValueFilter.tooltip": {
1939
+ text: string;
1940
+ crowdinContext: string;
1941
+ };
1934
1942
  "configurationPanel.drillConfig.filterSelection.drillToDashboardRankingFilter.tooltip": {
1935
1943
  text: string;
1936
1944
  crowdinContext: string;
@@ -1933,6 +1933,14 @@ export const en_US = {
1933
1933
  "text": "Metric filters cannot be passed to the target dashboard.",
1934
1934
  "crowdinContext": "Tooltip shown for disabled metric value filters in drill-to-dashboard configuration because metric filters are not supported on the target dashboard"
1935
1935
  },
1936
+ "configurationPanel.drillConfig.filterSelection.drillToDashboardDuplicateMetricFilter.tooltip": {
1937
+ "text": "Another filter for this metric is already being passed.",
1938
+ "crowdinContext": "Tooltip shown for a disabled dashboard or visualization metric filter in drill-to-dashboard configuration when another filter for the same metric is already selected"
1939
+ },
1940
+ "configurationPanel.drillConfig.filterSelection.drillToInsightMeasureValueFilter.tooltip": {
1941
+ "text": "Filter is not applicable to the drill destination.",
1942
+ "crowdinContext": "Tooltip shown for disabled measure value filters in drill-to-insight configuration because measure value filters are not supported on the target dashboard"
1943
+ },
1936
1944
  "configurationPanel.drillConfig.filterSelection.drillToDashboardRankingFilter.tooltip": {
1937
1945
  "text": "Top/bottom filters can’t be passed to the target dashboard.",
1938
1946
  "crowdinContext": "Tooltip shown for disabled ranking filters in drill-to-dashboard configuration because top and bottom filters are not transferred to the target dashboard"
@@ -2,11 +2,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2022-2026 GoodData Corporation
3
3
  import { useMemo } from "react";
4
4
  import { invariant } from "ts-invariant";
5
- import { dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemTitle, isAttributeMetadataObject, isDashboardAttributeFilterItem, isDashboardDateFilterWithDimension, isDashboardMeasureValueFilter, objRefToString, } from "@gooddata/sdk-model";
5
+ import { dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemTitle, isAttributeMetadataObject, isDashboardAttributeFilterItem, isDashboardDateFilterWithDimension, isDashboardMeasureValueFilter, isInsightWidget, objRefToString, } from "@gooddata/sdk-model";
6
6
  import { useAttributeFilterDisplayFormFromMap } from "../../../../_staging/sharedHooks/useAttributeFilterDisplayFormFromMap.js";
7
7
  import { useAttributes } from "../../../../_staging/sharedHooks/useAttributes.js";
8
8
  import { useDashboardSelector } from "../../../../model/react/DashboardStoreProvider.js";
9
9
  import { selectAllCatalogAttributesMap, selectAllCatalogDateDatasetsMap, selectAllCatalogMeasuresMap, } from "../../../../model/store/catalog/catalogSelectors.js";
10
+ import { selectInsightByRef } from "../../../../model/store/insights/insightsSelectors.js";
10
11
  import { selectAttributeFilterConfigsDisplayAsLabelMap } from "../../../../model/store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
11
12
  import { selectFilterContextFilters } from "../../../../model/store/tabs/filterContext/filterContextSelectors.js";
12
13
  import { AttributeFilterConfigurationItem } from "./AttributeFilterConfigurationItem.js";
@@ -34,7 +35,8 @@ export function FilterConfiguration({ widget }) {
34
35
  }, [attributeFilters]);
35
36
  const { attributes, attributesLoading } = useAttributes(displayForms);
36
37
  const measureValueFilters = useMemo(() => draggableFilters.filter(isDashboardMeasureValueFilter), [draggableFilters]);
37
- const { isCompatible } = useMeasureValueFilterCompatibility(widget, measureValueFilters);
38
+ const insight = useDashboardSelector(selectInsightByRef(isInsightWidget(widget) ? widget.insight : undefined));
39
+ const { isCompatible } = useMeasureValueFilterCompatibility(insight, measureValueFilters);
38
40
  if (attributesLoading) {
39
41
  return _jsx("span", { className: "gd-spinner small s-attribute-filter-configuration-loading" });
40
42
  }
@@ -1,5 +1,9 @@
1
- import { type IDashboardMeasureValueFilter, type IWidget, type ObjRef } from "@gooddata/sdk-model";
2
- export declare function useMeasureValueFilterCompatibility(widget: IWidget, filters: IDashboardMeasureValueFilter[]): {
1
+ import { type IDashboardMeasureValueFilter, type IInsight, type IInsightDefinition, type ObjRef } from "@gooddata/sdk-model";
2
+ import { useBackendStrict } from "@gooddata/sdk-ui";
3
+ export declare function useMeasureValueFilterCompatibility(insight: IInsight | undefined, filters: IDashboardMeasureValueFilter[]): {
3
4
  status: "error" | "loading" | "pending" | "success";
4
5
  isCompatible: (measureRef: ObjRef) => boolean;
6
+ compatibleMeasureRefs: ObjRef[] | undefined;
7
+ compatibleMeasureValueFilters: IDashboardMeasureValueFilter[];
5
8
  };
9
+ export declare function loadCompatibleMeasureRefs(backend: ReturnType<typeof useBackendStrict>, workspace: string, insight: IInsightDefinition, measureRefs: ObjRef[]): Promise<ObjRef[]>;
@@ -1,40 +1,78 @@
1
1
  // (C) 2026 GoodData Corporation
2
2
  import { useMemo } from "react";
3
- import { areObjRefsEqual, insightMeasures, isInsightWidget, measureItem, objRefToString, } from "@gooddata/sdk-model";
3
+ import { areObjRefsEqual, insightBuckets, insightFilters, insightMeasures, isObjRef, measureItem, objRefToString, } from "@gooddata/sdk-model";
4
4
  import { useBackendStrict, useCancelablePromise, useWorkspaceStrict } from "@gooddata/sdk-ui";
5
- import { safeSerializeObjRef } from "../../../../_staging/metadata/safeSerializeObjRef.js";
6
- import { useDashboardSelector } from "../../../../model/react/DashboardStoreProvider.js";
7
- import { selectInsightByRef } from "../../../../model/store/insights/insightsSelectors.js";
8
- export function useMeasureValueFilterCompatibility(widget, filters) {
5
+ const COMPATIBLE_MEASURE_REFS_CACHE_SIZE = 50;
6
+ const compatibleMeasureRefsCache = new Map();
7
+ const compatibleMeasureRefsPromises = new Map();
8
+ export function useMeasureValueFilterCompatibility(insight, filters) {
9
9
  const backend = useBackendStrict();
10
10
  const workspace = useWorkspaceStrict();
11
- const insight = useDashboardSelector(selectInsightByRef(isInsightWidget(widget) ? widget.insight : undefined));
12
11
  const measureRefs = useMemo(() => filters.map((filter) => filter.dashboardMeasureValueFilter.measure), [filters]);
13
12
  const measureRefsDigest = useMemo(() => measureRefs.map(objRefToString).join("|"), [measureRefs]);
14
- const { result: compatibleMeasureRefs, status } = useCancelablePromise({
15
- promise: async () => {
16
- if (!measureRefs.length) {
17
- return [];
18
- }
19
- if (!isInsightWidget(widget) || !insight) {
20
- return measureRefs;
21
- }
22
- return loadCompatibleMeasureRefs(backend, workspace, insight, measureRefs);
23
- },
13
+ const insightIdentifier = getInsightIdentifier(insight);
14
+ const cacheKey = useMemo(() => insight && measureRefs.length
15
+ ? getCompatibleMeasureRefsCacheKey(workspace, insight, measureRefsDigest)
16
+ : undefined, [insight, measureRefs.length, measureRefsDigest, workspace]);
17
+ const cachedCompatibleMeasureRefs = useMemo(() => {
18
+ if (!measureRefs.length) {
19
+ return [];
20
+ }
21
+ if (!insight) {
22
+ return measureRefs;
23
+ }
24
+ return cacheKey ? getCachedValue(compatibleMeasureRefsCache, cacheKey) : undefined;
25
+ }, [cacheKey, insight, measureRefs]);
26
+ const insightToLoadCompatibleMeasureRefsFor = cachedCompatibleMeasureRefs ? undefined : insight;
27
+ const { result: loadedCompatibleMeasureRefs, status } = useCancelablePromise({
28
+ promise: insightToLoadCompatibleMeasureRefsFor
29
+ ? async () => loadCompatibleMeasureRefs(backend, workspace, insightToLoadCompatibleMeasureRefsFor, measureRefs)
30
+ : undefined,
24
31
  }, [
25
32
  backend,
26
33
  workspace,
27
34
  measureRefsDigest,
28
- safeSerializeObjRef(isInsightWidget(widget) ? widget.insight : undefined),
29
- insight,
35
+ insightIdentifier,
36
+ cachedCompatibleMeasureRefs,
37
+ insightToLoadCompatibleMeasureRefsFor,
30
38
  ]);
39
+ const compatibleMeasureRefs = cachedCompatibleMeasureRefs ?? loadedCompatibleMeasureRefs;
40
+ const compatibleMeasureValueFilters = useMemo(() => compatibleMeasureRefs
41
+ ? filters.filter((filter) => isMeasureRefCompatible(filter.dashboardMeasureValueFilter.measure, compatibleMeasureRefs))
42
+ : filters, [compatibleMeasureRefs, filters]);
31
43
  return {
32
- status,
33
- isCompatible: (measureRef) => !compatibleMeasureRefs ||
34
- compatibleMeasureRefs.some((compatibleMeasureRef) => areObjRefsEqual(compatibleMeasureRef, measureRef)),
44
+ status: cachedCompatibleMeasureRefs ? "success" : status,
45
+ isCompatible: (measureRef) => isMeasureRefCompatible(measureRef, compatibleMeasureRefs),
46
+ compatibleMeasureRefs,
47
+ compatibleMeasureValueFilters,
35
48
  };
36
49
  }
37
- async function loadCompatibleMeasureRefs(backend, workspace, insight, measureRefs) {
50
+ function isMeasureRefCompatible(measureRef, compatibleMeasureRefs) {
51
+ return (!compatibleMeasureRefs ||
52
+ compatibleMeasureRefs.some((compatibleMeasureRef) => areObjRefsEqual(compatibleMeasureRef, measureRef)));
53
+ }
54
+ export async function loadCompatibleMeasureRefs(backend, workspace, insight, measureRefs) {
55
+ const cacheKey = getCompatibleMeasureRefsCacheKey(workspace, insight, measureRefs.map(objRefToString).join("|"));
56
+ const cachedCompatibleMeasureRefs = getCachedValue(compatibleMeasureRefsCache, cacheKey);
57
+ if (cachedCompatibleMeasureRefs) {
58
+ return cachedCompatibleMeasureRefs;
59
+ }
60
+ const pendingCompatibleMeasureRefs = getCachedValue(compatibleMeasureRefsPromises, cacheKey);
61
+ if (pendingCompatibleMeasureRefs) {
62
+ return pendingCompatibleMeasureRefs;
63
+ }
64
+ const compatibleMeasureRefsPromise = loadCompatibleMeasureRefsFromBackend(backend, workspace, insight, measureRefs)
65
+ .then((compatibleMeasureRefs) => {
66
+ setCachedValue(compatibleMeasureRefsCache, cacheKey, compatibleMeasureRefs);
67
+ return compatibleMeasureRefs;
68
+ })
69
+ .finally(() => {
70
+ compatibleMeasureRefsPromises.delete(cacheKey);
71
+ });
72
+ setCachedValue(compatibleMeasureRefsPromises, cacheKey, compatibleMeasureRefsPromise);
73
+ return compatibleMeasureRefsPromise;
74
+ }
75
+ async function loadCompatibleMeasureRefsFromBackend(backend, workspace, insight, measureRefs) {
38
76
  const catalog = await backend
39
77
  .workspace(workspace)
40
78
  .catalog()
@@ -49,3 +87,38 @@ async function loadCompatibleMeasureRefs(backend, workspace, insight, measureRef
49
87
  const validMeasureRefs = [...availableMeasureRefs, ...insightMeasureRefs];
50
88
  return measureRefs.filter((measureRef) => validMeasureRefs.some((validMeasureRef) => areObjRefsEqual(validMeasureRef, measureRef)));
51
89
  }
90
+ function getCompatibleMeasureRefsCacheKey(workspace, insight, measureRefsDigest) {
91
+ return JSON.stringify([
92
+ workspace,
93
+ getInsightIdentifier(insight),
94
+ insightBuckets(insight),
95
+ insightFilters(insight),
96
+ measureRefsDigest,
97
+ ]);
98
+ }
99
+ function getInsightIdentifier(insight) {
100
+ if (!insight) {
101
+ return undefined;
102
+ }
103
+ if ("identifier" in insight.insight && typeof insight.insight.identifier === "string") {
104
+ return insight.insight.identifier;
105
+ }
106
+ if ("ref" in insight.insight && isObjRef(insight.insight.ref)) {
107
+ return objRefToString(insight.insight.ref);
108
+ }
109
+ return undefined;
110
+ }
111
+ function getCachedValue(cache, key) {
112
+ return cache.get(key);
113
+ }
114
+ function setCachedValue(cache, key, value) {
115
+ cache.delete(key);
116
+ cache.set(key, value);
117
+ while (cache.size > COMPATIBLE_MEASURE_REFS_CACHE_SIZE) {
118
+ const oldestKey = cache.keys().next().value;
119
+ if (oldestKey === undefined) {
120
+ return;
121
+ }
122
+ cache.delete(oldestKey);
123
+ }
124
+ }
@@ -1,6 +1,6 @@
1
1
  // (C) 2020-2026 GoodData Corporation
2
2
  import { useMemo } from "react";
3
- import { filterLocalIdentifier, isAttributeFilter, isDateFilter, isDrillToInsight, } from "@gooddata/sdk-model";
3
+ import { filterLocalIdentifier, isAttributeFilter, isDateFilter, isDrillToInsight, isMeasureValueFilter, } from "@gooddata/sdk-model";
4
4
  export function useExcludedDrillDefinitionFilters(filtersForInsight, drillStep) {
5
5
  const ignoredDashboardFilterIds = useMemo(() => {
6
6
  if (!drillStep || !isDrillToInsight(drillStep.drillDefinition)) {
@@ -9,7 +9,7 @@ export function useExcludedDrillDefinitionFilters(filtersForInsight, drillStep)
9
9
  return new Set(drillStep.drillDefinition.ignoredDashboardFilters ?? []);
10
10
  }, [drillStep]);
11
11
  return useMemo(() => filtersForInsight?.filter((filter) => {
12
- if (!isAttributeFilter(filter) && !isDateFilter(filter)) {
12
+ if (!isAttributeFilter(filter) && !isDateFilter(filter) && !isMeasureValueFilter(filter)) {
13
13
  return true;
14
14
  }
15
15
  const localIdentifier = filterLocalIdentifier(filter);
@@ -1,11 +1,12 @@
1
1
  import { type ReactNode } from "react";
2
- import { type DashboardTextAttributeFilter, type FilterContextItem, type IDashboardAttributeFilter, type IDashboardAttributeFilterConfig, type ObjRef } from "@gooddata/sdk-model";
2
+ import { type DashboardTextAttributeFilter, type FilterContextItem, type IDashboardAttributeFilter, type IDashboardAttributeFilterConfig, type IDashboardMeasureValueFilter, type ObjRef } from "@gooddata/sdk-model";
3
3
  type TTargetDashboardFiltersCacheStatus = "loading" | "success" | "error";
4
4
  interface ITargetDashboardFiltersCacheEntry {
5
5
  status: TTargetDashboardFiltersCacheStatus;
6
6
  targetDashboardFilters: FilterContextItem[];
7
7
  targetDashboardAttributeFilters: IDashboardAttributeFilter[];
8
8
  targetDashboardTextAttributeFilters: DashboardTextAttributeFilter[];
9
+ targetDashboardMeasureValueFilters: IDashboardMeasureValueFilter[];
9
10
  targetDashboardAttributeFilterConfigs: IDashboardAttributeFilterConfig[];
10
11
  }
11
12
  interface ITargetDashboardFiltersContextValue {