Compare commits
9 Commits
wmill-scri
...
alp/update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9d194894d | ||
|
|
503930ff56 | ||
|
|
3529625774 | ||
|
|
045307ab41 | ||
|
|
e437764f5e | ||
|
|
6b3881bd00 | ||
|
|
410fb25309 | ||
|
|
804fd3468c | ||
|
|
e27a97dfbb |
@@ -6937,6 +6937,43 @@ paths:
|
|||||||
- resume
|
- resume
|
||||||
- cancel
|
- cancel
|
||||||
|
|
||||||
|
/w/{workspace}/jobs/slack_approval/{id}/{resume_id}:
|
||||||
|
get:
|
||||||
|
summary: get interactive slack approval payload given the job_id, resume_id and a nonce to resume a flow
|
||||||
|
operationId: getSlackApprovalPayload
|
||||||
|
tags:
|
||||||
|
- job
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/WorkspaceId"
|
||||||
|
- $ref: "#/components/parameters/JobId"
|
||||||
|
- name: resume_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: approver
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: message
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Blocks array for posting interactive slack approval
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
blocks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- blocks
|
||||||
|
|
||||||
/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}:
|
/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}:
|
||||||
get:
|
get:
|
||||||
summary: resume a job for a suspended flow
|
summary: resume a job for a suspended flow
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use serde_json::value::RawValue;
|
|||||||
use sqlx::Pool;
|
use sqlx::Pool;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::str::FromStr;
|
||||||
#[cfg(feature = "prometheus")]
|
#[cfg(feature = "prometheus")]
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
@@ -2316,7 +2317,7 @@ fn create_signature(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct ResumeUrls {
|
pub struct ResumeUrls {
|
||||||
approvalPage: String,
|
approvalPage: String,
|
||||||
cancel: String,
|
cancel: String,
|
||||||
@@ -5507,3 +5508,553 @@ async fn delete_completed_job<'a>(
|
|||||||
let response = Json(cj).into_response();
|
let response = Json(cj).into_response();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use axum::extract::Form;
|
||||||
|
use regex::Regex;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct SlackFormData {
|
||||||
|
payload: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Payload {
|
||||||
|
actions: Vec<Action>,
|
||||||
|
state: State,
|
||||||
|
response_url: Option<String>,
|
||||||
|
message: Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Message {
|
||||||
|
blocks: Option<Vec<Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Action {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct State {
|
||||||
|
values: HashMap<String, HashMap<String, ValueInput>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
|
enum ValueInput {
|
||||||
|
PlainTextInput { value: Option<Value> },
|
||||||
|
Datepicker { selected_date: Option<String> },
|
||||||
|
Timepicker { selected_time: Option<String> },
|
||||||
|
StaticSelect { selected_option: Option<SelectedOption> },
|
||||||
|
RadioButtons { selected_option: Option<SelectedOption> },
|
||||||
|
Checkboxes { selected_options: Option<Vec<SelectedOption>> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct SelectedOption {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn slack_app_callback_handler(
|
||||||
|
authed: Option<ApiAuthed>,
|
||||||
|
Extension(db): Extension<DB>,
|
||||||
|
Form(form_data): Form<SlackFormData>,
|
||||||
|
) -> error::Result<StatusCode> {
|
||||||
|
tracing::debug!("Form data: {:?}", form_data);
|
||||||
|
let payload: Payload = serde_json::from_str(&form_data.payload)?;
|
||||||
|
|
||||||
|
let action_value = payload.actions[0].value.clone();
|
||||||
|
let response_url = payload.response_url;
|
||||||
|
|
||||||
|
let re = Regex::new(r"/api/w/(?P<w_id>[^/]+)/jobs_u/(?P<action>resume|cancel)/(?P<job_id>[^/]+)/(?P<resume_id>[^/]+)/(?P<secret>[a-fA-F0-9]+)(?:\?approver=(?P<approver>[^&]+))?").unwrap();
|
||||||
|
|
||||||
|
if let Some(captures) = re.captures(&action_value) {
|
||||||
|
let w_id = captures.name("w_id").map_or("", |m| m.as_str());
|
||||||
|
let action = captures.name("action").map_or("", |m| m.as_str());
|
||||||
|
let job_id = captures.name("job_id").map_or("", |m| m.as_str());
|
||||||
|
let resume_id = captures.name("resume_id").map_or("", |m| m.as_str());
|
||||||
|
let secret = captures.name("secret").map_or("", |m| m.as_str());
|
||||||
|
let approver =
|
||||||
|
QueryApprover { approver: captures.name("approver").map(|m| m.as_str().to_string()) };
|
||||||
|
|
||||||
|
tracing::debug!("Request: {:?}", &form_data.payload.clone());
|
||||||
|
|
||||||
|
let state_values: HashMap<String, serde_json::Value> = 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().into()))
|
||||||
|
}
|
||||||
|
ValueInput::StaticSelect { selected_option } => selected_option
|
||||||
|
.as_ref()
|
||||||
|
.map(|so| (action_id.clone(), serde_json::json!(so.value))),
|
||||||
|
ValueInput::RadioButtons { selected_option } => selected_option
|
||||||
|
.as_ref()
|
||||||
|
.map(|so| (action_id.clone(), serde_json::json!(so.value))),
|
||||||
|
ValueInput::Checkboxes { selected_options } => {
|
||||||
|
selected_options.as_ref().map(|so| {
|
||||||
|
(
|
||||||
|
action_id.clone(),
|
||||||
|
serde_json::json!(so
|
||||||
|
.iter()
|
||||||
|
.map(|option| option.value.clone())
|
||||||
|
.collect::<Vec<_>>()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
tracing::debug!("Job ID: {}", job_id);
|
||||||
|
tracing::debug!("Resume ID: {}", resume_id);
|
||||||
|
tracing::debug!("Secret: {}", secret);
|
||||||
|
tracing::debug!("Approver: {:?}", approver.approver);
|
||||||
|
tracing::debug!("State JSON: {:?}", state_json);
|
||||||
|
|
||||||
|
let res = resume_suspended_job_internal(
|
||||||
|
Some(state_json),
|
||||||
|
db,
|
||||||
|
w_id.to_string(),
|
||||||
|
Uuid::from_str(job_id).unwrap_or_default(),
|
||||||
|
resume_id.parse::<u32>().unwrap_or_default(),
|
||||||
|
approver,
|
||||||
|
secret.to_string(),
|
||||||
|
authed,
|
||||||
|
action == "resume",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
tracing::debug!("Res: {:?}", res);
|
||||||
|
|
||||||
|
if let Some(url) = response_url {
|
||||||
|
let message = if action == "resume" {
|
||||||
|
"\n\n*Workflow has been resumed!*"
|
||||||
|
} else {
|
||||||
|
"\n\n*Workflow has been canceled!*"
|
||||||
|
};
|
||||||
|
let _ = post_slack_response(&url, message).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::error!("Resume URL does not match the pattern.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct QueryMessage {
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn request_slack_approval(
|
||||||
|
authed: ApiAuthed,
|
||||||
|
Extension(db): Extension<DB>,
|
||||||
|
Path((w_id, job_id, resume_id)): Path<(String, Uuid, u32)>,
|
||||||
|
Query(approver): Query<QueryApprover>,
|
||||||
|
Query(message): Query<QueryMessage>,
|
||||||
|
) -> windmill_common::error::JsonResult<serde_json::Value> {
|
||||||
|
|
||||||
|
let res = get_resume_urls(
|
||||||
|
authed,
|
||||||
|
axum::Extension(db.clone()),
|
||||||
|
Path((w_id, job_id, resume_id)),
|
||||||
|
axum::extract::Query(approver),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let schema: Option<ResumeFormRow> = sqlx::query_as!(
|
||||||
|
ResumeFormRow,
|
||||||
|
"SELECT
|
||||||
|
module.value->'suspend'->'resume_form' AS resume_form,
|
||||||
|
(module.value->'suspend'->>'hide_cancel')::boolean AS hide_cancel
|
||||||
|
FROM
|
||||||
|
job
|
||||||
|
LEFT JOIN
|
||||||
|
queue ON job.id = queue.parent_job
|
||||||
|
LEFT JOIN
|
||||||
|
jsonb_array_elements(job.raw_flow->'modules') AS module
|
||||||
|
ON module->>'id' = queue.flow_step_id
|
||||||
|
WHERE
|
||||||
|
queue.id = $1",
|
||||||
|
job_id
|
||||||
|
)
|
||||||
|
.fetch_optional(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!("schema: {:?}", schema);
|
||||||
|
tracing::debug!("job_id: {:?}", job_id);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let schema_obj = match resume_schema.resume_form {
|
||||||
|
Some(schema) => schema,
|
||||||
|
None => {
|
||||||
|
tracing::debug!("No suspend form found!");
|
||||||
|
return transform_schemas(message_str, None, &res.unwrap().0, None, hide_cancel)
|
||||||
|
.await
|
||||||
|
.map(Json);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let inner_schema = schema_obj
|
||||||
|
.get("schema")
|
||||||
|
.ok_or_else(|| Error::BadRequest("Schema object is missing the 'schema' field!".to_string()))?;
|
||||||
|
|
||||||
|
let order_value = inner_schema
|
||||||
|
.get("order")
|
||||||
|
.ok_or_else(|| Error::BadRequest("Schema does not contain order field!".to_string()))?;
|
||||||
|
|
||||||
|
let order: Vec<String> = serde_json::from_value(order_value.clone())
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to deserialize order: {:?}", e);
|
||||||
|
Error::BadRequest("Failed to deserialize order!".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let properties_value = inner_schema
|
||||||
|
.get("properties")
|
||||||
|
.ok_or_else(|| Error::BadRequest("Schema does not contain properties field!".to_string()))?;
|
||||||
|
let properties: HashMap<String, ResumeFormField> = serde_json::from_value(properties_value.clone())
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Deserialization failed: {:?}", e);
|
||||||
|
Error::BadRequest("Failed to deserialize properties!".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let blocks = transform_schemas(message_str, Some(&properties), &res.unwrap().0, Some(&order), hide_cancel)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(blocks))
|
||||||
|
} else {
|
||||||
|
Err(Error::BadRequest(
|
||||||
|
"Could not generate interactive Slack message!".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_slack_response(
|
||||||
|
response_url: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut final_blocks = vec![serde_json::json!({
|
||||||
|
"type": "section",
|
||||||
|
"text": {
|
||||||
|
"type": "mrkdwn",
|
||||||
|
"text": message
|
||||||
|
}
|
||||||
|
})];
|
||||||
|
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"replace_original": "true",
|
||||||
|
"text": message,
|
||||||
|
"blocks": final_blocks
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(response_url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&payload)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
tracing::debug!("Slack response to approval sent successfully!");
|
||||||
|
} else {
|
||||||
|
tracing::error!(
|
||||||
|
"Slack response to approval failed. Status: {}",
|
||||||
|
response.status()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ResumeSchema {
|
||||||
|
pub schema: Schema,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ResumeFormRow {
|
||||||
|
pub resume_form: Option<serde_json::Value>,
|
||||||
|
pub hide_cancel: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Schema {
|
||||||
|
pub order: Vec<String>,
|
||||||
|
pub required: Vec<String>,
|
||||||
|
pub properties: HashMap<String, ResumeFormField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ResumeFormField {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub r#type: String,
|
||||||
|
pub format: Option<String>,
|
||||||
|
pub default: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
#[serde(rename = "enum")]
|
||||||
|
pub r#enum: Option<Vec<String>>,
|
||||||
|
#[serde(rename = "enumLabels")]
|
||||||
|
pub enum_labels: Option<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transform_schemas(
|
||||||
|
text: &str,
|
||||||
|
properties: Option<&HashMap<String, ResumeFormField>>,
|
||||||
|
urls: &ResumeUrls,
|
||||||
|
order: Option<&Vec<String>>,
|
||||||
|
hide_cancel: bool,
|
||||||
|
) -> Result<serde_json::Value, Error> {
|
||||||
|
tracing::debug!("{:?}", urls);
|
||||||
|
|
||||||
|
let mut blocks = vec![serde_json::json!({
|
||||||
|
"type": "section",
|
||||||
|
"text": {
|
||||||
|
"type": "mrkdwn",
|
||||||
|
"text": format!("{}\n<{}|Flow suspension details>", text, urls.approvalPage),
|
||||||
|
}
|
||||||
|
})];
|
||||||
|
|
||||||
|
if let Some(properties) = properties {
|
||||||
|
if let Some(order) = order {
|
||||||
|
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 {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.push(create_action_buttons(urls, hide_cancel));
|
||||||
|
|
||||||
|
Ok(serde_json::Value::Array(blocks))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_input_block(key: &str, schema: &ResumeFormField) -> serde_json::Value {
|
||||||
|
let placeholder = schema
|
||||||
|
.description
|
||||||
|
.as_deref()
|
||||||
|
.filter(|desc| !desc.is_empty())
|
||||||
|
.unwrap_or("Select an option");
|
||||||
|
|
||||||
|
// 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": "Select a date",
|
||||||
|
"emoji": true
|
||||||
|
},
|
||||||
|
"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::<Vec<_>>(),
|
||||||
|
"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),
|
||||||
|
"emoji": true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Handle other types
|
||||||
|
serde_json::json!({
|
||||||
|
"type": "input",
|
||||||
|
"element": {
|
||||||
|
"type": "plain_text_input",
|
||||||
|
"action_id": key,
|
||||||
|
"initial_value": schema.default.as_deref().unwrap_or("")
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"type": "plain_text",
|
||||||
|
"text": schema.title.as_deref().unwrap_or(key),
|
||||||
|
"emoji": true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_action_buttons(urls: &ResumeUrls, hide_cancel: bool) -> serde_json::Value {
|
||||||
|
let mut elements = vec![
|
||||||
|
serde_json::json!({
|
||||||
|
"type": "button",
|
||||||
|
"text": {
|
||||||
|
"type": "plain_text",
|
||||||
|
"text": "Continue"
|
||||||
|
},
|
||||||
|
"style": "primary",
|
||||||
|
"action_id": "resume_action",
|
||||||
|
"value": urls.resume
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
if !hide_cancel {
|
||||||
|
elements.push(serde_json::json!({
|
||||||
|
"type": "button",
|
||||||
|
"text": {
|
||||||
|
"type": "plain_text",
|
||||||
|
"text": "Abort"
|
||||||
|
},
|
||||||
|
"style": "danger",
|
||||||
|
"action_id": "cancel_action",
|
||||||
|
"value": urls.cancel
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::json!({
|
||||||
|
"type": "actions",
|
||||||
|
"elements": elements
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use crate::{
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use axum::extract::DefaultBodyLimit;
|
use axum::extract::DefaultBodyLimit;
|
||||||
use axum::{middleware::from_extractor, routing::get, Extension, Router};
|
use axum::{middleware::from_extractor, routing::get, routing::post, Extension, Router};
|
||||||
use db::DB;
|
use db::DB;
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
@@ -367,6 +367,8 @@ pub async fn run_server(
|
|||||||
"/w/:workspace_id/jobs_u",
|
"/w/:workspace_id/jobs_u",
|
||||||
jobs::workspace_unauthed_service().layer(cors.clone()),
|
jobs::workspace_unauthed_service().layer(cors.clone()),
|
||||||
)
|
)
|
||||||
|
.route("/slack", post(jobs::slack_app_callback_handler))
|
||||||
|
.route("/w/:workspace_id/jobs/slack_approval/:job_id/:resume_id", get(jobs::request_slack_approval))
|
||||||
.nest(
|
.nest(
|
||||||
"/w/:workspace_id/resources_u",
|
"/w/:workspace_id/resources_u",
|
||||||
resources::public_service().layer(cors.clone()),
|
resources::public_service().layer(cors.clone()),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"gitSync": "hub/9087/sync-script-to-git-repo-windmill",
|
"gitSync_0": "hub/9087/sync-script-to-git-repo-windmill",
|
||||||
|
"gitSync": "hub/9987/sync-script-to-git-repo-windmill",
|
||||||
"gitSyncTest": "hub/9073/git-repo-test-read-write-windmill",
|
"gitSyncTest": "hub/9073/git-repo-test-read-write-windmill",
|
||||||
"slackErrorHandler": "hub/9206/workspace-or-schedule-error-handler-slack",
|
"slackErrorHandler": "hub/9206/workspace-or-schedule-error-handler-slack",
|
||||||
"slackErrorHandler_0": "hub/9079/workspace-or-schedule-error-handler-slack",
|
"slackErrorHandler_0": "hub/9079/workspace-or-schedule-error-handler-slack",
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ cp ../backend/windmill-api/openapi.yaml openapi/openapi.yaml
|
|||||||
|
|
||||||
npx @redocly/openapi-cli@latest bundle openapi/openapi.yaml > openapi-bundled.yaml
|
npx @redocly/openapi-cli@latest bundle openapi/openapi.yaml > openapi-bundled.yaml
|
||||||
|
|
||||||
sed -z 's/FlowModuleValue:/FlowModuleValue2:/' openapi-bundled.yaml > openapi-decycled.yaml
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# sed -z is not supported on macOS, use perl instead
|
||||||
|
perl -0777 -pe 's/FlowModuleValue:/FlowModuleValue2:/g' openapi-bundled.yaml > openapi-decycled.yaml
|
||||||
|
else
|
||||||
|
sed -z 's/FlowModuleValue:/FlowModuleValue2:/' openapi-bundled.yaml > openapi-decycled.yaml
|
||||||
|
fi
|
||||||
|
|
||||||
echo " FlowModuleValue: {}" >> openapi-decycled.yaml
|
echo " FlowModuleValue: {}" >> openapi-decycled.yaml
|
||||||
npx @redocly/openapi-cli@latest bundle openapi-decycled.yaml --ext json -d > openapi-deref.json
|
npx @redocly/openapi-cli@latest bundle openapi-decycled.yaml --ext json -d > openapi-deref.json
|
||||||
|
|
||||||
@@ -20,9 +26,19 @@ rm -rf openapi/
|
|||||||
rm openapi*
|
rm openapi*
|
||||||
|
|
||||||
cp LICENSE windmill-api/
|
cp LICENSE windmill-api/
|
||||||
sed -i '5 i license = "Apache-2.0"' windmill-api/pyproject.toml
|
|
||||||
|
|
||||||
sed -i 's/authors = \[\]/authors = \["Ruben Fiszel <ruben@windmill.dev>"\]/g' windmill-api/pyproject.toml
|
# Check if running on macOS
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS version
|
||||||
|
sed -i '' '5 i\
|
||||||
|
license = "Apache-2.0"' windmill-api/pyproject.toml
|
||||||
|
sed -i '' 's/authors = \[\]/\nauthors = \["Ruben Fiszel <ruben@windmill.dev>"\]/g' windmill-api/pyproject.toml
|
||||||
|
else
|
||||||
|
# Linux version
|
||||||
|
sed -i '5 i license = "Apache-2.0"' windmill-api/pyproject.toml
|
||||||
|
|
||||||
|
sed -i 's/authors = \[\]/authors = \["Ruben Fiszel <ruben@windmill.dev>"\]/g' windmill-api/pyproject.toml
|
||||||
|
fi
|
||||||
|
|
||||||
echo "# Autogenerated Windmill OpenApi Client" >> windmill-api/README.md.tmp
|
echo "# Autogenerated Windmill OpenApi Client" >> windmill-api/README.md.tmp
|
||||||
echo "This is the raw autogenerated api client. You are most likely more interested \
|
echo "This is the raw autogenerated api client. You are most likely more interested \
|
||||||
@@ -33,8 +49,7 @@ user friendly experience. We use \
|
|||||||
|
|
||||||
echo "" >> windmill-api/README.md.tmp
|
echo "" >> windmill-api/README.md.tmp
|
||||||
|
|
||||||
|
tail -r windmill-api/README.md | tail -n +14 | tail -r >> windmill-api/README.md.tmp
|
||||||
head -n -13 windmill-api/README.md >> windmill-api/README.md.tmp
|
|
||||||
mv windmill-api/README.md.tmp windmill-api/README.md
|
mv windmill-api/README.md.tmp windmill-api/README.md
|
||||||
|
|
||||||
cd windmill-api && poetry build
|
cd windmill-api && poetry build
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#! /usr/bin/env nu
|
#! /usr/bin/env nu
|
||||||
|
|
||||||
let cache = "/tmp/windmill/cache/pip/"
|
let cache = "/tmp/windmill/cache/python_311/"
|
||||||
|
|
||||||
# Clean cache
|
# Clean cache
|
||||||
def "main clean" [] {
|
def "main clean" [] {
|
||||||
@@ -42,7 +42,7 @@ def main [
|
|||||||
rm -rf ($cache ++ wmill*/wmill/*)
|
rm -rf ($cache ++ wmill*/wmill/*)
|
||||||
|
|
||||||
# Copy files from local ./dist to every wm-client version in cache
|
# Copy files from local ./dist to every wm-client version in cache
|
||||||
ls /tmp/windmill/cache/pip/wmill* | each {
|
ls /tmp/windmill/cache/python_311/wmill* | each {
|
||||||
|i|
|
|i|
|
||||||
|
|
||||||
let path = $i | get name;
|
let path = $i | get name;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ include = ["wmill/py.typed"]
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7"
|
python = "^3.7"
|
||||||
httpx = ">=0.24"
|
httpx = ">=0.24"
|
||||||
|
slack-sdk = "^3.33.5"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=1.0.2", "poetry-dynamic-versioning"]
|
requires = ["poetry>=1.0.2", "poetry-dynamic-versioning"]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import httpx
|
|||||||
|
|
||||||
from .s3_reader import S3BufferedReader, bytes_generator
|
from .s3_reader import S3BufferedReader, bytes_generator
|
||||||
from .s3_types import Boto3ConnectionSettings, DuckDbConnectionSettings, PolarsConnectionSettings, S3Object
|
from .s3_types import Boto3ConnectionSettings, DuckDbConnectionSettings, PolarsConnectionSettings, S3Object
|
||||||
|
from slack_sdk import WebClient
|
||||||
|
|
||||||
_client: "Windmill | None" = None
|
_client: "Windmill | None" = None
|
||||||
|
|
||||||
@@ -623,6 +624,48 @@ class Windmill:
|
|||||||
params={"approver": approver},
|
params={"approver": approver},
|
||||||
).json()
|
).json()
|
||||||
|
|
||||||
|
def request_interactive_slack_approval(
|
||||||
|
self,
|
||||||
|
slack_token: str,
|
||||||
|
channel: str,
|
||||||
|
message: str = None,
|
||||||
|
approver: str = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Request interactive Slack approval
|
||||||
|
:param slack_token: Slack token
|
||||||
|
:param channel: Slack channel
|
||||||
|
:param message: Message to send to Slack
|
||||||
|
:param approver: Approver name
|
||||||
|
"""
|
||||||
|
web = WebClient(slack_token)
|
||||||
|
nonce = random.randint(0, 4294967295)
|
||||||
|
workspace = self.workspace
|
||||||
|
flow_job_id = os.environ.get("WM_FLOW_JOB_ID")
|
||||||
|
|
||||||
|
if not flow_job_id:
|
||||||
|
raise Exception(
|
||||||
|
"You can't use 'request_interactive_slack_approval' function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only include non-empty parameters
|
||||||
|
params = {}
|
||||||
|
if message:
|
||||||
|
params["message"] = message
|
||||||
|
if approver:
|
||||||
|
params["approver"] = approver
|
||||||
|
|
||||||
|
blocks = self.get(
|
||||||
|
f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}/{nonce}",
|
||||||
|
params=params,
|
||||||
|
).json()
|
||||||
|
|
||||||
|
web.chat_postMessage(
|
||||||
|
channel=channel,
|
||||||
|
text=message,
|
||||||
|
blocks=blocks,
|
||||||
|
)
|
||||||
|
|
||||||
def username_to_email(self, username: str) -> str:
|
def username_to_email(self, username: str) -> str:
|
||||||
"""
|
"""
|
||||||
Get email from workspace username
|
Get email from workspace username
|
||||||
@@ -972,6 +1015,19 @@ def get_state_path() -> str:
|
|||||||
def get_resume_urls(approver: str = None) -> dict:
|
def get_resume_urls(approver: str = None) -> dict:
|
||||||
return _client.get_resume_urls(approver)
|
return _client.get_resume_urls(approver)
|
||||||
|
|
||||||
|
@init_global_client
|
||||||
|
def request_interactive_slack_approval(
|
||||||
|
slack_token: str,
|
||||||
|
channel: str,
|
||||||
|
message: str = None,
|
||||||
|
approver: str = None,
|
||||||
|
) -> dict:
|
||||||
|
return _client.request_interactive_slack_approval(
|
||||||
|
slack_token,
|
||||||
|
channel,
|
||||||
|
message,
|
||||||
|
approver,
|
||||||
|
)
|
||||||
|
|
||||||
@init_global_client
|
@init_global_client
|
||||||
def cancel_running() -> dict:
|
def cancel_running() -> dict:
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# Generate windmill-client bundle
|
# Generate windmill-client bundle
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./node_modules/.bin/esbuild src/index.ts --b
|
./node_modules/.bin/esbuild src/index.ts --bundle --outfile=windmill.js --format=esm --platform=node
|
||||||
undle --outfile=windmill.js --format=esm
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Generate d.ts bundle
|
# Generate d.ts bundle
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ cp "${script_dirpath}/s3Types.ts" "${script_dirpath}/src/"
|
|||||||
echo "" >> "${script_dirpath}/src/index.ts"
|
echo "" >> "${script_dirpath}/src/index.ts"
|
||||||
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||||
echo "" >> "${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 } 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} from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://loc
|
|||||||
const baseUrlApi = (baseUrl ?? '') + "/api";
|
const baseUrlApi = (baseUrl ?? '') + "/api";
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' src/core/OpenAPI.ts
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' src/core/OpenAPI.ts
|
sed -i '' 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' src/core/OpenAPI.ts
|
||||||
sed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" src/core/OpenAPI.ts
|
sed -i '' 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' src/core/OpenAPI.ts
|
||||||
|
sed -i '' "s/BASE: '\/api'/BASE: baseUrlApi/g" src/core/OpenAPI.ts
|
||||||
|
else
|
||||||
|
sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' "${script_dirpath}/src/core/OpenAPI.ts"
|
||||||
|
sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' "${script_dirpath}/src/core/OpenAPI.ts"
|
||||||
|
sed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" "${script_dirpath}/src/core/OpenAPI.ts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -34,4 +39,4 @@ cp "${script_dirpath}/s3Types.ts" "${script_dirpath}/src/"
|
|||||||
echo "" >> "${script_dirpath}/src/index.ts"
|
echo "" >> "${script_dirpath}/src/index.ts"
|
||||||
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||||
echo "" >> "${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 } 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 } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.usernameToEmail = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.getIdToken = exports.getResumeEndpoints = exports.getResumeUrls = exports.writeS3File = exports.loadS3FileStream = exports.loadS3File = exports.denoS3LightClientSettings = exports.databaseUrlFromResource = exports.setVariable = exports.getVariable = exports.getState = exports.getInternalState = exports.getFlowUserState = exports.setFlowUserState = exports.setState = exports.setInternalState = exports.setResource = exports.getStatePath = exports.resolveDefaultResource = exports.runScriptAsync = exports.task = exports.getResultMaybe = exports.getResult = exports.waitJob = exports.runScript = exports.getRootJobId = exports.getResource = exports.getWorkspace = exports.setClient = exports.SHARED_FOLDER = exports.WorkspaceService = exports.UserService = exports.SettingsService = exports.ScheduleService = exports.ScriptService = exports.VariableService = exports.ResourceService = exports.JobService = exports.GroupService = exports.GranularAclService = exports.FlowService = exports.AuditService = exports.AdminService = void 0;
|
exports.requestInteractiveSlackApproval = exports.usernameToEmail = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.getIdToken = exports.getResumeEndpoints = exports.getResumeUrls = exports.writeS3File = exports.loadS3FileStream = exports.loadS3File = exports.denoS3LightClientSettings = exports.databaseUrlFromResource = exports.setVariable = exports.getVariable = exports.getState = exports.getInternalState = exports.getFlowUserState = exports.setFlowUserState = exports.setState = exports.setInternalState = exports.setResource = exports.getStatePath = exports.resolveDefaultResource = exports.runScriptAsync = exports.task = exports.getResultMaybe = exports.getResult = exports.waitJob = exports.runScript = exports.getRootJobId = exports.getResource = exports.getWorkspace = exports.setClient = exports.SHARED_FOLDER = exports.WorkspaceService = exports.UserService = exports.SettingsService = exports.ScheduleService = exports.ScriptService = exports.VariableService = exports.ResourceService = exports.JobService = exports.GroupService = exports.GranularAclService = exports.FlowService = exports.AuditService = exports.AdminService = void 0;
|
||||||
const index_1 = require("./index");
|
const index_1 = require("./index");
|
||||||
const index_2 = require("./index");
|
const index_2 = require("./index");
|
||||||
var index_3 = require("./index");
|
var index_3 = require("./index");
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { OpenAPI } from "./index";
|
import { OpenAPI } from "./index";
|
||||||
// import type { DenoS3LightClientSettings } from "./index";
|
// import type { DenoS3LightClientSettings } from "./index";
|
||||||
import { DenoS3LightClientSettings, type S3Object } from "./s3Types";
|
import { DenoS3LightClientSettings, type S3Object } from "./s3Types";
|
||||||
|
import { WebClient, Block, KnownBlock } from "@slack/web-api";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AdminService,
|
AdminService,
|
||||||
@@ -395,15 +396,15 @@ export async function setState(state: any): Promise<void> {
|
|||||||
*/
|
*/
|
||||||
export async function setProgress(percent: number, jobId?: any): Promise<void> {
|
export async function setProgress(percent: number, jobId?: any): Promise<void> {
|
||||||
const workspace = getWorkspace();
|
const workspace = getWorkspace();
|
||||||
let flowId = getEnv("WM_FLOW_JOB_ID");
|
let flowId = getEnv("WM_FLOW_JOB_ID");
|
||||||
|
|
||||||
// If jobId specified we need to find if there is a parent/flow
|
// If jobId specified we need to find if there is a parent/flow
|
||||||
if (jobId) {
|
if (jobId) {
|
||||||
const job = await JobService.getJob({
|
const job = await JobService.getJob({
|
||||||
id: jobId ?? "NO_JOB_ID",
|
id: jobId ?? "NO_JOB_ID",
|
||||||
workspace,
|
workspace,
|
||||||
noLogs: true
|
noLogs: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Could be actual flowId or undefined
|
// Could be actual flowId or undefined
|
||||||
flowId = job.parent_job;
|
flowId = job.parent_job;
|
||||||
@@ -415,22 +416,22 @@ export async function setProgress(percent: number, jobId?: any): Promise<void> {
|
|||||||
requestBody: {
|
requestBody: {
|
||||||
// In case user inputs float, it should be converted to int
|
// In case user inputs float, it should be converted to int
|
||||||
percent: Math.floor(percent),
|
percent: Math.floor(percent),
|
||||||
flow_job_id: (flowId == "") ? undefined : flowId,
|
flow_job_id: flowId == "" ? undefined : flowId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the progress
|
* Get the progress
|
||||||
* @param jobId? Job to get progress from
|
* @param jobId? Job to get progress from
|
||||||
* @returns Optional clamped between 0 and 100 progress value
|
* @returns Optional clamped between 0 and 100 progress value
|
||||||
*/
|
*/
|
||||||
export async function getProgress(jobId?: any): Promise<number | null> {
|
export async function getProgress(jobId?: any): Promise<number | null> {
|
||||||
// TODO: Delete or set to 100 completed job metrics
|
// TODO: Delete or set to 100 completed job metrics
|
||||||
return await MetricsService.getJobProgress({
|
return await MetricsService.getJobProgress({
|
||||||
id: jobId ?? getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
|
id: jobId ?? getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
|
||||||
workspace: getWorkspace(),
|
workspace: getWorkspace(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -846,3 +847,51 @@ export async function usernameToEmail(username: string): Promise<string> {
|
|||||||
const workspace = getWorkspace();
|
const workspace = getWorkspace();
|
||||||
return await UserService.usernameToEmail({ username, workspace });
|
return await UserService.usernameToEmail({ username, workspace });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SlackApprovalOptions {
|
||||||
|
slackToken: string;
|
||||||
|
channel: string;
|
||||||
|
message?: string;
|
||||||
|
approver?: string;
|
||||||
|
slackBlocks?: (Block | KnownBlock)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestInteractiveSlackApproval({
|
||||||
|
slackToken,
|
||||||
|
channel,
|
||||||
|
message,
|
||||||
|
approver,
|
||||||
|
}: SlackApprovalOptions): Promise<void> {
|
||||||
|
const web = new WebClient(slackToken);
|
||||||
|
const nonce = Math.floor(Math.random() * 4294967295);
|
||||||
|
const workspace = getWorkspace();
|
||||||
|
const flowJobId = getEnv("WM_FLOW_JOB_ID");
|
||||||
|
|
||||||
|
if (!flowJobId) {
|
||||||
|
throw new Error(
|
||||||
|
"You can't use this function in a standalon script or flow step preview. Please us it in a flow or a flow preview."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only include non-empty parameters
|
||||||
|
const params: { approver?: string; message?: string } = {};
|
||||||
|
if (message) {
|
||||||
|
params.message = message;
|
||||||
|
}
|
||||||
|
if (approver) {
|
||||||
|
params.approver = approver;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocks = await JobService.getSlackApprovalPayload({
|
||||||
|
workspace,
|
||||||
|
resumeId: nonce,
|
||||||
|
...params,
|
||||||
|
id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
|
||||||
|
});
|
||||||
|
|
||||||
|
await web.chat.postMessage({
|
||||||
|
channel,
|
||||||
|
text: message,
|
||||||
|
blocks: blocks as unknown as (Block | KnownBlock)[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
270
typescript-client/package-lock.json
generated
270
typescript-client/package-lock.json
generated
@@ -1,13 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "windmill-client",
|
"name": "alex-windmill-client-alex",
|
||||||
"version": "1.335.0",
|
"version": "1.435.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "windmill-client",
|
"name": "alex-windmill-client-alex",
|
||||||
"version": "1.335.0",
|
"version": "1.435.2",
|
||||||
"license": "Apache 2.0",
|
"license": "Apache 2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@slack/web-api": "^7.8.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.4.10",
|
||||||
|
"dts-bundle-generator": "^9.5.1",
|
||||||
|
"esbuild": "^0.21.1",
|
||||||
|
"typescript": "^5.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"../typescript-client/": {
|
||||||
|
"name": "alex-windmill-client-alex",
|
||||||
|
"version": "1.435.2",
|
||||||
|
"extraneous": true,
|
||||||
|
"license": "Apache 2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@slack/web-api": "^7.8.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.4.10",
|
"@types/node": "^20.4.10",
|
||||||
"dts-bundle-generator": "^9.5.1",
|
"dts-bundle-generator": "^9.5.1",
|
||||||
@@ -383,11 +401,63 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@slack/logger": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18",
|
||||||
|
"npm": ">= 8.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@slack/types": {
|
||||||
|
"version": "2.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@slack/types/-/types-2.14.0.tgz",
|
||||||
|
"integrity": "sha512-n0EGm7ENQRxlXbgKSrQZL69grzg1gHLAVd+GlRVQJ1NSORo0FrApR7wql/gaKdu2n4TO83Sq/AmeUOqD60aXUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.13.0",
|
||||||
|
"npm": ">= 6.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@slack/web-api": {
|
||||||
|
"version": "7.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.8.0.tgz",
|
||||||
|
"integrity": "sha512-d4SdG+6UmGdzWw38a4sN3lF/nTEzsDxhzU13wm10ejOpPehtmRoqBKnPztQUfFiWbNvSb4czkWYJD4kt+5+Fuw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@slack/logger": "^4.0.0",
|
||||||
|
"@slack/types": "^2.9.0",
|
||||||
|
"@types/node": ">=18.0.0",
|
||||||
|
"@types/retry": "0.12.0",
|
||||||
|
"axios": "^1.7.8",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"is-electron": "2.2.2",
|
||||||
|
"is-stream": "^2",
|
||||||
|
"p-queue": "^6",
|
||||||
|
"p-retry": "^4",
|
||||||
|
"retry": "^0.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18",
|
||||||
|
"npm": ">= 8.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.4.10",
|
"version": "20.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.10.tgz",
|
||||||
"integrity": "sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==",
|
"integrity": "sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg=="
|
||||||
"dev": true
|
},
|
||||||
|
"node_modules/@types/retry": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
@@ -413,6 +483,23 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||||
|
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
@@ -445,6 +532,27 @@
|
|||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dts-bundle-generator": {
|
"node_modules/dts-bundle-generator": {
|
||||||
"version": "9.5.1",
|
"version": "9.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz",
|
||||||
@@ -514,6 +622,46 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-caller-file": {
|
"node_modules/get-caller-file": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
@@ -523,6 +671,12 @@
|
|||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-electron": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-fullwidth-code-point": {
|
"node_modules/is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
@@ -532,6 +686,101 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-stream": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-finally": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-queue": {
|
||||||
|
"version": "6.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
|
||||||
|
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^4.0.4",
|
||||||
|
"p-timeout": "^3.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-queue/node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/p-retry": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/retry": "0.12.0",
|
||||||
|
"retry": "^0.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-timeout": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-finally": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@@ -541,6 +790,15 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/retry": {
|
||||||
|
"version": "0.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||||
|
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "windmill-client",
|
"name": "alex-windmill-client-alex",
|
||||||
"description": "Windmill SDK client for browsers and Node.js",
|
"description": "Windmill SDK client for browsers and Node.js",
|
||||||
"version": "1.437.1",
|
"version": "1.437.1",
|
||||||
"author": "Ruben Fiszel",
|
"author": "Ruben Fiszel",
|
||||||
@@ -18,5 +18,8 @@
|
|||||||
"LICENSE",
|
"LICENSE",
|
||||||
"README.md",
|
"README.md",
|
||||||
"package.json"
|
"package.json"
|
||||||
]
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@slack/web-api": "^7.8.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user