@gooddata/sdk-ui-dashboard 11.50.0-alpha.0 → 11.50.0-alpha.2

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 (26) hide show
  1. package/NOTICE +216 -203
  2. package/esm/__version.d.ts +1 -1
  3. package/esm/__version.js +1 -1
  4. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useEditScheduledEmail.d.ts +6 -6
  5. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useEditScheduledEmail.d.ts.map +1 -1
  6. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useEditScheduledEmail.js +65 -534
  7. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailEffectiveFilters.d.ts +38 -0
  8. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailEffectiveFilters.d.ts.map +1 -0
  9. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailEffectiveFilters.js +61 -0
  10. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailExportSettings.d.ts +57 -0
  11. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailExportSettings.d.ts.map +1 -0
  12. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailExportSettings.js +241 -0
  13. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailFormState.d.ts +55 -0
  14. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailFormState.d.ts.map +1 -0
  15. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailFormState.js +203 -0
  16. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailFormValidity.d.ts +21 -0
  17. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailFormValidity.d.ts.map +1 -0
  18. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/hooks/useScheduledEmailFormValidity.js +52 -0
  19. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/utils/exportDefinitions.d.ts +27 -0
  20. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/utils/exportDefinitions.d.ts.map +1 -0
  21. package/esm/presentation/automations/scheduledEmail/DefaultScheduledEmailDialog/utils/exportDefinitions.js +95 -0
  22. package/esm/presentation/filterBar/filterBar/DefaultFilterBarContainer.d.ts.map +1 -1
  23. package/esm/presentation/filterBar/filterBar/DefaultFilterBarContainer.js +11 -5
  24. package/esm/tsdoc-metadata.json +1 -1
  25. package/package.json +21 -21
  26. package/styles/css/main.css +1 -1
@@ -1,98 +1,53 @@
1
1
  // (C) 2019-2026 GoodData Corporation
2
- import { useCallback, useMemo, useRef, useState } from "react";
3
- import { useIntl } from "react-intl";
4
- import { invariant } from "ts-invariant";
5
- import { DEFAULT_CSV_DELIMITER, insightProperties, isAutomationExternalUserRecipient, isAutomationUnknownUserRecipient, isAutomationUserRecipient, isExportDefinitionDashboardRequestPayload, isExportDefinitionVisualizationObjectRequestPayload, isInsightWidget, } from "@gooddata/sdk-model";
2
+ import { useCallback, useRef } from "react";
3
+ import { DEFAULT_CSV_DELIMITER, isExportDefinitionDashboardRequestPayload, isExportDefinitionVisualizationObjectRequestPayload, isInsightWidget, } from "@gooddata/sdk-model";
6
4
  import { getAutomationExportParametersByTab, setExportParametersByTab, } from "../../../../../_staging/automation/index.js";
7
5
  import { useAutomationsContext } from "../../../contexts/AutomationsContext.js";
8
6
  import { useScheduledEmailDialogContext } from "../../../contexts/ScheduledEmailDialogContext.js";
9
- import { shouldStoreExportParameters } from "../../../shared/automationFilters/automationParameters.js";
10
7
  import { getDefaultSelectedFiltersFromFiltersByTab } from "../../../shared/automationFilters/useAutomationFiltersSelect.js";
11
8
  import { getAppliedDashboardFilters, getAppliedWidgetFilters, getVisibleFiltersByFilters, getVisibleFiltersByFiltersByTab, } from "../../../shared/automationFilters/utils.js";
12
- import { areAutomationsEqual, convertCurrentUserToAutomationRecipient, convertCurrentUserToWorkspaceUser, convertExternalRecipientToAutomationRecipient, } from "../../../shared/utils/automationUtils.js";
13
- import { toModifiedISOStringToTimezone, toNormalizedFirstRunAndCron, toNormalizedStartDate, } from "../../utils/date.js";
14
- import { getUserTimezone } from "../../utils/timezone.js";
15
- import { isEmail } from "../../utils/validate.js";
16
- import { useScheduleValidation } from "./useScheduleValidation.js";
9
+ import { toNormalizedStartDate } from "../../utils/date.js";
10
+ import { useScheduledEmailEffectiveFilters } from "./useScheduledEmailEffectiveFilters.js";
11
+ import { useScheduledEmailExportSettings } from "./useScheduledEmailExportSettings.js";
12
+ import { useScheduledEmailFormState } from "./useScheduledEmailFormState.js";
13
+ import { useScheduledEmailFormValidity } from "./useScheduledEmailFormValidity.js";
17
14
  export function useEditScheduledEmail({ scheduledExportToEdit, notificationChannels, insight, widget, users, editedAutomationFilters, dashboardFilters, editedAutomationFiltersByTab, maxAutomationsRecipients, setEditedAutomationFilters, setEditedAutomationFiltersByTab, availableFiltersAsVisibleFilters, storeFilters, setStoreFilters, filtersForNewAutomation, externalRecipientOverride, defaultPdfPageSize, filtersDataByTab, availableFiltersAsVisibleFiltersByTab, }) {
18
- const intl = useIntl();
19
- const { settings, timezone, currentUser, features: { enableAutomationEvaluationMode }, } = useAutomationsContext();
20
- const { dashboardId, dashboardTitle, hiddenFilters: dashboardHiddenFilters, commonDateFilterId, widgetLocalIdToTabIdMap: widgetTabMap, exportParametersByTab: effectiveExportParametersByTab, } = useScheduledEmailDialogContext();
21
- const [isCronValid, setIsCronValid] = useState(true);
22
- const [isTitleValid, setIsTitleValid] = useState(true);
23
- const [isSubjectValid, setIsSubjectValid] = useState(true);
24
- const [isOnMessageValid, setIsOnMessageValid] = useState(true);
15
+ const { settings, features: { enableAutomationEvaluationMode }, } = useAutomationsContext();
16
+ const { dashboardId, dashboardTitle, hiddenFilters: dashboardHiddenFilters, commonDateFilterId, widgetLocalIdToTabIdMap: widgetTabMap, } = useScheduledEmailDialogContext();
25
17
  const isWidget = !!widget && !!insight;
26
18
  // Dashboard
27
19
  const resolvedDefaultCsvDelimiter = settings?.exportCsvCustomDelimiter ?? DEFAULT_CSV_DELIMITER;
28
20
  const areDashboardFiltersChanged = !!dashboardFilters;
29
- const defaultUser = convertCurrentUserToWorkspaceUser(users ?? [], currentUser);
30
- const defaultRecipient = externalRecipientOverride
31
- ? convertExternalRecipientToAutomationRecipient(externalRecipientOverride)
32
- : convertCurrentUserToAutomationRecipient(users ?? [], currentUser);
33
- const firstChannel = notificationChannels[0]?.id;
34
21
  // Determine target tab ID if widget is present
35
22
  const targetTabId = widget?.localIdentifier ? widgetTabMap[widget.localIdentifier] : undefined;
36
- const effectiveWidgetFilters = getAppliedWidgetFilters(editedAutomationFilters ?? [], dashboardHiddenFilters, widget, insight, commonDateFilterId, false);
37
- const effectiveWidgetFiltersWithInsight = getAppliedWidgetFilters(editedAutomationFilters ?? [], dashboardHiddenFilters, widget, insight, commonDateFilterId, true);
38
- const effectiveVisibleWidgetFilters = getVisibleFiltersByFilters(editedAutomationFilters, availableFiltersAsVisibleFilters, true);
39
- const effectiveDashboardFilters = getAppliedDashboardFilters(editedAutomationFilters ?? [], dashboardHiddenFilters, isWidget ? true : storeFilters);
40
- // Process filters per tab if provided (for dashboard automations with tabs enabled)
41
- const effectiveDashboardFiltersByTab = useMemo(() => {
42
- if (!editedAutomationFiltersByTab || !storeFilters) {
43
- return undefined;
44
- }
45
- // Apply the same processing as effectiveDashboardFilters to each tab's filters
46
- return Object.entries(editedAutomationFiltersByTab).reduce((acc, [tabId, filters]) => {
47
- const tabHiddenFilters = filtersDataByTab?.find((tab) => tab.tabId === tabId)?.hiddenFilters ?? [];
48
- const appliedFilters = getAppliedDashboardFilters(filters ?? [], tabHiddenFilters, storeFilters);
49
- // Only add if we got filters back (storeFilters is true)
50
- if (appliedFilters) {
51
- acc[tabId] = appliedFilters;
52
- }
53
- return acc;
54
- }, {});
55
- }, [editedAutomationFiltersByTab, filtersDataByTab, storeFilters]);
56
- const effectiveVisibleDashboardFilters = getVisibleFiltersByFilters(editedAutomationFilters ?? [], availableFiltersAsVisibleFilters, storeFilters);
57
- const effectiveVisibleDashboardFiltersByTab = getVisibleFiltersByFiltersByTab(editedAutomationFiltersByTab, availableFiltersAsVisibleFiltersByTab, storeFilters);
58
- // Mirrors the filters seed above, for parameters.
59
- const parametersByTabForNewAutomation = shouldStoreExportParameters(isWidget, storeFilters) &&
60
- Object.keys(effectiveExportParametersByTab).length > 0
61
- ? effectiveExportParametersByTab
62
- : undefined;
63
- const [editedAutomation, setEditedAutomation] = useState(scheduledExportToEdit ??
64
- newAutomationMetadataObjectDefinition(isWidget
65
- ? {
66
- timezone,
67
- dashboardId: dashboardId,
68
- notificationChannel: firstChannel,
69
- insight,
70
- widget,
71
- recipient: defaultRecipient,
72
- widgetFilters: effectiveWidgetFilters,
73
- widgetFiltersWithInsight: effectiveWidgetFiltersWithInsight,
74
- dashboardFilters: effectiveDashboardFilters,
75
- visibleFiltersMetadata: effectiveVisibleWidgetFilters,
76
- defaultPdfPageSize,
77
- evaluationMode: "PER_RECIPIENT",
78
- targetTabId,
79
- parametersByTab: parametersByTabForNewAutomation,
80
- }
81
- : {
82
- timezone,
83
- dashboardId: dashboardId,
84
- notificationChannel: firstChannel,
85
- title: dashboardTitle,
86
- recipient: defaultRecipient,
87
- dashboardFilters: effectiveDashboardFilters,
88
- filtersByTab: effectiveDashboardFiltersByTab,
89
- visibleFiltersMetadata: effectiveVisibleDashboardFilters,
90
- visibleFiltersByTab: effectiveVisibleDashboardFiltersByTab,
91
- defaultPdfPageSize,
92
- evaluationMode: "PER_RECIPIENT",
93
- parametersByTab: parametersByTabForNewAutomation,
94
- }));
95
- const [originalAutomation] = useState(editedAutomation);
23
+ const { effectiveWidgetFilters, effectiveWidgetFiltersWithInsight, effectiveVisibleWidgetFilters, effectiveDashboardFilters, effectiveDashboardFiltersByTab, effectiveVisibleDashboardFilters, effectiveVisibleDashboardFiltersByTab, parametersByTabForNewAutomation, } = useScheduledEmailEffectiveFilters({
24
+ widget,
25
+ insight,
26
+ editedAutomationFilters,
27
+ editedAutomationFiltersByTab,
28
+ availableFiltersAsVisibleFilters,
29
+ availableFiltersAsVisibleFiltersByTab,
30
+ filtersDataByTab,
31
+ storeFilters,
32
+ });
33
+ const { editedAutomation, setEditedAutomation, originalAutomation, defaultRecipient, defaultUser, onTitleChange, onRecurrenceChange, onEvaluationModeChange, onDestinationChange, onRecipientsChange, onSubjectChange, onMessageChange, isCronValid, isTitleValid, isSubjectValid, isOnMessageValid, } = useScheduledEmailFormState({
34
+ scheduledExportToEdit,
35
+ widget,
36
+ insight,
37
+ notificationChannels,
38
+ users,
39
+ externalRecipientOverride,
40
+ effectiveWidgetFilters,
41
+ effectiveWidgetFiltersWithInsight,
42
+ effectiveVisibleWidgetFilters,
43
+ effectiveDashboardFilters,
44
+ effectiveDashboardFiltersByTab,
45
+ effectiveVisibleDashboardFilters,
46
+ effectiveVisibleDashboardFiltersByTab,
47
+ parametersByTabForNewAutomation,
48
+ defaultPdfPageSize,
49
+ targetTabId,
50
+ });
96
51
  // Holds the wire outside the automation so it survives a rebuild from zero export definitions —
97
52
  // with no definitions `setExportParametersByTab` has nowhere to store it.
98
53
  // Seeded from the stored wire.
@@ -103,213 +58,23 @@ export function useEditScheduledEmail({ scheduledExportToEdit, notificationChann
103
58
  const setParametersWire = useCallback((wire) => {
104
59
  latestParametersWireRef.current = wire;
105
60
  setEditedAutomation((automation) => setExportParametersByTab(automation, wire));
106
- }, []);
107
- const selectedAttachments = useMemo(() => {
108
- return (editedAutomation.exportDefinitions
109
- ?.map((exportDefinition) => exportDefinition.requestPayload.format)
110
- .filter(Boolean) ?? []);
111
- }, [editedAutomation.exportDefinitions]);
112
- const onTitleChange = (value, isValid) => {
113
- setIsTitleValid(isValid);
114
- setEditedAutomation((s) => ({ ...s, title: value }));
115
- };
116
- const onRecurrenceChange = (cronExpression, startDate, isValid) => {
117
- setIsCronValid(isValid);
118
- setEditedAutomation((s) => ({
119
- ...s,
120
- schedule: {
121
- ...s.schedule,
122
- cron: cronExpression,
123
- firstRun: toModifiedISOStringToTimezone(startDate ?? new Date(), timezone).iso,
124
- },
125
- }));
126
- };
127
- const onEvaluationModeChange = (isShared) => {
128
- setEditedAutomation((s) => ({
129
- ...s,
130
- evaluationMode: isShared ? "SHARED" : "PER_RECIPIENT",
131
- }));
132
- };
133
- const onDestinationChange = (notificationChannelId) => {
134
- setEditedAutomation((s) => ({
135
- ...s,
136
- notificationChannel: notificationChannelId,
137
- }));
138
- };
139
- const onRecipientsChange = (updatedRecipients) => {
140
- setEditedAutomation((s) => ({
141
- ...s,
142
- recipients: updatedRecipients,
143
- }));
144
- };
145
- const onSubjectChange = (value, isValid) => {
146
- setIsSubjectValid(isValid);
147
- setEditedAutomation((s) => ({
148
- ...s,
149
- details: {
150
- ...s.details,
151
- subject: value,
152
- },
153
- }));
154
- };
155
- const onMessageChange = (value, isValid) => {
156
- setIsOnMessageValid(isValid);
157
- setEditedAutomation((s) => ({
158
- ...s,
159
- details: {
160
- ...s.details,
161
- message: value,
162
- },
163
- }));
164
- };
165
- const onDashboardAttachmentsChange = (formats) => {
166
- setEditedAutomation((s) => {
167
- const currentExportDefinitions = s.exportDefinitions || [];
168
- const currentDashboardExportDefinitions = currentExportDefinitions.filter((exportDefinition) => isExportDefinitionDashboardRequestPayload(exportDefinition.requestPayload));
169
- const currentFormats = currentDashboardExportDefinitions.map((exportDefinition) => exportDefinition.requestPayload.format);
170
- const formatsToKeep = currentFormats.filter((format) => formats.includes(format));
171
- const formatsToAdd = formats.filter((format) => !currentFormats.includes(format));
172
- const keptExportDefinitions = currentDashboardExportDefinitions.filter((exportDefinition) => formatsToKeep.includes(exportDefinition.requestPayload.format));
173
- const newExportDefinitions = formatsToAdd.map((format) => newDashboardExportDefinitionMetadataObjectDefinition({
174
- dashboardId: dashboardId,
175
- dashboardTitle,
176
- dashboardFilters: storeFilters ? effectiveDashboardFilters : undefined,
177
- filtersByTab: storeFilters ? effectiveDashboardFiltersByTab : undefined,
178
- format,
179
- }));
180
- const updatedExportDefinitions = [...keptExportDefinitions, ...newExportDefinitions];
181
- return withRebuiltExportDefinitions(s, updatedExportDefinitions, latestParametersWireRef.current);
182
- });
183
- };
184
- const onWidgetAttachmentsChange = (formats) => {
185
- invariant(isWidget, "Widget or insight is missing in scheduling dialog context.");
186
- setEditedAutomation((s) => {
187
- const currentExportDefinitions = s.exportDefinitions || [];
188
- const currentWidgetExportDefinitions = currentExportDefinitions.filter((exportDefinition) => isExportDefinitionVisualizationObjectRequestPayload(exportDefinition.requestPayload));
189
- const currentFormats = currentWidgetExportDefinitions.map((exportDefinition) => exportDefinition.requestPayload.format);
190
- const formatsToKeep = currentFormats.filter((format) => formats.includes(format));
191
- const formatsToAdd = formats.filter((format) => !currentFormats.includes(format));
192
- const keptExportDefinitions = currentWidgetExportDefinitions.filter((exportDefinition) => formatsToKeep.includes(exportDefinition.requestPayload.format));
193
- const newExportDefinitions = formatsToAdd.map((format) => newWidgetExportDefinitionMetadataObjectDefinition({
194
- insight,
195
- widget,
196
- dashboardId: dashboardId,
197
- format,
198
- widgetFilters: effectiveWidgetFilters,
199
- widgetFiltersWithInsight: effectiveWidgetFiltersWithInsight,
200
- dashboardFilters: effectiveDashboardFilters,
201
- defaultPdfPageSize,
202
- }));
203
- const updatedExportDefinitions = [...keptExportDefinitions, ...newExportDefinitions];
204
- return withRebuiltExportDefinitions(s, updatedExportDefinitions, latestParametersWireRef.current);
205
- });
206
- };
207
- const onXlsxSettingsChange = useCallback((settings) => {
208
- setEditedAutomation((s) => ({
209
- ...s,
210
- exportDefinitions: s.exportDefinitions?.map((exportDefinition) => {
211
- if (exportDefinition.requestPayload.format !== "XLSX") {
212
- return exportDefinition;
213
- }
214
- const nextSettings = {
215
- ...exportDefinition.requestPayload?.settings,
216
- mergeHeaders: settings.mergeHeaders,
217
- exportInfo: settings.exportInfo,
218
- };
219
- return {
220
- ...exportDefinition,
221
- requestPayload: {
222
- ...exportDefinition.requestPayload,
223
- settings: nextSettings,
224
- },
225
- };
226
- }),
227
- }));
228
- }, [setEditedAutomation]);
229
- const onPdfSettingsChange = useCallback((settings) => {
230
- setEditedAutomation((s) => ({
231
- ...s,
232
- exportDefinitions: s.exportDefinitions?.map((exportDefinition) => {
233
- if (exportDefinition.requestPayload.format !== "PDF_TABULAR") {
234
- return exportDefinition;
235
- }
236
- const nextSettings = {
237
- ...exportDefinition.requestPayload?.settings,
238
- pageSize: settings.pageSize,
239
- orientation: settings.orientation ?? "portrait",
240
- exportInfo: settings.exportInfo,
241
- };
242
- return {
243
- ...exportDefinition,
244
- requestPayload: {
245
- ...exportDefinition.requestPayload,
246
- settings: nextSettings,
247
- },
248
- };
249
- }),
250
- }));
251
- }, [setEditedAutomation]);
252
- const onCsvSettingsChange = useCallback((settings) => {
253
- setEditedAutomation((s) => ({
254
- ...s,
255
- exportDefinitions: s.exportDefinitions?.map((exportDefinition) => {
256
- if (exportDefinition.requestPayload.format !== "CSV") {
257
- return exportDefinition;
258
- }
259
- return {
260
- ...exportDefinition,
261
- requestPayload: {
262
- ...exportDefinition.requestPayload,
263
- settings: {
264
- ...exportDefinition.requestPayload.settings,
265
- delimiter: settings.delimiter,
266
- },
267
- },
268
- };
269
- }),
270
- }));
271
61
  }, [setEditedAutomation]);
272
- const onCsvRawSettingsChange = useCallback((settings) => {
273
- setEditedAutomation((s) => ({
274
- ...s,
275
- exportDefinitions: s.exportDefinitions?.map((exportDefinition) => {
276
- if (exportDefinition.requestPayload.format !== "CSV_RAW") {
277
- return exportDefinition;
278
- }
279
- return {
280
- ...exportDefinition,
281
- requestPayload: {
282
- ...exportDefinition.requestPayload,
283
- settings: {
284
- ...exportDefinition.requestPayload.settings,
285
- delimiter: settings.delimiter,
286
- },
287
- },
288
- };
289
- }),
290
- }));
291
- }, [setEditedAutomation]);
292
- const onSlidesTemplateIdChange = useCallback((templateId, format) => {
293
- setEditedAutomation((s) => ({
294
- ...s,
295
- exportDefinitions: s.exportDefinitions?.map((exportDefinition) => {
296
- const matchesFormat = exportDefinition.requestPayload.format === format;
297
- const matchesType = isWidget
298
- ? isExportDefinitionVisualizationObjectRequestPayload(exportDefinition.requestPayload)
299
- : isExportDefinitionDashboardRequestPayload(exportDefinition.requestPayload);
300
- if (!matchesFormat || !matchesType) {
301
- return exportDefinition;
302
- }
303
- return {
304
- ...exportDefinition,
305
- requestPayload: {
306
- ...exportDefinition.requestPayload,
307
- templateId,
308
- },
309
- };
310
- }),
311
- }));
312
- }, [setEditedAutomation, isWidget]);
62
+ const { selectedAttachments, isDashboardExportSelected, isCsvExportSelected, isXlsxExportSelected, xlsxSettings, pdfSettings, csvSettings, csvRawSettings, slidesTemplateIds, onDashboardAttachmentsChange, onWidgetAttachmentsChange, onXlsxSettingsChange, onPdfSettingsChange, onCsvSettingsChange, onCsvRawSettingsChange, onSlidesTemplateIdChange, } = useScheduledEmailExportSettings({
63
+ editedAutomation,
64
+ setEditedAutomation,
65
+ insight,
66
+ widget,
67
+ dashboardId,
68
+ dashboardTitle,
69
+ storeFilters,
70
+ effectiveDashboardFilters,
71
+ effectiveDashboardFiltersByTab,
72
+ effectiveWidgetFilters,
73
+ effectiveWidgetFiltersWithInsight,
74
+ defaultPdfPageSize,
75
+ resolvedDefaultCsvDelimiter,
76
+ latestParametersWireRef,
77
+ });
313
78
  const onFiltersChange = useCallback((filters, storeFiltersParam) => {
314
79
  setEditedAutomationFilters(filters);
315
80
  const shouldStoreFilters = storeFiltersParam ?? storeFilters;
@@ -474,101 +239,19 @@ export function useEditScheduledEmail({ scheduledExportToEdit, notificationChann
474
239
  onFiltersChange(filters, value);
475
240
  }
476
241
  }, [onFiltersChange, onFiltersByTabChange, setStoreFilters]);
477
- const isDashboardExportSelected = editedAutomation.exportDefinitions?.some((exportDefinition) => isExportDefinitionDashboardRequestPayload(exportDefinition.requestPayload)) ?? true;
478
- const isCsvExportSelected = editedAutomation.exportDefinitions?.some((exportDefinition) => {
479
- if (isExportDefinitionVisualizationObjectRequestPayload(exportDefinition.requestPayload)) {
480
- return exportDefinition.requestPayload.format === "CSV";
481
- }
482
- return false;
483
- }) ?? false;
484
- const isXlsxExportSelected = editedAutomation.exportDefinitions?.some((exportDefinition) => {
485
- if (isExportDefinitionVisualizationObjectRequestPayload(exportDefinition.requestPayload)) {
486
- return exportDefinition.requestPayload.format === "XLSX";
487
- }
488
- return false;
489
- }) ?? false;
490
- const xlsxExportSettings = editedAutomation.exportDefinitions?.find((exportDefinition) => exportDefinition.requestPayload.format === "XLSX")?.requestPayload.settings;
491
- const xlsxSettings = {
492
- mergeHeaders: xlsxExportSettings?.mergeHeaders ?? true,
493
- exportInfo: xlsxExportSettings?.exportInfo ?? true,
494
- };
495
- const pdfVisualizationSettings = editedAutomation.exportDefinitions?.find((exportDefinition) => isExportDefinitionVisualizationObjectRequestPayload(exportDefinition.requestPayload) &&
496
- exportDefinition.requestPayload.format === "PDF_TABULAR")?.requestPayload.settings;
497
- const pdfTabularSettings = pdfVisualizationSettings && "pageSize" in pdfVisualizationSettings
498
- ? pdfVisualizationSettings
499
- : undefined;
500
- const pdfSettings = {
501
- pageSize: pdfTabularSettings?.pageSize ?? defaultPdfPageSize ?? "A4",
502
- orientation: pdfTabularSettings?.orientation ?? "portrait",
503
- exportInfo: pdfTabularSettings?.exportInfo ?? true,
504
- };
505
- const csvExportDefinition = editedAutomation.exportDefinitions?.find((exportDefinition) => isExportDefinitionVisualizationObjectRequestPayload(exportDefinition.requestPayload) &&
506
- exportDefinition.requestPayload.format === "CSV");
507
- const csvExportSettings = csvExportDefinition &&
508
- isExportDefinitionVisualizationObjectRequestPayload(csvExportDefinition.requestPayload)
509
- ? csvExportDefinition.requestPayload.settings
510
- : undefined;
511
- const csvSettings = {
512
- delimiter: csvExportSettings?.delimiter ?? resolvedDefaultCsvDelimiter,
513
- };
514
- const csvRawExportDefinition = editedAutomation.exportDefinitions?.find((exportDefinition) => isExportDefinitionVisualizationObjectRequestPayload(exportDefinition.requestPayload) &&
515
- exportDefinition.requestPayload.format === "CSV_RAW");
516
- const csvRawExportSettings = csvRawExportDefinition &&
517
- isExportDefinitionVisualizationObjectRequestPayload(csvRawExportDefinition.requestPayload)
518
- ? csvRawExportDefinition.requestPayload.settings
519
- : undefined;
520
- const csvRawSettings = {
521
- delimiter: csvRawExportSettings?.delimiter ?? resolvedDefaultCsvDelimiter,
522
- };
523
- // Extract templateId per slides format, scoped to the current dialog mode
524
- const getTemplateIdForFormat = (format) => {
525
- const def = editedAutomation.exportDefinitions?.find((ed) => ed.requestPayload.format === format &&
526
- (isWidget
527
- ? isExportDefinitionVisualizationObjectRequestPayload(ed.requestPayload)
528
- : isExportDefinitionDashboardRequestPayload(ed.requestPayload)));
529
- return def?.requestPayload.templateId;
530
- };
531
- const slidesTemplateIds = {
532
- PPTX: getTemplateIdForFormat("PPTX"),
533
- PDF_SLIDES: getTemplateIdForFormat("PDF_SLIDES"),
534
- PDF: getTemplateIdForFormat("PDF"),
535
- };
536
242
  const startDate = toNormalizedStartDate(editedAutomation.schedule?.firstRun, editedAutomation.schedule?.timezone);
537
- const selectedNotificationChannel = notificationChannels.find((channel) => channel.id === editedAutomation.notificationChannel);
538
- const allowExternalRecipients = selectedNotificationChannel?.allowedRecipients === "external";
539
- const allowOnlyLoggedUserRecipients = selectedNotificationChannel?.allowedRecipients === "creator";
540
- const { isValid: isParentValid } = useScheduleValidation(originalAutomation);
541
- const validationErrorMessage = isParentValid
542
- ? undefined
543
- : intl.formatMessage({ id: "dialogs.schedule.email.widgetError" });
544
- const hasAttachments = !!editedAutomation.exportDefinitions?.length;
545
- const hasRecipients = (editedAutomation.recipients?.length ?? 0) > 0;
546
- const hasValidExternalRecipients = allowExternalRecipients
547
- ? true
548
- : !editedAutomation.recipients?.some(isAutomationExternalUserRecipient);
549
- const hasValidCreatorRecipient = allowOnlyLoggedUserRecipients
550
- ? editedAutomation.recipients?.length === 1 &&
551
- editedAutomation.recipients[0].id === defaultRecipient.id
552
- : true;
553
- const hasNoUnknownRecipients = !editedAutomation.recipients?.some(isAutomationUnknownUserRecipient);
554
- const hasDestination = !!editedAutomation.notificationChannel;
555
- const respectsRecipientsLimit = (editedAutomation.recipients?.length ?? 0) <= maxAutomationsRecipients;
556
- const hasFilledEmails = selectedNotificationChannel?.destinationType === "smtp"
557
- ? editedAutomation.recipients?.every((recipient) => isAutomationUserRecipient(recipient) ? isEmail(recipient.email ?? "") : true)
558
- : true;
559
- const isValid = isCronValid &&
560
- hasRecipients &&
561
- respectsRecipientsLimit &&
562
- hasAttachments &&
563
- hasDestination &&
564
- hasValidExternalRecipients &&
565
- hasValidCreatorRecipient &&
566
- hasNoUnknownRecipients &&
567
- hasFilledEmails &&
568
- isOnMessageValid &&
569
- isTitleValid &&
570
- isSubjectValid;
571
- const isSubmitDisabled = !isValid || (scheduledExportToEdit && areAutomationsEqual(originalAutomation, editedAutomation));
243
+ const { isSubmitDisabled, validationErrorMessage, isParentValid, allowExternalRecipients, allowOnlyLoggedUserRecipients, } = useScheduledEmailFormValidity({
244
+ editedAutomation,
245
+ originalAutomation,
246
+ scheduledExportToEdit,
247
+ notificationChannels,
248
+ defaultRecipient,
249
+ maxAutomationsRecipients,
250
+ isCronValid,
251
+ isTitleValid,
252
+ isSubjectValid,
253
+ isOnMessageValid,
254
+ });
572
255
  return {
573
256
  defaultUser,
574
257
  areDashboardFiltersChanged,
@@ -614,155 +297,3 @@ export function useEditScheduledEmail({ scheduledExportToEdit, notificationChann
614
297
  enableAutomationEvaluationMode,
615
298
  };
616
299
  }
617
- /**
618
- * Rebuilds the export definitions and re-applies the parameter wire (fresh definitions carry no
619
- * `content.parametersByTab`). The wire is passed in rather than read off `automation` so it survives
620
- * a rebuild from zero definitions — see `latestParametersWireRef`.
621
- */
622
- function withRebuiltExportDefinitions(automation, exportDefinitions, parametersByTab) {
623
- const next = { ...automation, exportDefinitions };
624
- return parametersByTab ? setExportParametersByTab(next, parametersByTab) : next;
625
- }
626
- function newDashboardExportDefinitionMetadataObjectDefinition({ dashboardId, dashboardTitle, dashboardFilters, filtersByTab, format, templateId, }) {
627
- // Use filtersByTab if provided, otherwise fall back to simple filters
628
- const filtersObj = filtersByTab
629
- ? { filtersByTab }
630
- : dashboardFilters
631
- ? { filters: dashboardFilters }
632
- : {};
633
- const settingsObj = format === "XLSX" ? { settings: { mergeHeaders: true, exportInfo: true } } : {};
634
- return {
635
- type: "exportDefinition",
636
- title: dashboardTitle,
637
- requestPayload: {
638
- type: "dashboard",
639
- fileName: dashboardTitle,
640
- format,
641
- content: {
642
- dashboard: dashboardId,
643
- ...filtersObj,
644
- },
645
- ...settingsObj,
646
- ...(templateId ? { templateId } : {}),
647
- },
648
- };
649
- }
650
- function newWidgetExportDefinitionMetadataObjectDefinition({ insight, widget, dashboardId, format, widgetFilters, widgetFiltersWithInsight, dashboardFilters, defaultPdfPageSize, defaultCsvDelimiter, }) {
651
- const widgetTitle = widget.title;
652
- // Determine which filters to use based on format:
653
- // - CSV: Use widgetFiltersWithInsight (insight filters merged on frontend)
654
- // - CSV_RAW: Use widgetFilters (insight filters merged on backend)
655
- // - Other formats: Use dashboardFilters (backend handles insight filter merging)
656
- const shouldUseCsvFilters = format === "CSV";
657
- const shouldUseCsvRawFilters = format === "CSV_RAW";
658
- let filtersObj = {};
659
- if (shouldUseCsvFilters && (widgetFiltersWithInsight ?? []).length > 0) {
660
- filtersObj = { filters: widgetFiltersWithInsight };
661
- }
662
- else if (shouldUseCsvRawFilters && (widgetFilters ?? []).length > 0) {
663
- filtersObj = { filters: widgetFilters };
664
- }
665
- else if (!shouldUseCsvFilters && !shouldUseCsvRawFilters && (dashboardFilters ?? []).length > 0) {
666
- filtersObj = { filters: dashboardFilters };
667
- }
668
- const grandTotalsPosition = insightProperties(insight)?.["controls"]?.["grandTotalsPosition"];
669
- const pdfSettings = {
670
- pageSize: defaultPdfPageSize ?? "A4",
671
- orientation: "portrait",
672
- exportInfo: true,
673
- ...(grandTotalsPosition ? { grandTotalsPosition } : {}),
674
- };
675
- const xlsxSettings = {
676
- mergeHeaders: true,
677
- exportInfo: true,
678
- ...(grandTotalsPosition ? { grandTotalsPosition } : {}),
679
- };
680
- const csvSettings = {
681
- ...(defaultCsvDelimiter ? { delimiter: defaultCsvDelimiter } : {}),
682
- ...(grandTotalsPosition ? { grandTotalsPosition } : {}),
683
- };
684
- const hasCsvSettings = Object.keys(csvSettings).length > 0;
685
- const settingsObj = format === "XLSX"
686
- ? { settings: xlsxSettings }
687
- : format === "PDF_TABULAR"
688
- ? { settings: pdfSettings }
689
- : (format === "CSV" || format === "CSV_RAW") && hasCsvSettings
690
- ? { settings: csvSettings }
691
- : {};
692
- return {
693
- type: "exportDefinition",
694
- title: widgetTitle,
695
- requestPayload: {
696
- type: "visualizationObject",
697
- fileName: widgetTitle,
698
- format: format,
699
- content: {
700
- visualizationObject: insight.insight.identifier,
701
- widget: widget.identifier,
702
- dashboard: dashboardId,
703
- ...filtersObj,
704
- },
705
- ...settingsObj,
706
- },
707
- };
708
- }
709
- function newAutomationMetadataObjectDefinition({ timezone, dashboardId, notificationChannel, title, insight, widget, recipient, dashboardFilters, filtersByTab, widgetFilters, widgetFiltersWithInsight, visibleFiltersMetadata, visibleFiltersByTab, defaultPdfPageSize, evaluationMode, targetTabId, parametersByTab, }) {
710
- const { firstRun, cron } = toNormalizedFirstRunAndCron(timezone);
711
- const exportDefinition = widget && insight
712
- ? newWidgetExportDefinitionMetadataObjectDefinition({
713
- insight,
714
- widget,
715
- dashboardId,
716
- format: "PNG",
717
- widgetFilters,
718
- widgetFiltersWithInsight,
719
- dashboardFilters,
720
- defaultPdfPageSize,
721
- })
722
- : newDashboardExportDefinitionMetadataObjectDefinition({
723
- dashboardId,
724
- dashboardTitle: title ?? "",
725
- dashboardFilters,
726
- filtersByTab,
727
- format: "PDF",
728
- });
729
- let metadataObj = visibleFiltersMetadata || visibleFiltersByTab
730
- ? {
731
- metadata: {
732
- ...(visibleFiltersMetadata ? { visibleFilters: visibleFiltersMetadata } : {}),
733
- ...(visibleFiltersByTab ? { visibleFiltersByTab } : {}),
734
- },
735
- }
736
- : {};
737
- if (targetTabId) {
738
- metadataObj = {
739
- ...metadataObj,
740
- metadata: {
741
- ...metadataObj.metadata,
742
- targetTabIdentifier: targetTabId,
743
- },
744
- };
745
- }
746
- const automation = {
747
- type: "automation",
748
- title: undefined,
749
- description: undefined,
750
- tags: [],
751
- schedule: {
752
- timezone: timezone ?? getUserTimezone().identifier,
753
- firstRun,
754
- cron,
755
- },
756
- details: {
757
- message: "",
758
- subject: "",
759
- },
760
- exportDefinitions: [{ ...exportDefinition }],
761
- recipients: [recipient],
762
- evaluationMode,
763
- notificationChannel,
764
- dashboard: dashboardId ? { id: dashboardId } : undefined,
765
- ...metadataObj,
766
- };
767
- return parametersByTab ? setExportParametersByTab(automation, parametersByTab) : automation;
768
- }