Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/docusaurus/docs/releases/01-Release Next/release-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ title: Release Notes next
SPDX-FileCopyrightText: 2025 Sequent Tech <legal@sequentech.io>
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.
Expand Down
27 changes: 24 additions & 3 deletions packages/admin-portal/src/resources/Reports/EditReportForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,19 @@ export const EditReportForm: React.FC<CreateReportProps> = ({
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<Sequent_Backend_Report> = {
...formValues,
encryption_policy: reportEncryptionPolicy,
Expand Down Expand Up @@ -392,6 +405,7 @@ export const EditReportForm: React.FC<CreateReportProps> = ({
interface EmailRecipientsInputProps extends InputProps {
label?: string
placeholder?: string
onChange?: (newValue: any) => void
}

interface EmailRecipientsInputProps extends InputProps {
Expand All @@ -406,6 +420,7 @@ const EmailRecipientsInput: React.FC<EmailRecipientsInputProps> = (props) => {
isRequired,
id,
} = useInput(props)
const {label, placeholder, onChange} = props

return (
<Autocomplete
Expand All @@ -414,7 +429,10 @@ const EmailRecipientsInput: React.FC<EmailRecipientsInputProps> = (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) =>
Expand All @@ -426,8 +444,8 @@ const EmailRecipientsInput: React.FC<EmailRecipientsInputProps> = (props) => {
<TextField
{...params}
variant="outlined"
label={props.label}
placeholder={props.placeholder}
label={label}
placeholder={placeholder}
error={fieldState.invalid}
helperText={fieldState.error?.message}
required={isRequired}
Expand Down Expand Up @@ -747,6 +765,9 @@ const FormContent: React.FC<CreateReportProps> = ({
label={t("reportsScreen.fields.emailRecipients")}
placeholder={t("reportsScreen.fields.emailRecipientsPlaceholder")}
isRequired={false}
onChange={(newValue) => {
setValue("cron_config.email_recipients", newValue)
}}
/>
</>
)}
Expand Down
2 changes: 1 addition & 1 deletion packages/windmill/src/bin/beat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 5 additions & 7 deletions packages/windmill/src/postgres/reports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 23 additions & 5 deletions packages/windmill/src/tasks/generate_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
)
Expand All @@ -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(())
}
Loading