From 35296257748133cfb08eff8397c51ae2d095a173 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Thu, 12 Dec 2024 17:00:46 +0100 Subject: [PATCH] date time picker and default values --- backend/windmill-api/src/jobs.rs | 210 +++++++++++++++++++++++++------ 1 file changed, 170 insertions(+), 40 deletions(-) diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 24ea441530..39e7845b23 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -5524,6 +5524,8 @@ struct State { #[serde(tag = "type", rename_all = "snake_case")] enum ValueInput { PlainTextInput { value: Option }, + Datepicker { selected_date: Option }, + Timepicker { selected_time: Option }, StaticSelect { selected_option: Option }, RadioButtons { selected_option: Option }, Checkboxes { selected_options: Option> }, @@ -5539,6 +5541,7 @@ pub async fn slack_app_callback_handler( Extension(db): Extension, Form(form_data): Form, ) -> error::Result { + tracing::debug!("Form data: {:?}", form_data); let payload: Payload = serde_json::from_str(&form_data.payload)?; let action_value = payload.actions[0].value.clone(); @@ -5557,14 +5560,41 @@ pub async fn slack_app_callback_handler( tracing::debug!("Request: {:?}", &form_data.payload.clone()); - let state_values = payload - .state - .values - .iter() - .flat_map(|(_, inputs)| { - inputs.iter().filter_map(|(action_id, input)| match input { + let state_values: HashMap = payload + .state + .values + .iter() + .flat_map(|(_, inputs)| { + inputs.iter().filter_map(|(action_id, input)| { + if action_id.ends_with("_date") { + let base_key = action_id.strip_suffix("_date").unwrap(); + let time_key = format!("{}_time", base_key); + + // Check for Datepicker and Timepicker inputs specifically + if let ValueInput::Datepicker { selected_date: Some(date) } = input { + let matching_time = payload.state.values.values().flat_map(|inputs| { + inputs.get(&time_key).and_then(|time_input| { + if let ValueInput::Timepicker { selected_time: Some(time) } = time_input { + Some(time) + } else { + None + } + }) + }).next(); + + if let Some(time) = matching_time { + return Some(( + base_key.to_string(), + serde_json::json!(format!("{}T{}:00.000Z", date, time)), + )); + } + } + } + + // Process non-datetime inputs, including plain text or other types with `_date` + match input { ValueInput::PlainTextInput { value } => { - value.as_ref().map(|v| (action_id.clone(), v.clone())) + value.as_ref().map(|v| (action_id.clone(), v.clone().into())) } ValueInput::StaticSelect { selected_option } => selected_option .as_ref() @@ -5583,12 +5613,15 @@ pub async fn slack_app_callback_handler( ) }) } - }) + _ => None, + } }) - .collect::>(); + }) + .collect(); + - let state_json = - serde_json::to_value(state_values).unwrap_or_else(|_| serde_json::json!({})); + let state_json = serde_json::to_value(state_values) + .unwrap_or_else(|_| serde_json::json!({})); tracing::debug!("W ID: {}", w_id); tracing::debug!("Action: {}", action); @@ -5615,14 +5648,14 @@ pub async fn slack_app_callback_handler( if let Some(url) = response_url { let message = if action == "resume" { - "\n\n*Flow has been resumed!*" + "\n\n*Workflow has been resumed!*" } else { - "\n\n*Flow has been canceled!*" + "\n\n*Workflow has been canceled!*" }; let _ = post_slack_response(&url, message).await; } } else { - println!("Resume URL does not match the pattern."); + tracing::error!("Resume URL does not match the pattern."); } Ok(StatusCode::OK) @@ -5671,7 +5704,7 @@ pub async fn request_slack_approval( tracing::debug!("schema: {:?}", schema); tracing::debug!("job_id: {:?}", job_id); - let message_str = message.message.as_deref().unwrap_or("*A flow has been suspended and is waiting for approval:*\n"); + let message_str = message.message.as_deref().unwrap_or("*A workflow has been suspended and is waiting for approval:*\n"); if let Some(resume_schema) = schema { let hide_cancel = resume_schema.hide_cancel.unwrap_or(false); @@ -5780,6 +5813,7 @@ pub struct Schema { pub struct ResumeFormField { #[serde(rename = "type")] pub r#type: String, + pub format: Option, pub default: Option, pub description: Option, pub title: Option, @@ -5808,11 +5842,23 @@ async fn transform_schemas( if let Some(properties) = properties { if let Some(order) = order { - blocks.extend(order.iter().filter_map(|key| { - properties.get(key).map(|schema| create_input_block(key, schema)) - })); + for key in order { + if let Some(schema) = properties.get(key) { + let input_block = create_input_block(key, schema); + match input_block { + serde_json::Value::Array(arr) => blocks.extend(arr), + _ => blocks.push(input_block), + } + } + } } else { - blocks.extend(properties.iter().map(|(key, schema)| create_input_block(key, schema))); + for (key, schema) in properties { + let input_block = create_input_block(key, schema); + match input_block { + serde_json::Value::Array(arr) => blocks.extend(arr), + _ => blocks.push(input_block), + } + } } } @@ -5828,30 +5874,112 @@ fn create_input_block(key: &str, schema: &ResumeFormField) -> serde_json::Value .filter(|desc| !desc.is_empty()) .unwrap_or("Select an option"); - if let Some(enums) = &schema.r#enum { - serde_json::json!({ - "type": "input", - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": placeholder, - "emoji": true, - }, - "options": enums.iter().map(|enum_value| { - serde_json::json!({ - "text": { + // Handle date-time format + if schema.r#type == "string" && schema.format.as_deref() == Some("date-time") { + let now = chrono::Local::now(); + let current_date = now.format("%Y-%m-%d").to_string(); + let current_time = now.format("%H:%M").to_string(); + + let (default_date, default_time) = if let Some(default) = &schema.default { + if let Ok(parsed_date) = chrono::DateTime::parse_from_rfc3339(default) { + ( + parsed_date.format("%Y-%m-%d").to_string(), + parsed_date.format("%H:%M").to_string(), + ) + } else { + (current_date.clone(), current_time.clone()) + } + } else { + (current_date.clone(), current_time.clone()) + }; + + return serde_json::json!([ + { + "type": "input", + "element": { + "type": "datepicker", + "initial_date": &default_date, + "placeholder": { "type": "plain_text", - "text": schema.enum_labels.as_ref() - .and_then(|labels| labels.get(enum_value)) - .unwrap_or(enum_value), + "text": "Select a date", "emoji": true }, - "value": enum_value - }) - }).collect::>(), - "action_id": key + "action_id": format!("{}_date", key) + }, + "label": { + "type": "plain_text", + "text": schema.title.as_deref().unwrap_or(key), + "emoji": true + } + }, + { + "type": "input", + "element": { + "type": "timepicker", + "initial_time": &default_time, + "placeholder": { + "type": "plain_text", + "text": "Select time", + "emoji": true + }, + "action_id": format!("{}_time", key) + }, + "label": { + "type": "plain_text", + "text": " ", + "emoji": true + } + } + ]); + } + + // Handle enum type + if let Some(enums) = &schema.r#enum { + + let initial_option = schema.default.as_ref().and_then(|default_value| { + enums.iter().find(|enum_value| enum_value == &default_value).map(|enum_value| { + serde_json::json!({ + "text": { + "type": "plain_text", + "text": schema.enum_labels.as_ref() + .and_then(|labels| labels.get(enum_value)) + .unwrap_or(enum_value), + "emoji": true + }, + "value": enum_value + }) + }) + }); + + let mut element = serde_json::json!({ + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": placeholder, + "emoji": true, }, + "options": enums.iter().map(|enum_value| { + serde_json::json!({ + "text": { + "type": "plain_text", + "text": schema.enum_labels.as_ref() + .and_then(|labels| labels.get(enum_value)) + .unwrap_or(enum_value), + "emoji": true + }, + "value": enum_value + }) + }).collect::>(), + "action_id": key + }); + + if let Some(option) = initial_option { + element["initial_option"] = option; + } + + serde_json::json!({ + "type": "input", + "element": element, "label": { "type": "plain_text", "text": schema.title.as_deref().unwrap_or(key), @@ -5859,11 +5987,13 @@ fn create_input_block(key: &str, schema: &ResumeFormField) -> serde_json::Value } }) } else { + // Handle other types serde_json::json!({ "type": "input", "element": { "type": "plain_text_input", - "action_id": key + "action_id": key, + "initial_value": schema.default.as_deref().unwrap_or("") }, "label": { "type": "plain_text",