diff --git a/docs/docusaurus/docs/releases/01-Release Next/release-next.md b/docs/docusaurus/docs/releases/01-Release Next/release-next.md index cf1fb30ec7b..29c2f388f2f 100644 --- a/docs/docusaurus/docs/releases/01-Release Next/release-next.md +++ b/docs/docusaurus/docs/releases/01-Release Next/release-next.md @@ -6,6 +6,16 @@ title: Release Notes next SPDX-FileCopyrightText: 2025 Sequent Tech SPDX-License-Identifier: AGPL-3.0-only --> + +## 🐞 Admin Portal: Scheduled Repeatable Reports is not working + +Modify the admin portal add/edit report form to require cron expression and email +list to be filled. +Fix using the recipients email list instead of empty list +when execute the report. + +- Issue: [#5412](https://github.com/sequentech/meta/issues/5412) + ## 🐞 Tally > Export option can't be read correctly if title is too long Modify the tally export translations to show the format before the document name. diff --git a/packages/admin-portal/src/resources/Reports/EditReportForm.tsx b/packages/admin-portal/src/resources/Reports/EditReportForm.tsx index e8b67e96aa8..c1d33324bbc 100644 --- a/packages/admin-portal/src/resources/Reports/EditReportForm.tsx +++ b/packages/admin-portal/src/resources/Reports/EditReportForm.tsx @@ -277,6 +277,19 @@ export const EditReportForm: React.FC = ({ delete formValues.password } + if (isCronActive) { + const expr = values.cron_config?.cron_expression + const emails = values.cron_config?.email_recipients || [] + if (!expr) { + notify("Please configure a cron schedule before saving", {type: "error"}) + return + } + if (emails.length === 0) { + notify("Please enter at least one e-mail recipient", {type: "error"}) + return + } + } + const formData: Partial = { ...formValues, encryption_policy: reportEncryptionPolicy, @@ -392,6 +405,7 @@ export const EditReportForm: React.FC = ({ interface EmailRecipientsInputProps extends InputProps { label?: string placeholder?: string + onChange?: (newValue: any) => void } interface EmailRecipientsInputProps extends InputProps { @@ -406,6 +420,7 @@ const EmailRecipientsInput: React.FC = (props) => { isRequired, id, } = useInput(props) + const {label, placeholder, onChange} = props return ( = (props) => { options={[] as string[]} value={field.value || []} onChange={(event: any, newValue: string[]) => { - field.onChange(newValue) + field.onChange(newValue || []) + if (onChange) { + onChange(newValue || []) // Call the passed onChange prop + } }} fullWidth={true} renderTags={(value: string[], getTagProps) => @@ -426,8 +444,8 @@ const EmailRecipientsInput: React.FC = (props) => { = ({ label={t("reportsScreen.fields.emailRecipients")} placeholder={t("reportsScreen.fields.emailRecipientsPlaceholder")} isRequired={false} + onChange={(newValue) => { + setValue("cron_config.email_recipients", newValue) + }} /> )} diff --git a/packages/windmill/src/bin/beat.rs b/packages/windmill/src/bin/beat.rs index ed37e58f471..328b0c3fbb3 100644 --- a/packages/windmill/src/bin/beat.rs +++ b/packages/windmill/src/bin/beat.rs @@ -31,7 +31,7 @@ struct CeleryOpt { review_boards_interval: u64, #[structopt(short = "s", long, default_value = "10")] schedule_events_interval: u64, - #[structopt(short = "c", long, default_value = "10")] + #[structopt(short = "c", long, default_value = "60")] schedule_reports_interval: u64, #[structopt(short = "e", long, default_value = "5")] electoral_log_interval: u64, diff --git a/packages/windmill/src/postgres/reports.rs b/packages/windmill/src/postgres/reports.rs index b2cc00bfa2c..c4e44fa0f35 100644 --- a/packages/windmill/src/postgres/reports.rs +++ b/packages/windmill/src/postgres/reports.rs @@ -172,16 +172,14 @@ pub async fn update_report_last_document_time( UPDATE "sequent_backend".report SET - cron_config = + cron_config = jsonb_set( COALESCE(cron_config, '{}'::jsonb), '{last_document_produced}', to_jsonb( - to_char(NOW() at time zone 'utc', - 'YYYY-MM-DD"T"HH24:MI:SS.US' - ) - ), - true - ) + to_char(NOW() at time zone 'utc', 'YYYY-MM-DD"T"HH24:MI:SS.US') + ), + true + ) WHERE tenant_id = $1 AND id = $2 diff --git a/packages/windmill/src/tasks/generate_report.rs b/packages/windmill/src/tasks/generate_report.rs index fcd295cb110..664c08d5881 100644 --- a/packages/windmill/src/tasks/generate_report.rs +++ b/packages/windmill/src/tasks/generate_report.rs @@ -114,6 +114,8 @@ pub async fn generate_report( } }; + let email_recipients = report.cron_config.unwrap_or_default().email_recipients; + // Helper macro to reduce duplication in execute_report call macro_rules! execute_report { ($report:expr) => { @@ -123,7 +125,7 @@ pub async fn generate_report( &report.tenant_id, &report.election_event_id, is_scheduled_task, - vec![], + email_recipients, report_mode, Some(report_clone), &hasura_transaction, @@ -265,6 +267,7 @@ pub async fn generate_report( ) -> Result<()> { let _permit = acquire_semaphore().await?; // Spawn the task using an async block + let task_execution_clone = task_execution.clone(); let handle = tokio::task::spawn_blocking({ move || { tokio::runtime::Handle::current().block_on(async move { @@ -273,7 +276,7 @@ pub async fn generate_report( document_id, report_mode, is_scheduled_task, - task_execution, + task_execution_clone, executer_username, tally_session_id, ) @@ -285,9 +288,24 @@ pub async fn generate_report( // Await the result and handle JoinError explicitly match handle.await { - Ok(inner_result) => inner_result.map_err(|err| Error::from(err.context("Task failed"))), - Err(join_error) => Err(Error::from(anyhow!("Task panicked: {}", join_error))), - }?; + Ok(inner_result) => { + if let Err(ref err) = inner_result { + if let Some(ref task_exec) = task_execution { + let _ = update_fail(task_exec, &format!("Task failed: {:?}", err)).await; + } + } + inner_result.map_err(|err| Error::from(err.context("Task failed")))?; + } + Err(join_error) => { + if let Some(ref task_exec) = task_execution { + let _ = update_fail(task_exec, &format!("Task panicked: {}", join_error)).await; + } + return Err(Error::from(anyhow::anyhow!( + "Task panicked: {}", + join_error + ))); + } + } Ok(()) }