mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-04 08:01:54 +00:00
quicksave
This commit is contained in:
+3575
-217
File diff suppressed because it is too large
Load Diff
+3622
-1173
File diff suppressed because it is too large
Load Diff
@@ -7315,6 +7315,52 @@ paths:
|
||||
"200":
|
||||
description: Interactive slack approval message sent successfully
|
||||
|
||||
/w/{workspace}/jobs/teams_approval/{id}:
|
||||
get:
|
||||
summary: generate interactive teams approval for suspended job
|
||||
operationId: getTeamsApprovalPayload
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/JobId"
|
||||
- name: approver
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: message
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: team_name
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: channel_name
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: flow_step_id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: default_args_json
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: dynamic_enums_json
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Interactive slack approval message sent successfully
|
||||
|
||||
/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}:
|
||||
get:
|
||||
summary: resume a job for a suspended flow
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
use serde::Deserialize;
|
||||
use serde_json::value::RawValue;
|
||||
use sqlx::types::Uuid;
|
||||
use windmill_common::cache;
|
||||
use windmill_common::db::DB;
|
||||
use windmill_common::error::Error;
|
||||
|
||||
use axum::extract::{Path, Query};
|
||||
|
||||
use crate::jobs::{get_resume_urls_internal, ResumeUrls};
|
||||
use windmill_common::{
|
||||
error::{self},
|
||||
jobs::JobKind,
|
||||
scripts::ScriptHash,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ResumeFormRow {
|
||||
pub resume_form: Option<serde_json::Value>,
|
||||
pub hide_cancel: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryApprover {
|
||||
pub approver: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryMessage {
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryResourcePath {
|
||||
pub slack_resource_path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryChannelId {
|
||||
pub channel_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryFlowStepId {
|
||||
pub flow_step_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct QueryDefaultArgsJson {
|
||||
pub default_args_json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct QueryDynamicEnumJson {
|
||||
pub dynamic_enums_json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ApprovalFormDetails {
|
||||
pub message_str: String,
|
||||
pub urls: ResumeUrls,
|
||||
pub schema: Option<ResumeFormRow>,
|
||||
}
|
||||
|
||||
pub async fn get_approval_form(
|
||||
db: DB,
|
||||
w_id: &str,
|
||||
job_id: Uuid,
|
||||
flow_step_id: Option<&str>,
|
||||
resume_id: u32,
|
||||
approver: Option<&str>,
|
||||
message: Option<&str>,
|
||||
) -> Result<ApprovalFormDetails, Error> {
|
||||
let res = get_resume_urls_internal(
|
||||
axum::Extension(db.clone()),
|
||||
Path((w_id.to_string(), job_id, resume_id)),
|
||||
Query(QueryApprover { approver: approver.map(|a| a.to_string()) }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let urls = res.0;
|
||||
|
||||
tracing::debug!("Job ID: {:?}", job_id);
|
||||
|
||||
let (job_kind, script_hash, raw_flow, parent_job_id, created_at, created_by, script_path, args) = sqlx::query!(
|
||||
"SELECT
|
||||
v2_as_queue.job_kind AS \"job_kind!: JobKind\",
|
||||
v2_as_queue.script_hash AS \"script_hash: ScriptHash\",
|
||||
v2_as_queue.raw_flow AS \"raw_flow: sqlx::types::Json<Box<RawValue>>\",
|
||||
v2_as_completed_job.parent_job AS \"parent_job: Uuid\",
|
||||
v2_as_completed_job.created_at AS \"created_at!: chrono::NaiveDateTime\",
|
||||
v2_as_completed_job.created_by AS \"created_by!\",
|
||||
v2_as_queue.script_path,
|
||||
v2_as_queue.args AS \"args: sqlx::types::Json<Box<RawValue>>\"
|
||||
FROM v2_as_queue
|
||||
JOIN v2_as_completed_job ON v2_as_completed_job.parent_job = v2_as_queue.id
|
||||
WHERE v2_as_completed_job.id = $1 AND v2_as_completed_job.workspace_id = $2
|
||||
LIMIT 1",
|
||||
job_id,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await
|
||||
.map_err(|e| error::Error::BadRequest(e.to_string()))?
|
||||
.ok_or_else(|| error::Error::BadRequest("This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string()))
|
||||
.map(|r| (r.job_kind, r.script_hash, r.raw_flow, r.parent_job, r.created_at, r.created_by, r.script_path, r.args))?;
|
||||
|
||||
let flow_data = match cache::job::fetch_flow(&db, job_kind, script_hash).await {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
if let Some(parent_job_id) = parent_job_id.as_ref() {
|
||||
cache::job::fetch_preview_flow(&db, parent_job_id, raw_flow).await?
|
||||
} else {
|
||||
return Err(error::Error::BadRequest(
|
||||
"This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let flow_value = &flow_data.flow;
|
||||
let flow_step_id = flow_step_id.unwrap_or("");
|
||||
let module = flow_value.modules.iter().find(|m| m.id == flow_step_id);
|
||||
|
||||
tracing::debug!("Module: {:#?}", module);
|
||||
|
||||
let schema = module.and_then(|module| {
|
||||
module.suspend.as_ref().map(|suspend| ResumeFormRow {
|
||||
resume_form: suspend.resume_form.clone(),
|
||||
hide_cancel: suspend.hide_cancel,
|
||||
})
|
||||
});
|
||||
|
||||
let args_str = args.map_or("None".to_string(), |a| a.get().to_string());
|
||||
let parent_job_id_str = parent_job_id.map_or("None".to_string(), |id| id.to_string());
|
||||
let script_path_str = script_path.as_deref().unwrap_or("None");
|
||||
|
||||
let created_at_formatted = created_at.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
let mut message_str = format!(
|
||||
"A workflow has been suspended and is waiting for approval:\n\n\
|
||||
*Created by*: {created_by}\n\
|
||||
*Created at*: {created_at_formatted}\n\
|
||||
*Script path*: {script_path_str}\n\
|
||||
*Args*: {args_str}\n\
|
||||
*Flow ID*: {parent_job_id_str}\n\n"
|
||||
);
|
||||
|
||||
// Append custom message if provided
|
||||
if let Some(msg) = message {
|
||||
message_str.push_str(msg);
|
||||
}
|
||||
|
||||
tracing::debug!("Schema: {:#?}", schema);
|
||||
|
||||
Ok(ApprovalFormDetails { message_str, urls, schema })
|
||||
}
|
||||
@@ -80,6 +80,8 @@ use windmill_common::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::approvals::QueryApprover;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS;
|
||||
#[cfg(feature = "prometheus")]
|
||||
@@ -2125,11 +2127,6 @@ pub struct SuspendedJobFlow {
|
||||
pub approvers: Vec<Approval>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct QueryApprover {
|
||||
pub approver: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_suspended_job_flow(
|
||||
authed: Option<ApiAuthed>,
|
||||
Extension(db): Extension<DB>,
|
||||
|
||||
@@ -55,6 +55,8 @@ use windmill_common::{utils::GIT_VERSION, BASE_URL, INSTANCE_NAME};
|
||||
use crate::scim_ee::has_scim_token;
|
||||
use windmill_common::error::AppError;
|
||||
|
||||
use crate::teams_approvals::request_teams_approval;
|
||||
|
||||
mod ai;
|
||||
mod apps;
|
||||
mod args;
|
||||
@@ -101,7 +103,9 @@ mod scim_ee;
|
||||
mod scripts;
|
||||
mod service_logs;
|
||||
mod settings;
|
||||
mod approvals;
|
||||
mod slack_approvals;
|
||||
mod teams_approvals;
|
||||
#[cfg(feature = "smtp")]
|
||||
mod smtp_server_ee;
|
||||
mod static_assets;
|
||||
@@ -494,6 +498,10 @@ pub async fn run_server(
|
||||
"/w/:workspace_id/jobs/slack_approval/:job_id",
|
||||
get(slack_approvals::request_slack_approval),
|
||||
)
|
||||
.route(
|
||||
"/w/:workspace_id/jobs/teams_approval/:job_id",
|
||||
get(request_teams_approval),
|
||||
)
|
||||
.nest(
|
||||
"/w/:workspace_id/resources_u",
|
||||
resources::public_service().layer(cors.clone()),
|
||||
|
||||
@@ -4,7 +4,7 @@ use axum::{
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::{RawValue, Value};
|
||||
use serde_json::value::Value;
|
||||
|
||||
use sqlx::types::Uuid;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
@@ -13,19 +13,18 @@ use regex::Regex;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
use crate::jobs::{
|
||||
cancel_suspended_job, get_resume_urls_internal, resume_suspended_job, QueryApprover,
|
||||
QueryOrBody, ResumeUrls,
|
||||
};
|
||||
use crate::jobs::{cancel_suspended_job, resume_suspended_job, QueryOrBody, ResumeUrls};
|
||||
|
||||
use windmill_common::{
|
||||
cache,
|
||||
error::{self, Error},
|
||||
jobs::JobKind,
|
||||
scripts::ScriptHash,
|
||||
error::Error,
|
||||
variables::{build_crypt, decrypt},
|
||||
};
|
||||
|
||||
use crate::approvals::{
|
||||
get_approval_form, ApprovalFormDetails, QueryApprover, QueryChannelId, QueryDefaultArgsJson,
|
||||
QueryDynamicEnumJson, QueryFlowStepId, QueryMessage, QueryResourcePath,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SlackFormData {
|
||||
payload: String,
|
||||
@@ -96,12 +95,6 @@ struct ResumeSchema {
|
||||
schema: Schema,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ResumeFormRow {
|
||||
resume_form: Option<serde_json::Value>,
|
||||
hide_cancel: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Schema {
|
||||
order: Vec<String>,
|
||||
@@ -133,36 +126,6 @@ struct ResumeFormField {
|
||||
nullable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryMessage {
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryResourcePath {
|
||||
slack_resource_path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryChannelId {
|
||||
channel_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryFlowStepId {
|
||||
flow_step_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct QueryDefaultArgsJson {
|
||||
default_args_json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct QueryDynamicEnumJson {
|
||||
dynamic_enums_json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ModalActionValue {
|
||||
w_id: String,
|
||||
@@ -964,92 +927,17 @@ async fn get_modal_blocks(
|
||||
default_args_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()),
|
||||
Path((w_id.to_string(), job_id, resume_id)),
|
||||
Query(QueryApprover { approver: approver.map(|a| a.to_string()) }),
|
||||
)
|
||||
.await?;
|
||||
let approval_details =
|
||||
get_approval_form(db, w_id, job_id, flow_step_id, resume_id, approver, message).await?;
|
||||
|
||||
let urls = res.0;
|
||||
let ApprovalFormDetails { mut message_str, urls, schema } = approval_details;
|
||||
|
||||
tracing::debug!("Job ID: {:?}", job_id);
|
||||
|
||||
let (job_kind, script_hash, raw_flow, parent_job_id, created_at, created_by, script_path, args) = sqlx::query!(
|
||||
"SELECT
|
||||
v2_as_queue.job_kind AS \"job_kind!: JobKind\",
|
||||
v2_as_queue.script_hash AS \"script_hash: ScriptHash\",
|
||||
v2_as_queue.raw_flow AS \"raw_flow: sqlx::types::Json<Box<RawValue>>\",
|
||||
v2_as_completed_job.parent_job AS \"parent_job: Uuid\",
|
||||
v2_as_completed_job.created_at AS \"created_at!: chrono::NaiveDateTime\",
|
||||
v2_as_completed_job.created_by AS \"created_by!\",
|
||||
v2_as_queue.script_path,
|
||||
v2_as_queue.args AS \"args: sqlx::types::Json<Box<RawValue>>\"
|
||||
FROM v2_as_queue
|
||||
JOIN v2_as_completed_job ON v2_as_completed_job.parent_job = v2_as_queue.id
|
||||
WHERE v2_as_completed_job.id = $1 AND v2_as_completed_job.workspace_id = $2
|
||||
LIMIT 1",
|
||||
job_id,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await
|
||||
.map_err(|e| error::Error::BadRequest(e.to_string()))?
|
||||
.ok_or_else(|| error::Error::BadRequest("This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string()))
|
||||
.map(|r| (r.job_kind, r.script_hash, r.raw_flow, r.parent_job, r.created_at, r.created_by, r.script_path, r.args))?;
|
||||
|
||||
let flow_data = match cache::job::fetch_flow(&db, job_kind, script_hash).await {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
if let Some(parent_job_id) = parent_job_id.as_ref() {
|
||||
cache::job::fetch_preview_flow(&db, parent_job_id, raw_flow).await?
|
||||
} else {
|
||||
return Err(error::Error::BadRequest(
|
||||
"This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let flow_value = &flow_data.flow;
|
||||
let flow_step_id = flow_step_id.unwrap_or("");
|
||||
let module = flow_value.modules.iter().find(|m| m.id == flow_step_id);
|
||||
|
||||
tracing::debug!("Module: {:#?}", module);
|
||||
|
||||
let schema = module.and_then(|module| {
|
||||
module.suspend.as_ref().map(|suspend| ResumeFormRow {
|
||||
resume_form: suspend.resume_form.clone(),
|
||||
hide_cancel: suspend.hide_cancel,
|
||||
})
|
||||
});
|
||||
|
||||
let args_str = args.map_or("None".to_string(), |a| a.get().to_string());
|
||||
let parent_job_id_str = parent_job_id.map_or("None".to_string(), |id| id.to_string());
|
||||
let script_path_str = script_path.as_deref().unwrap_or("None");
|
||||
|
||||
let created_at_formatted = created_at.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
let mut message_str = format!(
|
||||
"A workflow has been suspended and is waiting for approval:\n\n\
|
||||
*Created by*: {created_by}\n\
|
||||
*Created at*: {created_at_formatted}\n\
|
||||
*Script path*: {script_path_str}\n\
|
||||
*Args*: {args_str}\n\
|
||||
*Flow ID*: {parent_job_id_str}\n\n"
|
||||
);
|
||||
|
||||
// Append custom message if provided
|
||||
if let Some(msg) = message {
|
||||
message_str.push_str(msg);
|
||||
}
|
||||
|
||||
tracing::debug!("Schema: {:#?}", schema);
|
||||
// tracing::debug!("Approval Details: {:?}", approval_details);
|
||||
|
||||
if let Some(resume_schema) = schema {
|
||||
let hide_cancel = resume_schema.hide_cancel.unwrap_or(false);
|
||||
|
||||
// if hide cancel is false add note to message
|
||||
// If hide_cancel is false, add a note to the message
|
||||
if !hide_cancel {
|
||||
message_str.push_str("\n\n*NOTE*: closing this modal will cancel the workflow.\n\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::approvals::{
|
||||
get_approval_form, ApprovalFormDetails, QueryApprover, QueryDefaultArgsJson, QueryDynamicEnumJson, QueryFlowStepId, QueryMessage, ResumeFormRow,
|
||||
};
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
Extension,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::error::Error;
|
||||
use windmill_common::teams_ee::get_global_teams_bot_token;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RequestTeamsApprovalPayload {
|
||||
pub team_name: String,
|
||||
pub channel_name: String,
|
||||
pub message: String,
|
||||
pub approver: String,
|
||||
pub default_args_json: Option<serde_json::Value>,
|
||||
pub dynamic_enums_json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryTeamName {
|
||||
pub team_name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryChannelName {
|
||||
pub channel_name: String,
|
||||
}
|
||||
|
||||
pub async fn request_teams_approval(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, job_id)): Path<(String, Uuid)>,
|
||||
Query(team_name): Query<QueryTeamName>,
|
||||
Query(channel_name): Query<QueryChannelName>,
|
||||
Query(approver): Query<QueryApprover>,
|
||||
Query(message): Query<QueryMessage>,
|
||||
Query(flow_step_id): Query<QueryFlowStepId>,
|
||||
Query(default_args_json): Query<QueryDefaultArgsJson>,
|
||||
Query(dynamic_enums_json): Query<QueryDynamicEnumJson>,
|
||||
) -> Result<StatusCode, Error> {
|
||||
let teams_bot_token = get_global_teams_bot_token(&db).await?;
|
||||
|
||||
let resume_id = rand::random::<u32>();
|
||||
|
||||
let approval_details = get_approval_form(
|
||||
db,
|
||||
w_id.as_str(),
|
||||
job_id,
|
||||
Some(flow_step_id.flow_step_id.as_str()),
|
||||
resume_id,
|
||||
approver.approver.as_deref(),
|
||||
message.message.as_deref(),
|
||||
).await?;
|
||||
|
||||
let ApprovalFormDetails { mut message_str, urls, schema } = approval_details;
|
||||
|
||||
let blocks = transform_schemas(
|
||||
message_str,
|
||||
schema,
|
||||
);
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
fn transform_schemas(message_str: String, schema: Option<ResumeFormRow>) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
@@ -248,7 +248,7 @@ declare function setValue(id: string, value: any): void;
|
||||
*/
|
||||
declare function setSelectedIndex(id: string, index: number): void;
|
||||
|
||||
/** close a drawer or modal
|
||||
/** open a drawer or modal
|
||||
* @param id component's id
|
||||
*/
|
||||
declare function open(id: string): void;
|
||||
|
||||
@@ -14,5 +14,5 @@ cp "${script_dirpath}/s3Types.ts" "${script_dirpath}/src/"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval} from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, requestInteractiveTeamsApproval} from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
|
||||
|
||||
@@ -39,4 +39,4 @@ cp "${script_dirpath}/s3Types.ts" "${script_dirpath}/src/"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval, requestInteractiveTeamsApproval } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
|
||||
@@ -892,6 +892,15 @@ interface SlackApprovalOptions {
|
||||
dynamicEnumsJson?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface TeamsApprovalOptions {
|
||||
teamName: string;
|
||||
channelName: string;
|
||||
message?: string;
|
||||
approver?: string;
|
||||
defaultArgsJson?: Record<string, any>;
|
||||
dynamicEnumsJson?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an interactive approval request via Slack, allowing optional customization of the message, approver, and form fields.
|
||||
*
|
||||
@@ -984,6 +993,98 @@ export async function requestInteractiveSlackApproval({
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an interactive approval request via Teams, allowing optional customization of the message, approver, and form fields.
|
||||
*
|
||||
* **[Enterprise Edition Only]** To include form fields in the Teams 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 Teams approval request.
|
||||
* @param {string} options.teamName - The Teams team name where the approval request will be sent.
|
||||
* @param {string} options.channelName - The Teams channel name where the approval request will be sent.
|
||||
* @param {string} [options.message] - Optional custom message to include in the Teams 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 Teams approval request is successfully sent.
|
||||
*
|
||||
* @throws {Error} If the function is not called within a flow or flow preview.
|
||||
* @throws {Error} If the `JobService.getTeamsApprovalPayload` call fails.
|
||||
*
|
||||
* **Usage Example:**
|
||||
* ```typescript
|
||||
* await requestInteractiveTeamsApproval({
|
||||
* teamName: "admins-teams",
|
||||
* channelName: "admins-teams-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 requestInteractiveTeamsApproval({
|
||||
teamName,
|
||||
channelName,
|
||||
message,
|
||||
approver,
|
||||
defaultArgsJson,
|
||||
dynamicEnumsJson,
|
||||
}: TeamsApprovalOptions): Promise<void> {
|
||||
const workspace = getWorkspace();
|
||||
const flowJobId = getEnv("WM_FLOW_JOB_ID");
|
||||
|
||||
if (!flowJobId) {
|
||||
throw new Error(
|
||||
"You can't use this function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
|
||||
);
|
||||
}
|
||||
|
||||
const flowStepId = getEnv("WM_FLOW_STEP_ID");
|
||||
if (!flowStepId) {
|
||||
throw new Error("This function can only be called as a flow step");
|
||||
}
|
||||
|
||||
// Only include non-empty parameters
|
||||
const params: {
|
||||
approver?: string;
|
||||
message?: string;
|
||||
teamName: string;
|
||||
channelName: string;
|
||||
flowStepId: string;
|
||||
defaultArgsJson?: string;
|
||||
dynamicEnumsJson?: string;
|
||||
} = {
|
||||
teamName,
|
||||
channelName,
|
||||
flowStepId,
|
||||
};
|
||||
|
||||
if (message) {
|
||||
params.message = message;
|
||||
}
|
||||
if (approver) {
|
||||
params.approver = approver;
|
||||
}
|
||||
|
||||
if (defaultArgsJson) {
|
||||
params.defaultArgsJson = JSON.stringify(defaultArgsJson);
|
||||
}
|
||||
|
||||
if (dynamicEnumsJson) {
|
||||
params.dynamicEnumsJson = JSON.stringify(dynamicEnumsJson);
|
||||
}
|
||||
|
||||
await JobService.getTeamsApprovalPayload({
|
||||
workspace,
|
||||
...params,
|
||||
id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
|
||||
});
|
||||
}
|
||||
|
||||
async function getMockedApi(): Promise<MockedApi | undefined> {
|
||||
if (mockedApi) {
|
||||
return mockedApi;
|
||||
|
||||
Reference in New Issue
Block a user