@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.
- package/NOTICE +8 -8
- package/esm/__version.d.ts +1 -1
- package/esm/__version.js +1 -1
- package/esm/_staging/dashboard/dashboardFilterContext.js +18 -1
- package/esm/_staging/sharedHooks/useFiltersNamings.d.ts +1 -1
- package/esm/_staging/sharedHooks/useFiltersNamings.js +32 -5
- package/esm/index.d.ts +4 -5
- package/esm/index.js +2 -3
- package/esm/kdaDialog/dialog/hooks/useChangeAnalysis.js +21 -5
- package/esm/kdaDialog/internalTypes.d.ts +6 -1
- package/esm/kdaDialog/providers/Kda.js +4 -1
- package/esm/kdaDialog/providers/KdaState.js +1 -0
- package/esm/kdaDialog/types.d.ts +13 -2
- package/esm/model/commandHandlers/dashboard/common/parameterHydration.d.ts +11 -2
- package/esm/model/commandHandlers/dashboard/common/parameterHydration.js +21 -0
- package/esm/model/commandHandlers/dashboard/common/stateInitializers.js +44 -79
- package/esm/model/commandHandlers/dashboard/saveAsDashboardHandler.js +10 -5
- package/esm/model/commandHandlers/dashboard/saveDashboardHandler.js +11 -7
- package/esm/model/commandHandlers/drill/keyDriverAnalysisHandler.js +8 -3
- package/esm/model/store/_infra/generators.d.ts +1 -0
- package/esm/model/store/_infra/generators.js +4 -1
- package/esm/model/store/dashboardStore.d.ts +0 -2
- package/esm/model/store/dashboardStore.js +0 -2
- package/esm/model/store/filtering/dashboardFilterSelectors.d.ts +1 -1
- package/esm/model/store/filtering/dashboardFilterSelectors.js +8 -5
- package/esm/model/store/meta/metaSelectors.js +4 -3
- package/esm/model/store/tabs/filterContext/filterContextReducers.js +2 -2
- package/esm/model/store/tabs/index.d.ts +12 -0
- package/esm/model/store/tabs/index.js +2 -0
- package/esm/model/store/{parameters → tabs/parameters}/parametersReducers.d.ts +3 -7
- package/esm/model/store/tabs/parameters/parametersReducers.js +46 -0
- package/esm/model/store/tabs/parameters/parametersSelectors.d.ts +72 -0
- package/esm/model/store/tabs/parameters/parametersSelectors.js +228 -0
- package/esm/model/store/{parameters → tabs/parameters}/parametersState.d.ts +11 -1
- package/esm/model/store/tabs/parameters/parametersState.js +20 -0
- package/esm/model/store/tabs/tabsState.d.ts +2 -0
- package/esm/model/store/types.d.ts +0 -6
- package/esm/model/utils/widgetFilters.d.ts +1 -1
- package/esm/model/utils/widgetFilters.js +1 -1
- package/esm/presentation/automationFilters/components/AutomationFiltersSelect.js +8 -2
- package/esm/presentation/automationFilters/components/AutomationMeasureValueFilter.d.ts +10 -0
- package/esm/presentation/automationFilters/components/AutomationMeasureValueFilter.js +66 -0
- package/esm/presentation/automationFilters/components/AutomationMeasureValueFilterContext.d.ts +25 -0
- package/esm/presentation/automationFilters/components/AutomationMeasureValueFilterContext.js +28 -0
- package/esm/presentation/automationFilters/useAutomationFilters.d.ts +4 -1
- package/esm/presentation/automationFilters/useAutomationFilters.js +35 -4
- package/esm/presentation/automationFilters/utils.d.ts +2 -1
- package/esm/presentation/automationFilters/utils.js +34 -5
- package/esm/presentation/dragAndDrop/draggableParameterFilter/DefaultParameterDraggingComponent.js +1 -1
- package/esm/presentation/dragAndDrop/useFilterDeleteDrop.js +2 -2
- package/esm/presentation/filterBar/filterBar/DefaultFilterBar.js +1 -1
- package/esm/presentation/filterBar/measureValueFilter/DefaultDashboardMeasureValueFilter.js +8 -42
- package/esm/presentation/filterBar/measureValueFilter/useDashboardMeasureValueFilterData.d.ts +86 -0
- package/esm/presentation/filterBar/measureValueFilter/useDashboardMeasureValueFilterData.js +105 -0
- package/esm/presentation/filterBar/parameterFilter/DashboardParameterFilter.js +3 -3
- package/esm/presentation/filterBar/parameterFilter/DashboardParameterPicker.js +3 -3
- package/esm/presentation/widget/insight/ViewModeDashboardInsight/Insight/DashboardInsight.js +1 -1
- package/esm/sdk-ui-dashboard.d.ts +44 -32
- package/package.json +21 -21
- package/esm/model/store/parameters/index.d.ts +0 -12
- package/esm/model/store/parameters/index.js +0 -14
- package/esm/model/store/parameters/parametersReducers.js +0 -36
- package/esm/model/store/parameters/parametersSelectors.d.ts +0 -67
- package/esm/model/store/parameters/parametersSelectors.js +0 -122
- 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
|
|
10
|
+
export declare function getAttributeFilters(filters: FilterContextItem[]): DashboardAttributeFilterItem[];
|
|
@@ -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
|
+
}
|
package/esm/presentation/automationFilters/components/AutomationMeasureValueFilterContext.d.ts
ADDED
|
@@ -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;
|