interactive slack approvals: more improvements / nits (#5012)

* enum -> enums, client function docs

* advanced -> suspend -> form
This commit is contained in:
Alexander Petric
2025-01-04 01:48:25 +01:00
committed by GitHub
parent d291a287ba
commit 0dbaabc35a
4 changed files with 102 additions and 38 deletions
+1 -1
View File
@@ -6980,7 +6980,7 @@ paths:
required: false
schema:
type: string
- name: dynamic_enum_json
- name: dynamic_enums_json
in: query
required: false
schema:
+22 -22
View File
@@ -160,7 +160,7 @@ pub struct QueryDefaultArgsJson {
#[derive(Deserialize, Debug)]
pub struct QueryDynamicEnumJson {
dynamic_enum_json: Option<serde_json::Value>,
dynamic_enums_json: Option<serde_json::Value>,
}
#[derive(Deserialize, Debug)]
@@ -172,7 +172,7 @@ struct ModalActionValue {
message: Option<String>,
flow_step_id: Option<String>,
default_args_json: Option<String>,
dynamic_enum_json: Option<String>,
dynamic_enums_json: Option<String>,
}
#[derive(Deserialize, Debug)]
@@ -233,19 +233,19 @@ pub async fn slack_app_callback_handler(
)
})?;
let dynamic_enum_json: Option<serde_json::Value> = parsed_value
.dynamic_enum_json
let dynamic_enums_json: Option<serde_json::Value> = parsed_value
.dynamic_enums_json
.as_deref()
.map(|s| serde_json::from_str(s))
.transpose()
.map_err(|_| {
Error::BadRequest(
"Invalid JSON in dynamic_enum_json".to_string(),
"Invalid JSON in dynamic_enums_json".to_string(),
)
})?;
tracing::debug!("Default args json: {:#?}", default_args_json);
tracing::debug!("Dynamic enum json: {:#?}", dynamic_enum_json);
tracing::debug!("Dynamic enum json: {:#?}", dynamic_enums_json);
open_modal_with_blocks(
&client,
@@ -260,7 +260,7 @@ pub async fn slack_app_callback_handler(
flow_step_id,
container,
default_args_json.as_ref(),
dynamic_enum_json.as_ref(),
dynamic_enums_json.as_ref(),
)
.await
.map_err(|e| Error::BadRequest(e.to_string()))?;
@@ -289,7 +289,7 @@ pub async fn request_slack_approval(
Query(channel_id): Query<QueryChannelId>,
Query(flow_step_id): Query<QueryFlowStepId>,
Query(default_args_json): Query<QueryDefaultArgsJson>,
Query(dynamic_enum_json): Query<QueryDynamicEnumJson>,
Query(dynamic_enums_json): Query<QueryDynamicEnumJson>,
) -> Result<StatusCode, Error> {
let slack_resource_path = slack_resource_path.slack_resource_path;
let channel_id = channel_id.channel_id;
@@ -315,7 +315,7 @@ pub async fn request_slack_approval(
message.message.as_deref(),
flow_step_id.as_str(),
default_args_json.default_args_json.as_ref(),
dynamic_enum_json.dynamic_enum_json.as_ref(),
dynamic_enums_json.dynamic_enums_json.as_ref(),
)
.await
.map_err(|e| Error::BadRequest(e.to_string()))?;
@@ -430,7 +430,7 @@ async fn transform_schemas(
order: Option<&Vec<String>>,
required: Option<&Vec<String>>,
default_args_json: Option<&serde_json::Value>,
dynamic_enum_json: Option<&serde_json::Value>,
dynamic_enums_json: Option<&serde_json::Value>,
) -> Result<serde_json::Value, Error> {
tracing::debug!("Resume urls: {:#?}", urls);
@@ -448,10 +448,10 @@ async fn transform_schemas(
let is_required = required.unwrap().contains(key);
let default_value = default_args_json.and_then(|json| json.get(key).cloned());
let dynamic_enum_value = dynamic_enum_json.and_then(|json| json.get(key).cloned());
let dynamic_enums_value = dynamic_enums_json.and_then(|json| json.get(key).cloned());
let input_block =
create_input_block(key, schema, is_required, default_value, dynamic_enum_value);
create_input_block(key, schema, is_required, default_value, dynamic_enums_value);
match input_block {
serde_json::Value::Array(arr) => blocks.extend(arr),
_ => blocks.push(input_block),
@@ -468,7 +468,7 @@ fn create_input_block(
schema: &ResumeFormField,
required: bool,
default_value: Option<serde_json::Value>,
dynamic_enum_value: Option<serde_json::Value>,
dynamic_enums_value: Option<serde_json::Value>,
) -> serde_json::Value {
let placeholder = schema
.description
@@ -614,7 +614,7 @@ fn create_input_block(
// Handle enum type
if let Some(enums) = &schema.r#enum {
tracing::debug!("Enum type");
let enums = dynamic_enum_value
let enums = dynamic_enums_value
.as_ref()
.and_then(|v| v.as_array())
.cloned()
@@ -866,7 +866,7 @@ async fn send_slack_message(
message: Option<&str>,
flow_step_id: &str,
default_args_json: Option<&serde_json::Value>,
dynamic_enum_json: Option<&serde_json::Value>,
dynamic_enums_json: Option<&serde_json::Value>,
) -> Result<StatusCode, Box<dyn std::error::Error>> {
let url = "https://slack.com/api/chat.postMessage";
@@ -890,8 +890,8 @@ async fn send_slack_message(
value["default_args_json"] = default_args_json.clone();
}
if let Some(dynamic_enum_json) = dynamic_enum_json {
value["dynamic_enum_json"] = dynamic_enum_json.clone();
if let Some(dynamic_enums_json) = dynamic_enums_json {
value["dynamic_enums_json"] = dynamic_enums_json.clone();
}
let payload = serde_json::json!({
@@ -956,7 +956,7 @@ async fn get_modal_blocks(
resource_path: &str,
container: Container,
default_args_json: Option<&serde_json::Value>,
dynamic_enum_json: Option<&serde_json::Value>,
dynamic_enums_json: Option<&serde_json::Value>,
) -> Result<axum::Json<serde_json::Value>, Error> {
let res = get_resume_urls_internal(
axum::Extension(db.clone()),
@@ -1065,7 +1065,7 @@ async fn get_modal_blocks(
Some(&inner_schema.schema.order),
Some(&inner_schema.schema.required),
default_args_json,
dynamic_enum_json,
dynamic_enums_json,
)
.await?;
@@ -1087,7 +1087,7 @@ async fn get_modal_blocks(
None,
None,
default_args_json,
dynamic_enum_json,
dynamic_enums_json,
)
.await?;
return Ok(axum::Json(construct_payload(
@@ -1156,7 +1156,7 @@ async fn open_modal_with_blocks(
flow_step_id: Option<&str>,
container: Container,
default_args_json: Option<&serde_json::Value>,
dynamic_enum_json: Option<&serde_json::Value>,
dynamic_enums_json: Option<&serde_json::Value>,
) -> Result<(), Box<dyn std::error::Error>> {
let resume_id = rand::random::<u32>();
let blocks_json = match get_modal_blocks(
@@ -1171,7 +1171,7 @@ async fn open_modal_with_blocks(
resource_path,
container,
default_args_json,
dynamic_enum_json,
dynamic_enums_json,
)
.await
{
+41 -10
View File
@@ -631,14 +631,45 @@ class Windmill:
message: str = None,
approver: str = None,
default_args_json: dict = None,
dynamic_enum_json: dict = None,
dynamic_enums_json: dict = None,
) -> None:
"""
Request interactive Slack approval
:param slack_resource_path: Slack resource path
:param channel_id: Slack channel
:param message: Message to send to Slack
:param approver: Approver name
Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.
**[Enterprise Edition Only]** To include form fields in the Slack approval request, use the "Advanced -> Suspend -> Form" functionality.
Learn more at: https://www.windmill.dev/docs/flows/flow_approval#form
:param slack_resource_path: The path to the Slack resource in Windmill.
:type slack_resource_path: str
:param channel_id: The Slack channel ID where the approval request will be sent.
:type channel_id: str
:param message: Optional custom message to include in the Slack approval request.
:type message: str, optional
:param approver: Optional user ID or name of the approver for the request.
:type approver: str, optional
:param default_args_json: Optional dictionary defining or overriding the default arguments for form fields.
:type default_args_json: dict, optional
:param dynamic_enums_json: Optional dictionary overriding the enum default values of enum form fields.
:type dynamic_enums_json: dict, optional
:raises Exception: If the function is not called within a flow or flow preview.
:raises Exception: If the required flow job or flow step environment variables are not set.
:return: None
**Usage Example:**
>>> client.request_interactive_slack_approval(
... slack_resource_path="/u/alex/my_slack_resource",
... channel_id="admins-slack-channel",
... message="Please approve this request",
... approver="approver123",
... default_args_json={"key1": "value1", "key2": 42},
... dynamic_enums_json={"foo": ["choice1", "choice2"], "bar": ["optionA", "optionB"]},
... )
**Notes:**
- This function must be executed within a Windmill flow or flow preview.
- The function checks for required environment variables (`WM_FLOW_JOB_ID`, `WM_FLOW_STEP_ID`) to ensure it is run in the appropriate context.
"""
workspace = self.workspace
flow_job_id = os.environ.get("WM_FLOW_JOB_ID")
@@ -662,8 +693,8 @@ class Windmill:
params["flow_step_id"] = os.environ.get("WM_FLOW_STEP_ID")
if default_args_json:
params["default_args_json"] = json.dumps(default_args_json)
if dynamic_enum_json:
params["dynamic_enum_json"] = json.dumps(dynamic_enum_json)
if dynamic_enums_json:
params["dynamic_enums_json"] = json.dumps(dynamic_enums_json)
self.get(
f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}",
@@ -1026,7 +1057,7 @@ def request_interactive_slack_approval(
message: str = None,
approver: str = None,
default_args_json: dict = None,
dynamic_enum_json: dict = None,
dynamic_enums_json: dict = None,
) -> None:
return _client.request_interactive_slack_approval(
slack_resource_path=slack_resource_path,
@@ -1034,7 +1065,7 @@ def request_interactive_slack_approval(
message=message,
approver=approver,
default_args_json=default_args_json,
dynamic_enum_json=dynamic_enum_json,
dynamic_enums_json=dynamic_enums_json,
)
@init_global_client
+38 -5
View File
@@ -853,16 +853,49 @@ interface SlackApprovalOptions {
message?: string;
approver?: string;
defaultArgsJson?: Record<string, any>;
dynamicEnumJson?: Record<string, any>;
dynamicEnumsJson?: Record<string, any>;
}
/**
* Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.
*
* **[Enterprise Edition Only]** To include form fields in the Slack approval request, go to **Advanced -> Suspend -> Form**
* and define a form. Learn more at [Windmill Documentation](https://www.windmill.dev/docs/flows/flow_approval#form).
*
* @param {Object} options - The configuration options for the Slack approval request.
* @param {string} options.slackResourcePath - The path to the Slack resource in Windmill.
* @param {string} options.channelId - The Slack channel ID where the approval request will be sent.
* @param {string} [options.message] - Optional custom message to include in the Slack approval request.
* @param {string} [options.approver] - Optional user ID or name of the approver for the request.
* @param {DefaultArgs} [options.defaultArgsJson] - Optional object defining or overriding the default arguments to a form field.
* @param {Enums} [options.dynamicEnumsJson] - Optional object overriding the enum default values of an enum form field.
*
* @returns {Promise<void>} Resolves when the Slack approval request is successfully sent.
*
* @throws {Error} If the function is not called within a flow or flow preview.
* @throws {Error} If the `JobService.getSlackApprovalPayload` call fails.
*
* **Usage Example:**
* ```typescript
* await requestInteractiveSlackApproval({
* slackResourcePath: "/u/alex/my_slack_resource",
* channelId: "admins-slack-channel",
* message: "Please approve this request",
* approver: "approver123",
* defaultArgsJson: { key1: "value1", key2: 42 },
* dynamicEnumsJson: { foo: ["choice1", "choice2"], bar: ["optionA", "optionB"] },
* });
* ```
*
* **Note:** This function requires execution within a Windmill flow or flow preview.
*/
export async function requestInteractiveSlackApproval({
slackResourcePath,
channelId,
message,
approver,
defaultArgsJson,
dynamicEnumJson,
dynamicEnumsJson,
}: SlackApprovalOptions): Promise<void> {
const workspace = getWorkspace();
const flowJobId = getEnv("WM_FLOW_JOB_ID");
@@ -886,7 +919,7 @@ export async function requestInteractiveSlackApproval({
channelId: string;
flowStepId: string;
defaultArgsJson?: string;
dynamicEnumJson?: string;
dynamicEnumsJson?: string;
} = {
slackResourcePath,
channelId,
@@ -904,8 +937,8 @@ export async function requestInteractiveSlackApproval({
params.defaultArgsJson = JSON.stringify(defaultArgsJson);
}
if (dynamicEnumJson) {
params.dynamicEnumJson = JSON.stringify(dynamicEnumJson);
if (dynamicEnumsJson) {
params.dynamicEnumsJson = JSON.stringify(dynamicEnumsJson);
}
await JobService.getSlackApprovalPayload({