@gooddata/sdk-ui-dashboard 11.42.0-alpha.2 → 11.42.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 (46) hide show
  1. package/NOTICE +6 -6
  2. package/esm/__version.d.ts +1 -1
  3. package/esm/__version.js +1 -1
  4. package/esm/_staging/automation/index.d.ts +14 -1
  5. package/esm/_staging/automation/index.d.ts.map +1 -1
  6. package/esm/_staging/automation/index.js +50 -0
  7. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/DefaultScheduledEmailDialog.d.ts.map +1 -1
  8. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/DefaultScheduledEmailDialog.js +16 -2
  9. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useEditScheduledEmail.d.ts +2 -1
  10. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useEditScheduledEmail.d.ts.map +1 -1
  11. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useEditScheduledEmail.js +32 -12
  12. package/esm/presentation/automations/shared/automationFilters/automationParameters.d.ts +39 -1
  13. package/esm/presentation/automations/shared/automationFilters/automationParameters.d.ts.map +1 -1
  14. package/esm/presentation/automations/shared/automationFilters/automationParameters.js +66 -1
  15. package/esm/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.d.ts +27 -3
  16. package/esm/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.d.ts.map +1 -1
  17. package/esm/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.js +44 -12
  18. package/esm/presentation/automations/shared/automationFilters/components/AutomationParameter.d.ts +2 -1
  19. package/esm/presentation/automations/shared/automationFilters/components/AutomationParameter.d.ts.map +1 -1
  20. package/esm/presentation/automations/shared/automationFilters/components/AutomationParameter.js +7 -1
  21. package/esm/presentation/automations/shared/automationFilters/hooks/useValidateExistingAutomationFilters.d.ts +23 -1
  22. package/esm/presentation/automations/shared/automationFilters/hooks/useValidateExistingAutomationFilters.d.ts.map +1 -1
  23. package/esm/presentation/automations/shared/automationFilters/hooks/useValidateExistingAutomationFilters.js +93 -2
  24. package/esm/presentation/automations/shared/automationFilters/useAutomationExportParameters.d.ts +75 -0
  25. package/esm/presentation/automations/shared/automationFilters/useAutomationExportParameters.d.ts.map +1 -0
  26. package/esm/presentation/automations/shared/automationFilters/useAutomationExportParameters.js +143 -0
  27. package/esm/presentation/automations/shared/automationFilters/useAutomationFilters.d.ts +1 -0
  28. package/esm/presentation/automations/shared/automationFilters/useAutomationFilters.d.ts.map +1 -1
  29. package/esm/presentation/automations/shared/automationFilters/useAutomationFilters.js +1 -0
  30. package/esm/presentation/automations/shared/automationFilters/useAutomationFiltersSelect.d.ts.map +1 -1
  31. package/esm/presentation/automations/shared/automationFilters/useAutomationFiltersSelect.js +15 -2
  32. package/esm/presentation/automations/shared/automationFilters/useParameterAnnouncements.d.ts +15 -0
  33. package/esm/presentation/automations/shared/automationFilters/useParameterAnnouncements.d.ts.map +1 -0
  34. package/esm/presentation/automations/shared/automationFilters/useParameterAnnouncements.js +28 -0
  35. package/esm/presentation/localization/bundles/en-US.localization-bundle.d.ts +16 -0
  36. package/esm/presentation/localization/bundles/en-US.localization-bundle.d.ts.map +1 -1
  37. package/esm/presentation/localization/bundles/en-US.localization-bundle.js +16 -0
  38. package/esm/presentation/widget/widget/InsightWidget/EditableDashboardInsightWidget.d.ts.map +1 -1
  39. package/esm/presentation/widget/widget/InsightWidget/EditableDashboardInsightWidget.js +10 -1
  40. package/esm/presentation/widget/widget/warningPartialResult/InsightWidgetWarningPartialResult.d.ts +5 -4
  41. package/esm/presentation/widget/widget/warningPartialResult/InsightWidgetWarningPartialResult.d.ts.map +1 -1
  42. package/esm/presentation/widget/widget/warningPartialResult/InsightWidgetWarningPartialResult.js +3 -3
  43. package/package.json +20 -20
  44. package/styles/css/main.css +1 -1
  45. package/styles/css/warningPartialResult.css +1 -1
  46. package/styles/scss/warningPartialResult.scss +1 -1
@@ -7,18 +7,21 @@ import { Bubble, BubbleHoverTrigger, Typography, UiButton, UiIconButton, UiToolt
7
7
  import { AUTOMATION_FILTERS_DIALOG_ID, AUTOMATION_FILTERS_DIALOG_TITLE_ID, AUTOMATION_FILTERS_GROUP_LABEL_ID, } from "../../../../constants/automations.js";
8
8
  import { AttributesDropdown, } from "../../../../filterBar/attributeFilter/addAttributeFilter/AttributesDropdown.js";
9
9
  import { useAutomationFilters, useAutomationFiltersByTab } from "../useAutomationFilters.js";
10
+ import { useParameterAnnouncements } from "../useParameterAnnouncements.js";
10
11
  import { AutomationAttributeFilter } from "./AutomationAttributeFilter.js";
11
12
  import { AutomationDateFilter } from "./AutomationDateFilter.js";
12
13
  import { AutomationMeasureValueFilter } from "./AutomationMeasureValueFilter.js";
13
14
  import { AutomationParameter } from "./AutomationParameter.js";
14
15
  const COLLAPSED_FILTERS_COUNT = 2;
15
- function AutomationCheckboxOrNote({ isDashboardAutomation, storeFilters, handleStoreFiltersChange, handleKeyDown, automationFilterSelectTooltipId, }) {
16
+ function AutomationCheckboxOrNote({ isDashboardAutomation, storeFilters, handleStoreFiltersChange, handleKeyDown, automationFilterSelectTooltipId, parametersEnabled, }) {
16
17
  const intl = useIntl();
18
+ // Keep both message ids as static literals so the i18n extractor sees them.
19
+ const useFiltersTooltip = parametersEnabled ? (_jsx(FormattedMessage, { id: "dialogs.automation.filters.useFiltersMessage.parameters.tooltip" })) : (_jsx(FormattedMessage, { id: "dialogs.automation.filters.useFiltersMessage.tooltip" }));
17
20
  return isDashboardAutomation ? (_jsxs("label", { className: "input-checkbox-label gd-automation-filters__use-filters-checkbox s-automation-filters-use-filters-checkbox", children: [
18
21
  _jsx("input", { type: "checkbox", className: "input-checkbox s-checkbox", checked: storeFilters, onChange: (e) => handleStoreFiltersChange(e.target.checked), onKeyDown: handleKeyDown, "aria-label": intl.formatMessage({
19
22
  id: "dialogs.automation.filters.useFiltersMessage",
20
23
  }) }), _jsxs("span", { className: "input-label-text gd-automation-filters__use-filters-message", children: [
21
- _jsx("div", { id: automationFilterSelectTooltipId, className: "sr-only", children: _jsx(FormattedMessage, { id: "dialogs.automation.filters.useFiltersMessage.tooltip" }) }), _jsx(FormattedMessage, { id: "dialogs.automation.filters.useFiltersMessage" }), _jsx(UiTooltip, { arrowPlacement: "left", triggerBy: ["hover", "focus"], optimalPlacement: true, width: 300, content: _jsx(FormattedMessage, { id: "dialogs.automation.filters.useFiltersMessage.tooltip" }), anchor: _jsx(UiIconButton, { icon: "question", variant: "tertiary", size: "xsmall", accessibilityConfig: {
24
+ _jsx("div", { id: automationFilterSelectTooltipId, className: "sr-only", children: useFiltersTooltip }), _jsx(FormattedMessage, { id: "dialogs.automation.filters.useFiltersMessage" }), _jsx(UiTooltip, { arrowPlacement: "left", triggerBy: ["hover", "focus"], optimalPlacement: true, width: 300, content: useFiltersTooltip, anchor: _jsx(UiIconButton, { icon: "question", variant: "tertiary", size: "xsmall", accessibilityConfig: {
22
25
  ariaLabel: intl.formatMessage({
23
26
  id: "dialogs.automation.filters.schedule.ariaLabel",
24
27
  }),
@@ -30,7 +33,7 @@ function AutomationCheckboxOrNote({ isDashboardAutomation, storeFilters, handleS
30
33
  function Divider() {
31
34
  return _jsx("div", { className: "gd-automation-filters__divider", style: { height: "20px" } });
32
35
  }
33
- export function AutomationFiltersSelect({ availableFilters = [], selectedFilters = [], onFiltersChange, isDashboardAutomation, storeFilters, onStoreFiltersChange, areFiltersMissing, overlayPositionType, hideTitle = false, showAllFilters = false, disableDateFilters = false, filtersByTab, editedFiltersByTab, onFiltersByTabChange, parameters = [], onParameterChange, onParameterDelete, availableParameters = [], onParameterAdd, }) {
36
+ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters = [], onFiltersChange, isDashboardAutomation, storeFilters, onStoreFiltersChange, areFiltersMissing, overlayPositionType, hideTitle = false, showAllFilters = false, disableDateFilters = false, filtersByTab, editedFiltersByTab, onFiltersByTabChange, parameters = [], availableParameters = [], onParameterAdd, onParameterChange, onParameterDelete, parametersByTab, availableParametersByTab, onParameterAddByTab, onParameterChangeByTab, onParameterDeleteByTab, parametersEnabled, }) {
34
37
  // Determine rendering mode first
35
38
  const shouldRenderByTab = !!filtersByTab && filtersByTab.length > 1;
36
39
  // Use appropriate hook based on rendering mode
@@ -50,6 +53,7 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
50
53
  });
51
54
  // Use data from the appropriate hook based on rendering mode
52
55
  const { commonDateFilterId, lockedFilters, attributeConfigs, dateConfigs, filterAnnouncement, filterGroupRef, makeFilterGroupUnfocusable, setAddFilterButtonRefs, } = shouldRenderByTab && tabFiltersData.processedFiltersByTab ? tabFiltersData : flatFiltersData;
56
+ const { focusAddFilterButton } = flatFiltersData;
53
57
  const filters = shouldRenderByTab ? [] : flatFiltersData.visibleFilters;
54
58
  const visibleParameters = shouldRenderByTab ? [] : parameters;
55
59
  const attributes = shouldRenderByTab ? [] : flatFiltersData.attributes;
@@ -90,6 +94,8 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
90
94
  };
91
95
  const tooltipText = isAddButtonDisabled ? tooltipTextValues.addDisabled : tooltipTextValues.add;
92
96
  const searchAriaLabel = intl.formatMessage({ id: "dialogs.automation.filters.searchAriaLabel" });
97
+ // Screen-reader announcements for parameter chip add/remove/change
98
+ const { parameterAnnouncement, announceParameterAdded, announceParameterChanged, announceParameterRemoved, } = useParameterAnnouncements();
93
99
  const disableFilters = !storeFilters && !!isDashboardAutomation;
94
100
  return (_jsxs("div", { className: "gd-input-component gd-notification-channels-automation-filters s-gd-notifications-channels-dialog-automation-filters", children: [hideTitle ? (_jsx("div", { className: "sr-only", id: AUTOMATION_FILTERS_GROUP_LABEL_ID, children: _jsx(FormattedMessage, { id: "dialogs.schedule.email.filters", values: { count: chipCount } }) })) : (_jsx("div", { className: "gd-label", id: AUTOMATION_FILTERS_GROUP_LABEL_ID, children: isExpandable && !shouldRenderByTab ? (_jsxs(BubbleHoverTrigger, { showDelay: 500, hideDelay: 0, children: [
95
101
  _jsx(UiButton, { label: intl.formatMessage({ id: "dialogs.schedule.email.filters" }, { count: chipCount }), variant: "tertiary", onClick: () => setIsExpanded(!isExpanded), iconAfter: isExpanded ? "navigateUp" : "navigateDown", accessibilityConfig: {
@@ -104,21 +110,38 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
104
110
  iconAriaHidden: true,
105
111
  } }), _jsx(Bubble, { className: "bubble-primary", alignPoints: [{ align: "bc tc" }], children: isExpanded ? (_jsx(FormattedMessage, { id: "dialogs.automation.filters.showLess" })) : (_jsx(FormattedMessage, { id: "dialogs.automation.filters.showAll" })) })
106
112
  ] })) : (_jsx(FormattedMessage, { id: "dialogs.schedule.email.filters", values: { count: chipCount } })) })), _jsxs("div", { className: "gd-automation-filters", children: [showAllFilters ? (_jsxs(_Fragment, { children: [
107
- _jsx(AutomationCheckboxOrNote, { isDashboardAutomation: isDashboardAutomation, storeFilters: storeFilters, handleStoreFiltersChange: handleStoreFiltersChange, handleKeyDown: handleKeyDown, automationFilterSelectTooltipId: automationFilterSelectTooltipId }), _jsx(Divider, {})
113
+ _jsx(AutomationCheckboxOrNote, { isDashboardAutomation: isDashboardAutomation, storeFilters: storeFilters, handleStoreFiltersChange: handleStoreFiltersChange, handleKeyDown: handleKeyDown, automationFilterSelectTooltipId: automationFilterSelectTooltipId, parametersEnabled: parametersEnabled }), _jsx(Divider, {})
108
114
  ] })) : null, _jsxs("div", { className: `gd-automation-filters__wrapper${storeFilters || !isDashboardAutomation ? "" : " gd-automation-filters__wrapper--disabled"}`, children: [disableFilters ? _jsx("div", { className: "gd-automation-filters__overlay" }) : null, shouldRenderByTab && tabFiltersData.processedFiltersByTab ? (
109
115
  // Render filters grouped by tab with dividers between sections
110
116
  _jsx("div", { className: "gd-automation-filters__list gd-automation-filters__list--tabbed", role: "group", "aria-labelledby": AUTOMATION_FILTERS_GROUP_LABEL_ID, ref: filterGroupRef, onBlur: makeFilterGroupUnfocusable, children: tabFiltersData.processedFiltersByTab.map((tab, index) => {
111
117
  // Check if all filters for this tab are already selected
112
118
  const tabEditedFilters = editedFiltersByTab?.[tab.tabId] ?? [];
113
119
  const tabAvailableFilters = filtersByTab?.find((t) => t.tabId === tab.tabId)?.availableFilters ?? [];
114
- const isTabAddButtonDisabled = tabEditedFilters.length >= tabAvailableFilters.length;
120
+ const tabParameters = parametersByTab?.[tab.tabId] ?? [];
121
+ const tabAvailableParameters = availableParametersByTab?.[tab.tabId] ?? [];
122
+ const tabParameterDropdownItems = tabAvailableParameters.map((parameter) => ({
123
+ type: "parameter",
124
+ ref: parameter.ref,
125
+ title: parameter.title,
126
+ }));
127
+ const isTabAddButtonDisabled = tabEditedFilters.length >= tabAvailableFilters.length &&
128
+ tabAvailableParameters.length === 0;
115
129
  const tabTooltipText = isTabAddButtonDisabled
116
130
  ? tooltipTextValues.addDisabled
117
131
  : tooltipTextValues.add;
118
- return (_jsx(AutomationFiltersTabSection, { tabTitle: tab.tabTitle, tabId: tab.tabId, filters: tab.visibleFilters, attributeConfigs: tab.attributeConfigs, commonDateFilterId: commonDateFilterId, lockedFilters: tab.lockedFilters, onChange: (filter) => tabFiltersData.handleTabFilterChange(tab.tabId, filter), onDelete: (filter) => tabFiltersData.handleTabFilterDelete(tab.tabId, filter), overlayPositionType: overlayPositionType, showDivider: index < tabFiltersData.processedFiltersByTab.length - 1, readonlyFilters: disableFilters,
132
+ return (_jsx(AutomationFiltersTabSection, { tabTitle: tab.tabTitle, tabId: tab.tabId, canAddItems: !isTabAddButtonDisabled, filters: tab.visibleFilters, attributeConfigs: tab.attributeConfigs, commonDateFilterId: commonDateFilterId, lockedFilters: tab.lockedFilters, onChange: (filter) => tabFiltersData.handleTabFilterChange(tab.tabId, filter), onDelete: (filter) => tabFiltersData.handleTabFilterDelete(tab.tabId, filter), parameters: tabParameters, onParameterChange: (ref, value) => {
133
+ announceParameterChanged(tabParameters, ref, value);
134
+ onParameterChangeByTab?.(tab.tabId, ref, value);
135
+ }, onParameterDelete: (ref) => {
136
+ announceParameterRemoved(tabParameters, ref);
137
+ onParameterDeleteByTab?.(tab.tabId, ref);
138
+ }, overlayPositionType: overlayPositionType, showDivider: index < tabFiltersData.processedFiltersByTab.length - 1, readonlyFilters: disableFilters,
119
139
  // Add button for each tab section
120
140
  addFilterButton: _jsx(AttributesDropdown, { id: `${AUTOMATION_FILTERS_DIALOG_ID}-${tab.tabId}`, onClose: () => { }, onSelect: (value) => {
121
141
  tabFiltersData.handleTabFilterAdd(tab.tabId, value, tab.attributes, tab.dateDatasets);
142
+ }, parameters: tabParameterDropdownItems, onParameterSelect: (ref) => {
143
+ announceParameterAdded(tabAvailableParameters, ref);
144
+ onParameterAddByTab?.(tab.tabId, ref);
122
145
  }, 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: {
123
146
  ariaLabelledBy: AUTOMATION_FILTERS_DIALOG_TITLE_ID,
124
147
  searchAriaLabel: searchAriaLabel,
@@ -143,13 +166,22 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
143
166
  commonDateFilterId === filter.dateFilter.localIdentifier);
144
167
  return (_jsx(AutomationFilter, { filter: filter, attributeConfigs: attributeConfigs, onChange: handleChangeFilter, onDelete: handleDeleteFilter, isCommonDateFilter: isCommonDateFilter, overlayPositionType: overlayPositionType, lockedFilters: lockedFilters, isReadOnly: disableFilters }, dashboardFilterLocalIdentifier(filter)));
145
168
  }),
146
- ...visibleParameters.map((parameter) => (_jsx(AutomationParameter, { parameter: parameter, onChange: onParameterChange, onDelete: onParameterDelete, overlayPositionType: overlayPositionType }, `parameter-${parameter.ref.identifier}`))),
169
+ ...visibleParameters.map((parameter) => (_jsx(AutomationParameter, { parameter: parameter, onChange: (ref, value) => {
170
+ announceParameterChanged(visibleParameters, ref, value);
171
+ onParameterChange?.(ref, value);
172
+ }, onDelete: (ref) => {
173
+ announceParameterRemoved(visibleParameters, ref);
174
+ onParameterDelete?.(ref);
175
+ }, overlayPositionType: overlayPositionType, isReadOnly: disableFilters }, `parameter-${parameter.ref.identifier}`))),
147
176
  ].slice(0, showAllFilters || isExpanded ? undefined : COLLAPSED_FILTERS_COUNT), isExpanded || !isExpandable ? (_jsx(AttributesDropdown, { id: AUTOMATION_FILTERS_DIALOG_ID, onClose: () => { }, onSelect: (value) => {
148
177
  handleAddFilter(value, attributes, dateDatasets);
149
178
  setIsExpanded(true);
150
179
  }, parameters: parameterDropdownItems, onParameterSelect: (ref) => {
180
+ announceParameterAdded(availableParameters, ref);
151
181
  onParameterAdd?.(ref);
152
182
  setIsExpanded(true);
183
+ // Restore "+" focus, else it falls to <body> when the chip's list node unmounts.
184
+ setTimeout(focusAddFilterButton);
153
185
  }, 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: {
154
186
  ariaLabelledBy: AUTOMATION_FILTERS_DIALOG_TITLE_ID,
155
187
  searchAriaLabel: searchAriaLabel,
@@ -165,10 +197,10 @@ export function AutomationFiltersSelect({ availableFilters = [], selectedFilters
165
197
  ariaExpanded: isOpen,
166
198
  ariaHaspopup: "dialog",
167
199
  } }) }) })), DropdownTitleComponent: () => (_jsx("div", { className: "gd-automation-filters__dropdown-header", children: _jsx(Typography, { tagName: "h3", id: AUTOMATION_FILTERS_DIALOG_TITLE_ID, children: _jsx(FormattedMessage, { id: "dialogs.automation.filters.title" }) }) })), renderNoData: () => (_jsx("div", { className: "gd-automation-filters__dropdown-no-filters", children: _jsx(FormattedMessage, { id: "dialogs.automation.filters.noFilters" }) })) })) : null] }))] }), showAllFilters ? null : (_jsxs(_Fragment, { children: [
168
- _jsx(Divider, {}), _jsx(AutomationCheckboxOrNote, { isDashboardAutomation: isDashboardAutomation, storeFilters: storeFilters, handleStoreFiltersChange: handleStoreFiltersChange, handleKeyDown: handleKeyDown, automationFilterSelectTooltipId: automationFilterSelectTooltipId })
200
+ _jsx(Divider, {}), _jsx(AutomationCheckboxOrNote, { isDashboardAutomation: isDashboardAutomation, storeFilters: storeFilters, handleStoreFiltersChange: handleStoreFiltersChange, handleKeyDown: handleKeyDown, automationFilterSelectTooltipId: automationFilterSelectTooltipId, parametersEnabled: parametersEnabled })
169
201
  ] })), areFiltersMissing ? (_jsx("div", { className: "gd-automation-filters__warning-message", children: _jsx(FormattedMessage, { id: "dialogs.automation.filters.missing", values: {
170
202
  b: (chunk) => _jsx("strong", { children: chunk }),
171
- } }) })) : null] }), _jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", role: "status", children: filterAnnouncement })
203
+ } }) })) : null] }), _jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", role: "status", children: filterAnnouncement }), _jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", role: "status", children: parameterAnnouncement })
172
204
  ] }));
173
205
  }
174
206
  function AutomationFilter({ filter, attributeConfigs, onChange, onDelete, isCommonDateFilter, overlayPositionType, lockedFilters, isReadOnly, tabId, }) {
@@ -194,13 +226,13 @@ function AutomationFilter({ filter, attributeConfigs, onChange, onDelete, isComm
194
226
  * Renders filters for a single tab with a tab title header.
195
227
  * Used when displaying filters grouped by tab for whole dashboard automations.
196
228
  */
197
- function AutomationFiltersTabSection({ tabId, tabTitle, filters, attributeConfigs, commonDateFilterId, lockedFilters, onChange, onDelete, overlayPositionType, showDivider = false, addFilterButton, readonlyFilters, }) {
229
+ function AutomationFiltersTabSection({ tabId, tabTitle, filters, attributeConfigs, commonDateFilterId, lockedFilters, onChange, onDelete, parameters, onParameterChange, onParameterDelete, overlayPositionType, showDivider = false, addFilterButton, canAddItems, readonlyFilters, }) {
198
230
  const intl = useIntl();
199
231
  const tabSectionLabelId = useIdPrefixed(`automation-filters-tab-${tabId}`);
200
232
  // Display tab title or fallback to generic "Tab" label
201
233
  const displayTitle = tabTitle || intl.formatMessage({ id: "dialogs.automation.filters.tab.untitled" });
202
234
  const tabLabel = intl.formatMessage({ id: "dialogs.automation.filters.tab.label" });
203
- if (filters.length === 0) {
235
+ if (filters.length === 0 && parameters.length === 0 && !canAddItems) {
204
236
  return null;
205
237
  }
206
238
  const content = (_jsx("span", { className: "gd-automation-filters__tab-title s-automation-filters-tab-title", children: displayTitle }));
@@ -213,7 +245,7 @@ function AutomationFiltersTabSection({ tabId, tabTitle, filters, attributeConfig
213
245
  (isDashboardDateFilter(filter) &&
214
246
  commonDateFilterId === filter.dateFilter.localIdentifier);
215
247
  return (_jsx(AutomationFilter, { filter: filter, attributeConfigs: attributeConfigs, onChange: onChange, onDelete: onDelete, isCommonDateFilter: isCommonDateFilter, overlayPositionType: overlayPositionType, lockedFilters: lockedFilters, isReadOnly: readonlyFilters, tabId: tabId }, dashboardFilterLocalIdentifier(filter)));
216
- }), addFilterButton] })
248
+ }), parameters.map((parameter) => (_jsx(AutomationParameter, { parameter: parameter, onChange: onParameterChange, onDelete: onParameterDelete, overlayPositionType: overlayPositionType, isReadOnly: readonlyFilters }, `parameter-${parameter.ref.identifier}`))), addFilterButton] })
217
249
  ] }), showDivider ? _jsx("div", { className: "gd-automation-filters__tab-divider" }) : null] }));
218
250
  }
219
251
  /**
@@ -10,11 +10,12 @@ export interface IAutomationParameterProps {
10
10
  onChange?: (ref: IdentifierRef, value: number) => void;
11
11
  onDelete?: (ref: IdentifierRef) => void;
12
12
  overlayPositionType?: OverlayPositionType;
13
+ isReadOnly?: boolean;
13
14
  }
14
15
  /**
15
16
  * Workspace parameter as an editable chip in automation dialogs.
16
17
  *
17
18
  * @internal
18
19
  */
19
- export declare function AutomationParameter({ parameter, onChange, onDelete, overlayPositionType }: IAutomationParameterProps): ReactNode;
20
+ export declare function AutomationParameter({ parameter, onChange, onDelete, overlayPositionType, isReadOnly }: IAutomationParameterProps): ReactNode;
20
21
  //# sourceMappingURL=AutomationParameter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AutomationParameter.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/automations/shared/automationFilters/components/AutomationParameter.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI3D,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAEH,KAAK,mBAAmB,EAM3B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACtB,EAAE,yBAAyB,GAAG,SAAS,CAoEvC"}
1
+ {"version":3,"file":"AutomationParameter.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/automations/shared/automationFilters/components/AutomationParameter.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI3D,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAEH,KAAK,mBAAmB,EAM3B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,UAAU,EACb,EAAE,yBAAyB,GAAG,SAAS,CAmFvC"}
@@ -7,7 +7,7 @@ import { Dropdown, ParameterControlDropdown, UiChip, UiTooltip, isActionKey, use
7
7
  *
8
8
  * @internal
9
9
  */
10
- export function AutomationParameter({ parameter, onChange, onDelete, overlayPositionType, }) {
10
+ export function AutomationParameter({ parameter, onChange, onDelete, overlayPositionType, isReadOnly, }) {
11
11
  const intl = useIntl();
12
12
  const { ref, title, value, mode, constraints } = parameter;
13
13
  const testId = `automation-parameter-${ref.identifier}`;
@@ -15,6 +15,12 @@ export function AutomationParameter({ parameter, onChange, onDelete, overlayPosi
15
15
  const lockedTooltip = intl.formatMessage({ id: "dialogs.automation.filters.lockedTooltip" });
16
16
  const deleteAriaLabel = intl.formatMessage({ id: "dialogs.automation.filters.deleteAriaLabel" });
17
17
  const tooltipId = useIdPrefixed("automation-parameter-tooltip");
18
+ if (isReadOnly) {
19
+ // `isDisabled` renders a real disabled button — unfocusable, so keyboard users can't edit or
20
+ // delete. No lock icon/tooltip: unlike author-`READONLY` this chip is only frozen because
21
+ // persistence is off, so it must not read as author-locked.
22
+ return (_jsx(UiChip, { label: label, iconBefore: "parameter", isDisabled: true, isExpandable: false, dataTestId: testId }));
23
+ }
18
24
  if (mode === DashboardParameterModeValues.READONLY) {
19
25
  return (_jsx(UiChip, { label: label, iconBefore: "parameter", isLocked: true, isExpandable: false, dataTestId: testId, renderChipContent: (content) => (_jsx(UiTooltip, { id: tooltipId, arrowPlacement: "top-start", content: lockedTooltip, triggerBy: ["hover", "focus"], anchor: content, anchorWrapperStyles: { display: "flex", width: "100%", height: "100%", minWidth: 0 } })) }));
20
26
  }
@@ -1,4 +1,4 @@
1
- import { type DashboardAttributeFilterSelectionType, type FilterContextItem, type IAutomationMetadataObject, type IAutomationVisibleFilter, type IFilter, type IInsight } from "@gooddata/sdk-model";
1
+ import { type DashboardAttributeFilterSelectionType, type FilterContextItem, type IAutomationMetadataObject, type IAutomationVisibleFilter, type IDashboardExportParameter, type IDashboardParameter, type IFilter, type IInsight, type IParameterMetadataObject } from "@gooddata/sdk-model";
2
2
  import type { ExtendedDashboardWidget } from "../../../../../model/types/layoutTypes.js";
3
3
  export interface IAutomationValidationResult {
4
4
  isValid: boolean;
@@ -12,12 +12,34 @@ export interface IAutomationValidationResult {
12
12
  visibleFilterIsMissingInSavedFilters: boolean;
13
13
  visibleFiltersAreMissing: boolean;
14
14
  incompatibleSelectionTypeIsAppliedInSavedFilters: boolean;
15
+ /**
16
+ * A stored parameter override is stale: its ref left the catalog, its tab is gone, or a
17
+ * `readonly`/`hidden` parameter's pinned value drifted from the current dashboard.
18
+ */
19
+ parametersAreStale?: boolean;
15
20
  }
16
21
  export declare function useValidateExistingAutomationFilters({ automationToEdit, widget, insight }: {
17
22
  automationToEdit?: IAutomationMetadataObject;
18
23
  widget?: ExtendedDashboardWidget;
19
24
  insight?: IInsight;
20
25
  }): IAutomationValidationResult;
26
+ /**
27
+ * Flags stale stored parameter overrides for an existing automation: a ref that left the workspace
28
+ * catalog, a tab that no longer exists, or a `readonly`/`hidden` parameter whose pinned value
29
+ * drifted from the current dashboard. `active` parameters are user-owned, so their drift is allowed.
30
+ */
31
+ export declare function validateExistingAutomationParameters({ storedParametersByTab, catalog, dashboardParametersByTab, existingTabIds, widgetTabId }: {
32
+ storedParametersByTab: Record<string, IDashboardExportParameter[]> | undefined;
33
+ catalog: IParameterMetadataObject[];
34
+ dashboardParametersByTab: Record<string, IDashboardParameter[]>;
35
+ existingTabIds: Set<string>;
36
+ /**
37
+ * For widget schedules, the widget's current tab. A stored override under any other tab is
38
+ * orphaned (the widget was moved) and flagged stale. Undefined for dashboard schedules and when
39
+ * the tab can't be resolved, where every existing tab is accepted.
40
+ */
41
+ widgetTabId?: string;
42
+ }): boolean;
21
43
  /**
22
44
  * Validate existing automation filters against current dashboard filter context and optionally saved widget / insight.
23
45
  * Check for inconsistencies, that could lead to unwanted results when editing existing automation.
@@ -1 +1 @@
1
- {"version":3,"file":"useValidateExistingAutomationFilters.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/automations/shared/automationFilters/hooks/useValidateExistingAutomationFilters.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,qCAAqC,EAC1C,KAAK,iBAAiB,EAEtB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,OAAO,EAEZ,KAAK,QAAQ,EAkBhB,MAAM,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AA+DzF,MAAM,WAAW,2BAA2B;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,mCAAmC,EAAE,OAAO,CAAC;IAC7C,0CAA0C,EAAE,OAAO,CAAC;IACpD,mCAAmC,EAAE,OAAO,CAAC;IAC7C,0CAA0C,EAAE,OAAO,CAAC;IACpD,oCAAoC,EAAE,OAAO,CAAC;IAC9C,oCAAoC,EAAE,OAAO,CAAC;IAC9C,8CAA8C,EAAE,OAAO,CAAC;IACxD,oCAAoC,EAAE,OAAO,CAAC;IAC9C,wBAAwB,EAAE,OAAO,CAAC;IAClC,gDAAgD,EAAE,OAAO,CAAC;CAC7D;AAgBD,wBAAgB,oCAAoC,CAAC,EACjD,gBAAgB,EAChB,MAAM,EACN,OAAO,EACV,EAAE;IACC,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAC7C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,OAAO,CAAC,EAAE,QAAQ,CAAC;CACtB,GAAG,2BAA2B,CAyF9B;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iCAAiC,CAAC,EAC9C,sBAAsB,EACtB,6BAA6B,EAC7B,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,OAAO,EACP,gBAAgB,EACnB,EAAE;IACC,sBAAsB,EAAE,OAAO,EAAE,CAAC;IAClC,6BAA6B,EAAE,SAAS,GAAG,wBAAwB,EAAE,CAAC;IACtE,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,cAAc,EAAE,iBAAiB,EAAE,CAAC;IACpC,gBAAgB,EAAE,iBAAiB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,qCAAqC,GAAG,SAAS,CAAC,CAAC;CACrF,GAAG,2BAA2B,CAgE9B;AAED,MAAM,WAAW,4BAA4B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,iBAAiB,EAAE,CAAC;IACtC,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,EAAE,iBAAiB,EAAE,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,uCAAuC,CAAC,EACpD,0BAA0B,EAC1B,kCAAkC,EAClC,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACxB,EAAE;IACC,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAChE,kCAAkC,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAAC;IAC/E,sBAAsB,EAAE,4BAA4B,EAAE,CAAC;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,qCAAqC,GAAG,SAAS,CAAC,CAAC,CAAC;CAC1G,GAAG,2BAA2B,CAoE9B"}
1
+ {"version":3,"file":"useValidateExistingAutomationFilters.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/automations/shared/automationFilters/hooks/useValidateExistingAutomationFilters.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,qCAAqC,EAE1C,KAAK,iBAAiB,EAEtB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,OAAO,EAEZ,KAAK,QAAQ,EACb,KAAK,wBAAwB,EAmBhC,MAAM,qBAAqB,CAAC;AA+B7B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AA+DzF,MAAM,WAAW,2BAA2B;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,mCAAmC,EAAE,OAAO,CAAC;IAC7C,0CAA0C,EAAE,OAAO,CAAC;IACpD,mCAAmC,EAAE,OAAO,CAAC;IAC7C,0CAA0C,EAAE,OAAO,CAAC;IACpD,oCAAoC,EAAE,OAAO,CAAC;IAC9C,oCAAoC,EAAE,OAAO,CAAC;IAC9C,8CAA8C,EAAE,OAAO,CAAC;IACxD,oCAAoC,EAAE,OAAO,CAAC;IAC9C,wBAAwB,EAAE,OAAO,CAAC;IAClC,gDAAgD,EAAE,OAAO,CAAC;IAC1D;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAChC;AAiBD,wBAAgB,oCAAoC,CAAC,EACjD,gBAAgB,EAChB,MAAM,EACN,OAAO,EACV,EAAE;IACC,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAC7C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,OAAO,CAAC,EAAE,QAAQ,CAAC;CACtB,GAAG,2BAA2B,CAoD9B;AAoHD;;;;GAIG;AACH,wBAAgB,oCAAoC,CAAC,EACjD,qBAAqB,EACrB,OAAO,EACP,wBAAwB,EACxB,cAAc,EACd,WAAW,EACd,EAAE;IACC,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,EAAE,CAAC,GAAG,SAAS,CAAC;IAC/E,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAChE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAoCV;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iCAAiC,CAAC,EAC9C,sBAAsB,EACtB,6BAA6B,EAC7B,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,OAAO,EACP,gBAAgB,EACnB,EAAE;IACC,sBAAsB,EAAE,OAAO,EAAE,CAAC;IAClC,6BAA6B,EAAE,SAAS,GAAG,wBAAwB,EAAE,CAAC;IACtE,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,cAAc,EAAE,iBAAiB,EAAE,CAAC;IACpC,gBAAgB,EAAE,iBAAiB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,qCAAqC,GAAG,SAAS,CAAC,CAAC;CACrF,GAAG,2BAA2B,CAgE9B;AAED,MAAM,WAAW,4BAA4B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,iBAAiB,EAAE,CAAC;IACtC,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,EAAE,iBAAiB,EAAE,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,uCAAuC,CAAC,EACpD,0BAA0B,EAC1B,kCAAkC,EAClC,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACxB,EAAE;IACC,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAChE,kCAAkC,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAAC;IAC/E,sBAAsB,EAAE,4BAA4B,EAAE,CAAC;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,qCAAqC,GAAG,SAAS,CAAC,CAAC,CAAC;CAC1G,GAAG,2BAA2B,CAoE9B"}
@@ -1,12 +1,17 @@
1
1
  // (C) 2025-2026 GoodData Corporation
2
2
  import { differenceBy, omit } from "lodash-es";
3
- import { dashboardFilterLocalIdentifier, filterLocalIdentifier, isAllDashboardMeasureValueFilter, isAllValuesAttributeFilter, isAllValuesDashboardAttributeFilter, isAttributeFilter, isAttributeFilterWithSelection, isDashboardAttributeFilter, isDashboardCommonDateFilter, isDateFilter, isInsightWidget, isLocalIdRef, isNegativeAttributeFilter, isPositiveAttributeFilter, isRelativeDateFilter, isSingleSelectionFilter, } from "@gooddata/sdk-model";
4
- import { getAutomationAlertFilters, getAutomationDashboardFilters, getAutomationDashboardFiltersByTab, getAutomationVisualizationFilters, } from "../../../../../_staging/automation/index.js";
3
+ import { DashboardParameterModeValues, dashboardFilterLocalIdentifier, filterLocalIdentifier, isAllDashboardMeasureValueFilter, isAllValuesAttributeFilter, isAllValuesDashboardAttributeFilter, isAttributeFilter, isAttributeFilterWithSelection, isDashboardAttributeFilter, isDashboardCommonDateFilter, isDateFilter, isInsightWidget, isLocalIdRef, isNegativeAttributeFilter, isNumberParameterDefinition, isPositiveAttributeFilter, isRelativeDateFilter, isSingleSelectionFilter, } from "@gooddata/sdk-model";
4
+ import { getAutomationAlertFilters, getAutomationDashboardFilters, getAutomationDashboardFiltersByTab, getAutomationExportParametersByTab, getAutomationVisualizationFilters, } from "../../../../../_staging/automation/index.js";
5
5
  import { filterContextItemsToDashboardFiltersByWidget } from "../../../../../converters/filterConverters.js";
6
6
  import { isFilterTypeCompatibleWithSelectionType } from "../../../../../model/commandHandlers/dashboard/common/attributeFilterSelectionTypeCompatibility.js";
7
7
  import { useDashboardSelector } from "../../../../../model/react/DashboardStoreProvider.js";
8
+ import { selectCatalogParameters, selectCatalogParametersIsLoaded, } from "../../../../../model/store/catalog/catalogSelectors.js";
9
+ import { selectEnableParameters } from "../../../../../model/store/config/configSelectors.js";
8
10
  import { selectAutomationCommonDateFilterId, selectAutomationFiltersByTab, selectDashboardFiltersWithoutCrossFiltering, selectDashboardHiddenFilters, selectDashboardLockedFilters, } from "../../../../../model/store/filtering/dashboardFilterSelectors.js";
9
11
  import { selectAttributeFilterConfigsSelectionTypeMap, selectAttributeFilterConfigsSelectionTypeMapByTab, } from "../../../../../model/store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
12
+ import { selectWidgetLocalIdToTabIdMap } from "../../../../../model/store/tabs/layout/layoutSelectors.js";
13
+ import { selectSmartPersistedTabsParameters } from "../../../../../model/store/tabs/parameters/parametersSelectors.js";
14
+ import { selectTabs } from "../../../../../model/store/tabs/tabsSelectors.js";
10
15
  import { areFiltersEqual, isFilterIgnoredByWidget, isFilterMatch, isNoopAllTimeDateFilterFixed, } from "../utils.js";
11
16
  function sanitizeCommonDateFilter(filter, commonDateFilterId) {
12
17
  // Sanitize common date filters by removing date dataSet
@@ -51,6 +56,7 @@ const defaultValidState = {
51
56
  visibleFilterIsMissingInSavedFilters: false,
52
57
  visibleFiltersAreMissing: false,
53
58
  incompatibleSelectionTypeIsAppliedInSavedFilters: false,
59
+ parametersAreStale: false,
54
60
  };
55
61
  export function useValidateExistingAutomationFilters({ automationToEdit, widget, insight, }) {
56
62
  const lockedFilters = useDashboardSelector(selectDashboardLockedFilters);
@@ -60,6 +66,52 @@ export function useValidateExistingAutomationFilters({ automationToEdit, widget,
60
66
  const dashboardFiltersByTab = useDashboardSelector(selectAutomationFiltersByTab);
61
67
  const selectionTypeMap = useDashboardSelector(selectAttributeFilterConfigsSelectionTypeMap);
62
68
  const selectionTypeMapByTab = useDashboardSelector(selectAttributeFilterConfigsSelectionTypeMapByTab);
69
+ const parametersEnabled = useDashboardSelector(selectEnableParameters);
70
+ const catalogParameters = useDashboardSelector(selectCatalogParameters);
71
+ const catalogParametersIsLoaded = useDashboardSelector(selectCatalogParametersIsLoaded);
72
+ const dashboardParametersByTab = useDashboardSelector(selectSmartPersistedTabsParameters);
73
+ const tabs = useDashboardSelector(selectTabs);
74
+ const widgetTabMap = useDashboardSelector(selectWidgetLocalIdToTabIdMap);
75
+ // A widget export covers exactly the widget's current tab, so its stored overrides must live under
76
+ // that tab; a dashboard export covers every tab. Resolved here so the validator can flag a widget
77
+ // whose stored override was orphaned under its previous tab after a move.
78
+ const widgetTabId = widget?.localIdentifier ? widgetTabMap[widget.localIdentifier] : undefined;
79
+ // Before the catalog loads, every stored ref looks removed — treat loading as not-stale.
80
+ const parametersAreStale = parametersEnabled && catalogParametersIsLoaded
81
+ ? validateExistingAutomationParameters({
82
+ storedParametersByTab: getAutomationExportParametersByTab(automationToEdit),
83
+ catalog: catalogParameters,
84
+ dashboardParametersByTab,
85
+ existingTabIds: new Set((tabs ?? []).map((tab) => tab.localIdentifier)),
86
+ widgetTabId,
87
+ })
88
+ : false;
89
+ const filterValidation = resolveFilterValidation({
90
+ automationToEdit,
91
+ widget,
92
+ insight,
93
+ lockedFilters,
94
+ hiddenFilters,
95
+ dashboardFilters,
96
+ commonDateFilterId,
97
+ dashboardFiltersByTab,
98
+ selectionTypeMap,
99
+ selectionTypeMapByTab,
100
+ });
101
+ // Parameter staleness is resolved at this single hook boundary and folded into the filter result
102
+ // exactly once; the pure filter validators below never deal with the parameter concept.
103
+ return {
104
+ ...filterValidation,
105
+ isValid: filterValidation.isValid && !parametersAreStale,
106
+ parametersAreStale,
107
+ };
108
+ }
109
+ /**
110
+ * Resolves the filter-only validation result (no parameter staleness) for an existing automation.
111
+ * Pure: it takes everything the hook already read from the store, so the early-return branching stays
112
+ * testable and free of React hooks.
113
+ */
114
+ function resolveFilterValidation({ automationToEdit, widget, insight, lockedFilters, hiddenFilters, dashboardFilters, commonDateFilterId, dashboardFiltersByTab, selectionTypeMap, selectionTypeMapByTab, }) {
63
115
  const savedAutomationVisibleFilters = automationToEdit?.metadata?.visibleFilters;
64
116
  const savedAutomationVisibleFiltersByTab = automationToEdit?.metadata?.visibleFiltersByTab;
65
117
  const ignoredFilters = widget ? dashboardFilters.filter((f) => isFilterIgnoredByWidget(f, widget)) : [];
@@ -114,6 +166,45 @@ export function useValidateExistingAutomationFilters({ automationToEdit, widget,
114
166
  //
115
167
  // Validations
116
168
  //
169
+ /**
170
+ * Flags stale stored parameter overrides for an existing automation: a ref that left the workspace
171
+ * catalog, a tab that no longer exists, or a `readonly`/`hidden` parameter whose pinned value
172
+ * drifted from the current dashboard. `active` parameters are user-owned, so their drift is allowed.
173
+ */
174
+ export function validateExistingAutomationParameters({ storedParametersByTab, catalog, dashboardParametersByTab, existingTabIds, widgetTabId, }) {
175
+ if (!storedParametersByTab) {
176
+ return false;
177
+ }
178
+ const workspaceById = new Map(catalog.map((parameter) => [parameter.id, parameter]));
179
+ for (const [tabId, storedParameters] of Object.entries(storedParametersByTab)) {
180
+ const tabIsValid = widgetTabId === undefined ? existingTabIds.has(tabId) : tabId === widgetTabId;
181
+ if (!tabIsValid) {
182
+ return true;
183
+ }
184
+ const dashboardById = new Map((dashboardParametersByTab[tabId] ?? []).map((parameter) => [parameter.ref.identifier, parameter]));
185
+ for (const stored of storedParameters) {
186
+ const workspaceParameter = workspaceById.get(stored.id);
187
+ if (!workspaceParameter) {
188
+ return true;
189
+ }
190
+ const dashboardParameter = dashboardById.get(stored.id);
191
+ const mode = dashboardParameter?.mode ?? DashboardParameterModeValues.ACTIVE;
192
+ const isPinnedByAuthor = mode === DashboardParameterModeValues.READONLY ||
193
+ mode === DashboardParameterModeValues.HIDDEN;
194
+ if (!isPinnedByAuthor) {
195
+ continue;
196
+ }
197
+ const workspaceDefault = isNumberParameterDefinition(workspaceParameter.definition)
198
+ ? workspaceParameter.definition.defaultValue
199
+ : undefined;
200
+ const currentValue = dashboardParameter?.value ?? workspaceDefault;
201
+ if (typeof currentValue === "number" && Number(stored.value) !== currentValue) {
202
+ return true;
203
+ }
204
+ }
205
+ }
206
+ return false;
207
+ }
117
208
  /**
118
209
  * Validate existing automation filters against current dashboard filter context and optionally saved widget / insight.
119
210
  * Check for inconsistencies, that could lead to unwanted results when editing existing automation.
@@ -0,0 +1,75 @@
1
+ import { type IAutomationMetadataObject, type IDashboardExportParameter, type IdentifierRef } from "@gooddata/sdk-model";
2
+ import type { ExtendedDashboardWidget } from "../../../../model/types/layoutTypes.js";
3
+ import { type IAutomationParameter } from "./automationParameters.js";
4
+ /**
5
+ * Per-tab parameter working set, keyed by tab `localIdentifier`.
6
+ * @internal
7
+ */
8
+ export type EditedParametersByTab = Record<string, IAutomationParameter[]>;
9
+ /**
10
+ * @internal
11
+ */
12
+ export interface IUseAutomationExportParametersProps {
13
+ automationToEdit?: IAutomationMetadataObject;
14
+ widget?: ExtendedDashboardWidget;
15
+ /**
16
+ * Persist parameter values onto the export, versus omitting them so the server resolves the
17
+ * latest dashboard defaults. Widget schedules force-store (no store-filters checkbox).
18
+ */
19
+ storeParameters?: boolean;
20
+ /**
21
+ * Writes the per-tab parameter wire back onto the edited automation; `undefined` clears it.
22
+ * The user-edit path into `content.parametersByTab` (export-definition rebuilds re-carry it separately).
23
+ */
24
+ setParametersWire: (wire: Record<string, IDashboardExportParameter[]> | undefined) => void;
25
+ }
26
+ /**
27
+ * Scheduled-export parameter editing behind one seam: chips and addable sets to render, the
28
+ * add/change/delete handlers (flat + per-tab), and the wire write-back onto the edited automation.
29
+ * The working set (full execution set incl. `hidden` entries) is implementation — it is held outside
30
+ * the automation so turning persistence off can drop the stored wire without losing the chips. Mirrors
31
+ * {@link useAutomationAlertParameters} in role.
32
+ *
33
+ * The automation is written only at user interactions, never on mount — new automations are seeded
34
+ * at document creation by the owning dialog, and an edited automation's stored wire must survive an
35
+ * open-and-close untouched, or the dialog would open dirty (reconstruct → re-encode is lossy).
36
+ *
37
+ * @internal
38
+ */
39
+ export interface IUseAutomationExportParameters {
40
+ parametersEnabled: boolean;
41
+ /**
42
+ * Per-tab visible parameters to render as chips (the execution set minus `hidden` entries).
43
+ */
44
+ visibleParametersByTab: EditedParametersByTab;
45
+ /**
46
+ * Per-tab workspace parameters addable via the "+" menu (catalog minus selected/hidden/readonly).
47
+ */
48
+ availableParametersByTab: EditedParametersByTab;
49
+ /**
50
+ * The tab the flat (non-tabbed) UI edits — the widget's owning tab or the single dashboard tab.
51
+ * Undefined for multi-tab dashboards (per-tab rendering) or when no parameter context applies.
52
+ */
53
+ flatTabId: string | undefined;
54
+ onParameterAdd: (ref: IdentifierRef) => void;
55
+ onParameterChange: (ref: IdentifierRef, value: number) => void;
56
+ onParameterDelete: (ref: IdentifierRef) => void;
57
+ onParameterAddByTab: (tabId: string, ref: IdentifierRef) => void;
58
+ onParameterChangeByTab: (tabId: string, ref: IdentifierRef, value: number) => void;
59
+ onParameterDeleteByTab: (tabId: string, ref: IdentifierRef) => void;
60
+ /**
61
+ * Resets the working set to the current dashboard's effective values and writes it back — the
62
+ * "apply latest" flow, dropping any stale stored entries the staleness gate flagged.
63
+ */
64
+ applyLatest: () => void;
65
+ /**
66
+ * `storeParameters` still holds the old value at call time, so the new value is passed in;
67
+ * re-encodes the working set onto the automation with it.
68
+ */
69
+ onStoreParametersChange: (storeParameters: boolean) => void;
70
+ }
71
+ /**
72
+ * @internal
73
+ */
74
+ export declare function useAutomationExportParameters({ automationToEdit, widget, storeParameters, setParametersWire }: IUseAutomationExportParametersProps): IUseAutomationExportParameters;
75
+ //# sourceMappingURL=useAutomationExportParameters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAutomationExportParameters.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/automations/shared/automationFilters/useAutomationExportParameters.ts"],"names":[],"mappings":"AAMA,OAAO,EAEH,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAG9B,KAAK,aAAa,EAGrB,MAAM,qBAAqB,CAAC;AAa7B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAEtF,OAAO,EACH,KAAK,oBAAoB,EAM5B,MAAM,2BAA2B,CAAC;AAEnC;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,EAAE,CAAC,CAAC;AAE3E;;GAEG;AACH,MAAM,WAAW,mCAAmC;IAChD,gBAAgB,CAAC,EAAE,yBAAyB,CAAC;IAC7C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,CAAC;CAC9F;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,8BAA8B;IAC3C,iBAAiB,EAAE,OAAO,CAAC;IAC3B;;OAEG;IACH,sBAAsB,EAAE,qBAAqB,CAAC;IAC9C;;OAEG;IACH,wBAAwB,EAAE,qBAAqB,CAAC;IAChD;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,cAAc,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,iBAAiB,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,iBAAiB,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IACjE,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnF,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IACpE;;;OAGG;IACH,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB;;;OAGG;IACH,uBAAuB,EAAE,CAAC,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/D;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,EAC1C,gBAAgB,EAChB,MAAM,EACN,eAAe,EACf,iBAAiB,EACpB,EAAE,mCAAmC,GAAG,8BAA8B,CAuLtE"}
@@ -0,0 +1,143 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { mapValues } from "lodash-es";
4
+ import { DashboardParameterModeValues, areObjRefsEqual, objRefToString, } from "@gooddata/sdk-model";
5
+ import { getAutomationExportParametersByTab } from "../../../../_staging/automation/index.js";
6
+ import { useDashboardSelector } from "../../../../model/react/DashboardStoreProvider.js";
7
+ import { selectCatalogParameters } from "../../../../model/store/catalog/catalogSelectors.js";
8
+ import { selectEnableParameters } from "../../../../model/store/config/configSelectors.js";
9
+ import { selectWidgetLocalIdToTabIdMap } from "../../../../model/store/tabs/layout/layoutSelectors.js";
10
+ import { selectExportEffectiveParameters, selectSmartPersistedTabsParameters, } from "../../../../model/store/tabs/parameters/parametersSelectors.js";
11
+ import { selectTabs } from "../../../../model/store/tabs/tabsSelectors.js";
12
+ import { availableAutomationParameters, exportParametersToValues, reconstructAutomationParametersFromExportParameters, shouldStoreExportParameters, toEffectiveParametersByTab, } from "./automationParameters.js";
13
+ /**
14
+ * @internal
15
+ */
16
+ export function useAutomationExportParameters({ automationToEdit, widget, storeParameters, setParametersWire, }) {
17
+ const parametersEnabled = useDashboardSelector(selectEnableParameters);
18
+ const catalog = useDashboardSelector(selectCatalogParameters);
19
+ const dashboardParametersByTab = useDashboardSelector(selectSmartPersistedTabsParameters);
20
+ const tabs = useDashboardSelector(selectTabs);
21
+ const widgetTabMap = useDashboardSelector(selectWidgetLocalIdToTabIdMap);
22
+ const widgetIds = widget ? [objRefToString(widget.ref)] : undefined;
23
+ const effectiveParametersByTab = useDashboardSelector(selectExportEffectiveParameters(widgetIds));
24
+ const flatTabId = resolveFlatTabId(widget, widgetTabMap, tabs);
25
+ const shouldStore = shouldStoreExportParameters(!!widget, storeParameters);
26
+ const [editedParametersByTab, setEditedParametersByTab] = useState(() => {
27
+ if (!parametersEnabled) {
28
+ return {};
29
+ }
30
+ const storedByTab = getAutomationExportParametersByTab(automationToEdit);
31
+ return reconstructParametersByTab(storedByTab ?? effectiveParametersByTab, dashboardParametersByTab, catalog);
32
+ });
33
+ // Encodes the gated wire shape and writes it onto the automation — the only path to the document.
34
+ const persistWire = useCallback((parametersByTab, store) => setParametersWire(toEffectiveParametersByTab(parametersByTab, parametersEnabled && store)), [parametersEnabled, setParametersWire]);
35
+ const visibleParametersByTab = useMemo(() => mapValues(editedParametersByTab, (parameters) => parameters.filter(isVisibleParameter)), [editedParametersByTab]);
36
+ const availableParametersByTab = useMemo(() => {
37
+ if (!parametersEnabled) {
38
+ return {};
39
+ }
40
+ // The set of tabs the export covers: the widget's owning tab, or every dashboard tab. Keyed by
41
+ // every in-scope tab (not just seeded ones) so tabs with no current override can still add params.
42
+ const scopeTabIds = widget
43
+ ? flatTabId
44
+ ? [flatTabId]
45
+ : []
46
+ : (tabs ?? []).map((tab) => tab.localIdentifier);
47
+ const result = {};
48
+ for (const tabId of scopeTabIds) {
49
+ // Widget schedules seed addable chips from the widget-effective values, mirroring the alert
50
+ // path; dashboard schedules have no widget context and fall back to dashboard/default
51
+ // inside availableAutomationParameters.
52
+ const widgetParameterValues = widget
53
+ ? exportParametersToValues(effectiveParametersByTab[tabId] ?? [])
54
+ : [];
55
+ result[tabId] = availableAutomationParameters(catalog, editedParametersByTab[tabId] ?? [], dashboardParametersByTab[tabId] ?? [], widgetParameterValues);
56
+ }
57
+ return result;
58
+ }, [
59
+ parametersEnabled,
60
+ widget,
61
+ flatTabId,
62
+ tabs,
63
+ editedParametersByTab,
64
+ catalog,
65
+ dashboardParametersByTab,
66
+ effectiveParametersByTab,
67
+ ]);
68
+ // The fresh per-tab execution set reconstructed from the current dashboard's effective values.
69
+ const parametersForNewAutomation = useMemo(() => parametersEnabled
70
+ ? reconstructParametersByTab(effectiveParametersByTab, dashboardParametersByTab, catalog)
71
+ : {}, [parametersEnabled, effectiveParametersByTab, dashboardParametersByTab, catalog]);
72
+ const applyLatest = useCallback(() => {
73
+ setEditedParametersByTab(parametersForNewAutomation);
74
+ persistWire(parametersForNewAutomation, shouldStore);
75
+ }, [parametersForNewAutomation, persistWire, shouldStore]);
76
+ const onStoreParametersChange = useCallback((nextStoreParameters) => {
77
+ persistWire(editedParametersByTab, nextStoreParameters);
78
+ }, [editedParametersByTab, persistWire]);
79
+ // Patches one tab's parameter execution set by ref, updates the working state, and writes the
80
+ // gated wire shape onto the automation. Edits never touch `hidden`/untouched entries.
81
+ const patchTabParameters = useCallback((tabId, update) => {
82
+ const next = {
83
+ ...editedParametersByTab,
84
+ [tabId]: update(editedParametersByTab[tabId] ?? []),
85
+ };
86
+ setEditedParametersByTab(next);
87
+ persistWire(next, shouldStore);
88
+ }, [editedParametersByTab, persistWire, shouldStore]);
89
+ const onParameterChangeByTab = useCallback((tabId, ref, value) => {
90
+ patchTabParameters(tabId, (current) => current.map((parameter) => areObjRefsEqual(parameter.ref, ref) ? { ...parameter, value } : parameter));
91
+ }, [patchTabParameters]);
92
+ const onParameterDeleteByTab = useCallback((tabId, ref) => {
93
+ patchTabParameters(tabId, (current) => current.filter((parameter) => !areObjRefsEqual(parameter.ref, ref)));
94
+ }, [patchTabParameters]);
95
+ const onParameterAddByTab = useCallback((tabId, ref) => {
96
+ const parameter = availableParametersByTab[tabId]?.find((candidate) => areObjRefsEqual(candidate.ref, ref));
97
+ if (parameter) {
98
+ patchTabParameters(tabId, (current) => [...current, parameter]);
99
+ }
100
+ }, [availableParametersByTab, patchTabParameters]);
101
+ // Flat handlers serve the non-tabbed UI; they delegate to the per-tab ones via `flatTabId`.
102
+ const onParameterChange = useCallback((ref, value) => {
103
+ if (flatTabId) {
104
+ onParameterChangeByTab(flatTabId, ref, value);
105
+ }
106
+ }, [flatTabId, onParameterChangeByTab]);
107
+ const onParameterDelete = useCallback((ref) => {
108
+ if (flatTabId) {
109
+ onParameterDeleteByTab(flatTabId, ref);
110
+ }
111
+ }, [flatTabId, onParameterDeleteByTab]);
112
+ const onParameterAdd = useCallback((ref) => {
113
+ if (flatTabId) {
114
+ onParameterAddByTab(flatTabId, ref);
115
+ }
116
+ }, [flatTabId, onParameterAddByTab]);
117
+ return {
118
+ parametersEnabled,
119
+ visibleParametersByTab,
120
+ availableParametersByTab,
121
+ flatTabId,
122
+ onParameterAdd,
123
+ onParameterChange,
124
+ onParameterDelete,
125
+ onParameterAddByTab,
126
+ onParameterChangeByTab,
127
+ onParameterDeleteByTab,
128
+ applyLatest,
129
+ onStoreParametersChange,
130
+ };
131
+ }
132
+ function resolveFlatTabId(widget, widgetTabMap, tabs) {
133
+ if (widget) {
134
+ return widget.localIdentifier ? widgetTabMap[widget.localIdentifier] : undefined;
135
+ }
136
+ return (tabs?.length ?? 0) <= 1 ? tabs?.[0]?.localIdentifier : undefined;
137
+ }
138
+ function isVisibleParameter(parameter) {
139
+ return parameter.mode !== DashboardParameterModeValues.HIDDEN;
140
+ }
141
+ function reconstructParametersByTab(byTab, dashboardParametersByTab, catalog) {
142
+ return mapValues(byTab, (exportParameters, tabId) => reconstructAutomationParametersFromExportParameters(exportParameters, dashboardParametersByTab[tabId] ?? [], catalog));
143
+ }