Merge branch 'main' into alp/docker_non_root

This commit is contained in:
Alexander Petric
2024-12-23 11:34:00 +01:00
committed by GitHub
48 changed files with 1665 additions and 250 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog
## [1.441.0](https://github.com/windmill-labs/windmill/compare/v1.440.3...v1.441.0) (2024-12-20)
### Features
* interactive slack approvals ([#4942](https://github.com/windmill-labs/windmill/issues/4942)) ([6308bf0](https://github.com/windmill-labs/windmill/commit/6308bf0dcb1d6670e839a1a1e0b794bf3ce6520c))
## [1.440.3](https://github.com/windmill-labs/windmill/compare/v1.440.2...v1.440.3) (2024-12-19)
@@ -0,0 +1,67 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n queue.job_kind AS \"job_kind: JobKind\",\n queue.script_hash AS \"script_hash: ScriptHash\",\n queue.raw_flow AS \"raw_flow: sqlx::types::Json<Box<RawValue>>\",\n completed_job.parent_job AS \"parent_job: Uuid\"\n FROM queue\n JOIN completed_job ON completed_job.parent_job = queue.id\n WHERE completed_job.id = $1 AND completed_job.workspace_id = $2\n LIMIT 1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "job_kind: JobKind",
"type_info": {
"Custom": {
"name": "job_kind",
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies",
"http",
"graphql",
"postgresql",
"noop",
"appdependencies",
"deploymentcallback",
"singlescriptflow",
"flowscript",
"flownode",
"appscript"
]
}
}
}
},
{
"ordinal": 1,
"name": "script_hash: ScriptHash",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "raw_flow: sqlx::types::Json<Box<RawValue>>",
"type_info": "Jsonb"
},
{
"ordinal": 3,
"name": "parent_job: Uuid",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": [
false,
true,
true,
true
]
},
"hash": "bb6141ad0e93986b38ccdf4d027c486137d4fe79906e22842b711f4a9379b8c8"
}
+41 -41
View File
@@ -1400,18 +1400,18 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.20.0"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a"
dependencies = [
"proc-macro2",
"quote",
@@ -1530,9 +1530,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.4"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
dependencies = [
"jobserver",
"libc",
@@ -2585,7 +2585,7 @@ dependencies = [
"http 1.2.0",
"http-body-util",
"hyper 1.5.2",
"hyper-rustls 0.27.4",
"hyper-rustls 0.27.5",
"hyper-util",
"ipnet",
"percent-encoding",
@@ -3310,9 +3310,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "foreign-types"
@@ -4270,9 +4270,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.27.4"
version = "0.27.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6884a48c6826ec44f524c7456b163cebe9e55a18d7b5e307cb4f100371cc767"
checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
dependencies = [
"futures-util",
"http 1.2.0",
@@ -4824,9 +4824,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.168"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libgit2-sys"
@@ -6976,7 +6976,7 @@ dependencies = [
"http-body 1.0.1",
"http-body-util",
"hyper 1.5.2",
"hyper-rustls 0.27.4",
"hyper-rustls 0.27.5",
"hyper-tls 0.6.0",
"hyper-util",
"ipnet",
@@ -9359,9 +9359,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
dependencies = [
"tinyvec_macros",
]
@@ -10657,7 +10657,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"axum",
@@ -10698,7 +10698,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"argon2",
@@ -10783,7 +10783,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -10801,7 +10801,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"chrono",
"serde",
@@ -10814,7 +10814,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"serde",
@@ -10828,7 +10828,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"async-stream",
@@ -10887,7 +10887,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"regex",
"serde",
@@ -10901,7 +10901,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"bytes",
@@ -10924,7 +10924,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"itertools 0.13.0",
"lazy_static",
@@ -10936,7 +10936,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -10945,7 +10945,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10957,7 +10957,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"serde_json",
@@ -10969,7 +10969,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"gosyn",
@@ -10981,7 +10981,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10993,7 +10993,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -11004,7 +11004,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -11015,7 +11015,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -11033,7 +11033,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -11050,7 +11050,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11062,7 +11062,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11080,7 +11080,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -11102,7 +11102,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"serde_json",
@@ -11112,7 +11112,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -11146,7 +11146,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -11156,7 +11156,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.440.3"
version = "1.441.0"
dependencies = [
"anyhow",
"async-recursion",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.440.3"
version = "1.441.0"
authors.workspace = true
edition.workspace = true
@@ -30,7 +30,7 @@ members = [
]
[workspace.package]
version = "1.440.3"
version = "1.441.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
+37 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.440.3
version: 1.441.0
title: Windmill API
contact:
@@ -6943,6 +6943,42 @@ paths:
- resume
- cancel
/w/{workspace}/jobs/slack_approval/{id}:
get:
summary: generate interactive slack approval for suspended job
operationId: getSlackApprovalPayload
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: slack_resource_path
in: query
required: true
schema:
type: string
- name: channel_id
in: query
required: true
schema:
type: string
- name: flow_step_id
in: query
required: true
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
+14 -5
View File
@@ -2160,7 +2160,7 @@ pub struct SuspendedJobFlow {
pub approvers: Vec<Approval>,
}
#[derive(Deserialize)]
#[derive(Deserialize, Debug)]
pub struct QueryApprover {
pub approver: Option<String>,
}
@@ -2373,11 +2373,11 @@ fn create_signature(
}
#[allow(non_snake_case)]
#[derive(Serialize)]
#[derive(Serialize, Debug)]
pub struct ResumeUrls {
approvalPage: String,
cancel: String,
resume: String,
pub approvalPage: String,
pub cancel: String,
pub resume: String,
}
fn build_resume_url(
@@ -2397,6 +2397,14 @@ pub async fn get_resume_urls(
Extension(db): Extension<DB>,
Path((w_id, job_id, resume_id)): Path<(String, Uuid, u32)>,
Query(approver): Query<QueryApprover>,
) -> error::JsonResult<ResumeUrls> {
get_resume_urls_internal(Extension(db), Path((w_id, job_id, resume_id)), Query(approver)).await
}
pub async fn get_resume_urls_internal(
Extension(db): Extension<DB>,
Path((w_id, job_id, resume_id)): Path<(String, Uuid, u32)>,
Query(approver): Query<QueryApprover>,
) -> error::JsonResult<ResumeUrls> {
let key = get_workspace_key(&w_id, &db).await?;
let signature = create_signature(key, job_id, resume_id, approver.approver.clone())?;
@@ -5561,3 +5569,4 @@ async fn delete_completed_job<'a>(
let response = Json(cj).into_response();
Ok(response)
}
+4 -1
View File
@@ -28,7 +28,7 @@ use crate::{
use anyhow::Context;
use argon2::Argon2;
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 http::HeaderValue;
use reqwest::Client;
@@ -109,6 +109,7 @@ mod websocket_triggers;
mod workers;
mod workspaces;
mod workspaces_ee;
mod slack_approvals;
mod workspaces_export;
mod workspaces_extra;
@@ -415,6 +416,8 @@ pub async fn run_server(
"/w/:workspace_id/jobs_u",
jobs::workspace_unauthed_service().layer(cors.clone()),
)
.route("/slack", post(slack_approvals::slack_app_callback_handler))
.route("/w/:workspace_id/jobs/slack_approval/:job_id", get(slack_approvals::request_slack_approval))
.nest(
"/w/:workspace_id/resources_u",
resources::public_service().layer(cors.clone()),
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,7 @@ use std::collections::HashMap;
use futures::future::BoxFuture;
use futures::{FutureExt, TryFutureExt};
use reqwest::Client;
use serde_json::{json, value::RawValue, Value};
use windmill_common::error::to_anyhow;
use windmill_common::jobs::QueuedJob;
@@ -9,11 +10,11 @@ use windmill_common::{error::Error, worker::to_raw_value};
use windmill_parser_sql::{
parse_bigquery_sig, parse_db_resource, parse_sql_blocks, parse_sql_statement_named_params,
};
use windmill_queue::{CanceledBy, HTTP_CLIENT};
use windmill_queue::CanceledBy;
use serde::Deserialize;
use crate::common::OccupancyMetrics;
use crate::common::{build_http_client, OccupancyMetrics};
use crate::handle_child::run_future_with_polling_update_job_poller;
use crate::{
common::{build_args_values, resolve_job_timeout},
@@ -68,9 +69,10 @@ fn do_bigquery_inner<'a>(
all_statement_values: &'a HashMap<String, Value>,
project_id: &'a str,
token: &'a str,
timeout_ms: i32,
timeout_ms: u64,
column_order: Option<&'a mut Option<Vec<String>>>,
skip_collect: bool,
http_client: &'a Client,
) -> windmill_common::error::Result<BoxFuture<'a, windmill_common::error::Result<Box<RawValue>>>> {
let param_names = parse_sql_statement_named_params(query, '@');
@@ -86,7 +88,7 @@ fn do_bigquery_inner<'a>(
.collect::<Vec<&Value>>();
let result_f = async move {
let response = HTTP_CLIENT
let response = http_client
.post(
"https://bigquery.googleapis.com/bigquery/v2/projects/".to_string()
+ project_id
@@ -249,13 +251,10 @@ pub async fn do_bigquery(
.await
.map_err(|e| Error::ExecutionErr(e.to_string()))?;
let timeout_ms = i32::try_from(
resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout)
.await
.0
.as_millis(),
)
.unwrap_or(200000);
let (timeout_duration, _, _) =
resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout).await;
let timeout_ms = timeout_duration.as_millis() as u64;
let http_client = build_http_client(timeout_duration)?;
let project_id = authentication_manager
.project_id()
@@ -325,6 +324,7 @@ pub async fn do_bigquery(
timeout_ms,
None,
annotations.return_last_result && i < queries.len() - 1,
&http_client,
)
})
.collect::<windmill_common::error::Result<Vec<_>>>()?;
@@ -353,6 +353,7 @@ pub async fn do_bigquery(
timeout_ms,
Some(column_order),
false,
&http_client,
)?
};
+11 -5
View File
@@ -3,6 +3,7 @@ use async_recursion::async_recursion;
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use serde_json::{json, Value};
@@ -32,11 +33,7 @@ use windmill_common::{
use anyhow::{anyhow, Result};
use std::path::Path;
use std::{
collections::HashMap,
sync::Arc,
time::Duration,
};
use std::{collections::HashMap, sync::Arc, time::Duration};
use uuid::Uuid;
use windmill_common::{variables, DB};
@@ -965,3 +962,12 @@ pub fn use_flow_root_path(flow_path: &str) -> String {
return flow_path.to_string();
}
}
pub fn build_http_client(timeout_duration: std::time::Duration) -> error::Result<Client> {
reqwest::ClientBuilder::new()
.user_agent("windmill/beta")
.timeout(timeout_duration)
.connect_timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| Error::InternalErr(format!("Error building http client: {e:#}")))
}
@@ -8,11 +8,11 @@ use windmill_common::jobs::QueuedJob;
use windmill_common::worker::to_raw_value;
use windmill_common::{error::Error, worker::CLOUD_HOSTED};
use windmill_parser_graphql::parse_graphql_sig;
use windmill_queue::{CanceledBy, HTTP_CLIENT};
use windmill_queue::CanceledBy;
use serde::Deserialize;
use crate::common::OccupancyMetrics;
use crate::common::{build_http_client, resolve_job_timeout, OccupancyMetrics};
use crate::handle_child::run_future_with_polling_update_job_poller;
use crate::{common::build_args_map, AuthedClientBackgroundTask};
@@ -81,8 +81,12 @@ pub async fn do_graphql(
}
}
}
let (timeout_duration, _, _) =
resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout).await;
let mut request = HTTP_CLIENT.post(api.base_url).json(&json!({
let http_client = build_http_client(timeout_duration)?;
let mut request = http_client.post(api.base_url).json(&json!({
"query": query,
"variables": variables
}));
@@ -332,6 +332,7 @@ pub async fn uv_pip_compile(
.env_clear()
.env("HOME", HOME_ENV.to_string())
.env("PATH", PATH_ENV.to_string())
.envs(PROXY_ENVS.clone())
.args(&args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
@@ -4,7 +4,7 @@ use core::fmt::Write;
use futures::future::BoxFuture;
use futures::{FutureExt, TryFutureExt};
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use reqwest::Response;
use reqwest::{Client, Response};
use serde_json::{json, value::RawValue, Value};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
@@ -17,7 +17,7 @@ use windmill_queue::{CanceledBy, HTTP_CLIENT};
use serde::{Deserialize, Serialize};
use crate::common::{resolve_job_timeout, OccupancyMetrics};
use crate::common::{build_http_client, resolve_job_timeout, OccupancyMetrics};
use crate::handle_child::run_future_with_polling_update_job_poller;
use crate::{common::build_args_values, AuthedClientBackgroundTask};
@@ -122,6 +122,7 @@ fn do_snowflake_inner<'a>(
token_is_keypair: bool,
column_order: Option<&'a mut Option<Vec<String>>>,
skip_collect: bool,
http_client: &'a Client,
) -> windmill_common::error::Result<BoxFuture<'a, windmill_common::error::Result<Box<RawValue>>>> {
body.insert("statement".to_string(), json!(query));
@@ -145,7 +146,7 @@ fn do_snowflake_inner<'a>(
}
let result_f = async move {
let mut request = HTTP_CLIENT
let mut request = http_client
.post(format!(
"https://{}.snowflakecomputing.com/api/v2/statements/",
account_identifier.to_uppercase()
@@ -365,6 +366,11 @@ pub async fn do_snowflake(
let queries = parse_sql_blocks(query);
let (timeout_duration, _, _) =
resolve_job_timeout(&db, &job.workspace_id, job.id, job.timeout).await;
let http_client = build_http_client(timeout_duration)?;
let result_f = if queries.len() > 1 {
let futures = queries
.iter()
@@ -379,6 +385,7 @@ pub async fn do_snowflake(
token_is_keypair,
None,
annotations.return_last_result && i < queries.len() - 1,
&http_client,
)
})
.collect::<windmill_common::error::Result<Vec<_>>>()?;
@@ -407,6 +414,7 @@ pub async fn do_snowflake(
token_is_keypair,
Some(column_order),
false,
&http_client,
)?
};
let r = run_future_with_polling_update_job_poller(
+1 -1
View File
@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.440.3";
export const VERSION = "v1.441.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+1 -1
View File
@@ -60,7 +60,7 @@ export {
// }
// });
export const VERSION = "1.440.3";
export const VERSION = "1.441.0";
const command = new Command()
.name("wmill")
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.440.3",
"version": "1.441.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.440.3",
"version": "1.441.0",
"license": "AGPL-3.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.440.3",
"version": "1.441.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -1,5 +1,6 @@
<script lang="ts">
import { type Job } from '$lib/gen'
import { isScriptPreview } from '$lib/utils'
import { onDestroy } from 'svelte'
export let job: Job | undefined = undefined
@@ -23,7 +24,7 @@
!busy &&
job &&
'running' in job &&
(job.job_kind == 'script' || job?.job_kind == 'preview')
(job.job_kind == 'script' || isScriptPreview(job?.job_kind))
)
start(job)
@@ -18,7 +18,7 @@
import Tabs from './common/tabs/Tabs.svelte'
import { type DurationStatus, type FlowStatusViewerContext, type GraphModuleState } from './graph'
import ModuleStatus from './ModuleStatus.svelte'
import { emptyString, msToSec, truncateRev } from '$lib/utils'
import { emptyString, isScriptPreview, msToSec, truncateRev } from '$lib/utils'
import JobArgs from './JobArgs.svelte'
import { ChevronDown, Hourglass, Loader2 } from 'lucide-svelte'
import FlowStatusWaitingForEvents from './FlowStatusWaitingForEvents.svelte'
@@ -728,11 +728,7 @@
})
}
if (
jobLoaded.job_kind == 'script' ||
jobLoaded.job_kind == 'flowscript' ||
jobLoaded.job_kind == 'preview'
) {
if (jobLoaded.job_kind == 'script' || isScriptPreview(jobLoaded.job_kind)) {
let id: string | undefined = undefined
if (
(innerModule?.type == 'forloopflow' || innerModule?.type == 'whileloopflow') &&
@@ -5,6 +5,7 @@
import { createEventDispatcher } from 'svelte'
import type { SupportedLanguage } from '$lib/common'
import { sendUserToast } from '$lib/toast'
import { isScriptPreview } from '$lib/utils'
export let isLoading = false
export let job: Job | undefined = undefined
@@ -192,7 +193,7 @@
let getProgress: boolean | undefined = undefined
// We only pull individual job progress this way
// Flow's progress we are getting from FlowStatusModule of flow job
if (job.job_kind == 'script' || job.job_kind == 'preview') {
if (job.job_kind == 'script' || isScriptPreview(job.job_kind)) {
// First time, before running job, lastTimeCheckedProgress is always undefined
if (lastTimeCheckedProgress) {
const lastTimeCheckedMs = Date.now() - lastTimeCheckedProgress
@@ -30,10 +30,10 @@
import ItemPicker from '$lib/components/ItemPicker.svelte'
import VariableEditor from '$lib/components/VariableEditor.svelte'
import { VariableService, type Job, type Policy } from '$lib/gen'
import { VariableService, type Policy } from '$lib/gen'
import { initHistory } from '$lib/history'
import { Component, Minus, Paintbrush, Plus, Smartphone, Scan, Hand, Grab } from 'lucide-svelte'
import { findGridItem, findGridItemParentGrid } from './appUtils'
import { animateTo, findGridItem, findGridItemParentGrid } from './appUtils'
import ComponentNavigation from './component/ComponentNavigation.svelte'
import CssSettings from './componentsPanel/CssSettings.svelte'
import SettingsPanel from './SettingsPanel.svelte'
@@ -49,7 +49,6 @@
import { getTheme } from './componentsPanel/themeUtils'
import StylePanel from './settingsPanel/StylePanel.svelte'
import type DiffDrawer from '$lib/components/DiffDrawer.svelte'
import RunnableJobPanel from './RunnableJobPanel.svelte'
import HideButton from './settingsPanel/HideButton.svelte'
import AppEditorBottomPanel from './AppEditorBottomPanel.svelte'
import panzoom from 'panzoom'
@@ -386,27 +385,6 @@
}
}
function animateTo(start: number, end: number, onUpdate: (newValue: number) => void) {
const duration = 400
const startTime = performance.now()
function animate(time: number) {
const elapsed = time - startTime
const progress = Math.min(elapsed / duration, 1)
const currentValue = start + (end - start) * easeInOut(progress)
onUpdate(currentValue)
if (progress < 1) {
requestAnimationFrame(animate)
}
}
requestAnimationFrame(animate)
}
function easeInOut(t: number) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
}
$: $cssEditorOpen && selectCss()
function selectCss() {
@@ -583,13 +561,11 @@
} else {
leftPanelSize = storedLeftPanelSize
}
storedLeftPanelSize = 0
}
function showRightPanel() {
rightPanelSize = storedRightPanelSize
centerPanelSize = centerPanelSize - storedRightPanelSize
storedRightPanelSize = 0
}
function showBottomPanel(animate: boolean = false) {
@@ -611,7 +587,6 @@
runnablePanelSize = storedBottomPanelSize
gridPanelSize = gridPanelSize - storedBottomPanelSize
}
storedBottomPanelSize = 0
}
function keydown(event: KeyboardEvent) {
@@ -705,9 +680,6 @@
$: $connectingInput.opened, updatePannelInConnecting()
let testJob: Job | undefined = undefined
let jobToWatch: { componentId: string; job: string } | undefined = undefined
$: updateCursorStyle(!!$connectingInput.opened && !$panzoomActive)
function updateCursorStyle(disabled: boolean) {
@@ -1125,14 +1097,7 @@
{rightPanelSize}
{centerPanelWidth}
{runnablePanelSize}
>
<RunnableJobPanel
float={rightPanelSize !== 0}
hidden={runnablePanelSize === 0}
bind:testJob
bind:jobToWatch
/>
</AppEditorBottomPanel>
/>
</Pane>
{/if}
</Splitpanes>
@@ -1,5 +1,4 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge'
import InlineScriptsPanel from './inlineScriptsPanel/InlineScriptsPanel.svelte'
import RunnableJobPanel from './RunnableJobPanel.svelte'
@@ -10,15 +9,13 @@
{#if rightPanelSize !== 0}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class={twMerge('relative h-full w-full overflow-x-visible')} on:mouseenter on:mouseleave>
<div class="relative h-full w-full overflow-x-visible" on:mouseenter on:mouseleave>
<InlineScriptsPanel on:hidePanel />
<RunnableJobPanel hidden={runnablePanelSize === 0} />
<slot />
</div>
{:else}
<div class="flex flex-row relative w-full h-full">
<InlineScriptsPanel width={centerPanelWidth - 400} on:hidePanel />
<slot />
<InlineScriptsPanel width={centerPanelWidth * 0.66} on:hidePanel />
<RunnableJobPanel float={false} hidden={runnablePanelSize === 0} />
</div>
{/if}
@@ -41,7 +41,8 @@
truncateRev,
orderedJsonStringify,
type Value,
replaceFalseWithUndefined
replaceFalseWithUndefined,
isFlowPreview
} from '../../../utils'
import type {
AppInput,
@@ -1404,7 +1405,7 @@
</div>
{/if}
{#if job?.job_kind !== 'flow' && job?.job_kind !== 'flowpreview'}
{#if job?.job_kind !== 'flow' && !isFlowPreview(job?.job_kind)}
{@const jobResult = $jobsById[selectedJobId]}
<Splitpanes horizontal class="grow border w-full">
<Pane size={50} minSize={10}>
@@ -9,6 +9,7 @@
export let hidden: boolean = false
export let testJob: Job | undefined = undefined
export let jobToWatch: { componentId: string; job: string } | undefined = undefined
export let width: number | undefined = undefined
const { runnableJobEditorPanel, selectedComponentInEditor } =
getContext<AppEditorContext>('AppEditorContext')
@@ -16,7 +17,7 @@
let testJobLoader: TestJobLoader
$: $runnableJobEditorPanel.focused &&
$: ($runnableJobEditorPanel.focused || !float) &&
$selectedComponentInEditor &&
$runnableJobEditorPanel.jobs &&
updateSelectedJob()
@@ -59,7 +60,10 @@
<RunnableJobPanelInner {testIsLoading} {frontendJob} {testJob} />
</div>
{:else}
<div class="flex flex-col w-full">
<div
class="flex flex-col min-w-0 grow h-full"
style={width !== undefined ? `width:${width}px;` : ''}
>
{#if $selectedComponentInEditor}
<RunnableJobPanelInner {testIsLoading} {frontendJob} {testJob} />
{:else if !hidden}
@@ -1281,3 +1281,24 @@ export function areShadowsTheSame(
shadow1.h === shadow2.h
)
}
export function animateTo(start: number, end: number, onUpdate: (newValue: number) => void) {
const duration = 400
const startTime = performance.now()
function animate(time: number) {
const elapsed = time - startTime
const progress = Math.min(elapsed / duration, 1)
const currentValue = start + (end - start) * easeInOut(progress)
onUpdate(currentValue)
if (progress < 1) {
requestAnimationFrame(animate)
}
}
requestAnimationFrame(animate)
}
function easeInOut(t: number) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
}
@@ -1,7 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext, AppViewerContext, HiddenRunnable } from '../../types'
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import InlineScriptsPanelList from './InlineScriptsPanelList.svelte'
import InlineScriptEditor from './InlineScriptEditor.svelte'
@@ -105,60 +104,58 @@
export let width: number | undefined = undefined
</script>
<SplitPanesWrapper>
<Splitpanes
class={twMerge('!overflow-visible')}
style={width !== undefined ? `width:${width}px;` : ''}
>
<Pane size={25}>
<InlineScriptsPanelList on:hidePanel />
</Pane>
<Pane size={75}>
{#if !$selectedComponentInEditor}
<div class="text-sm text-secondary text-center py-8 px-2">
Select a script on the left panel
</div>
{:else if gridItem}
{#key gridItem?.id}
<InlineScriptsPanelWithTable
<Splitpanes
class={twMerge('!overflow-visible')}
style={width !== undefined ? `width:${width}px;` : 'width: 100%;'}
>
<Pane size={25}>
<InlineScriptsPanelList on:hidePanel />
</Pane>
<Pane size={75}>
{#if !$selectedComponentInEditor}
<div class="text-sm text-secondary text-center py-8 px-2">
Select a script on the left panel
</div>
{:else if gridItem}
{#key gridItem?.id}
<InlineScriptsPanelWithTable
on:createScriptFromInlineScript={(e) => {
createScriptFromInlineScript(gridItem?.id ?? 'unknown', e.detail)
}}
bind:gridItem
/>
{/key}
{:else if unusedInlineScript > -1 && $app.unusedInlineScripts?.[unusedInlineScript]}
{#key unusedInlineScript}
<InlineScriptEditor
on:createScriptFromInlineScript={() =>
sendUserToast('Cannot save to workspace unused scripts', true)}
id={`unused-${unusedInlineScript}`}
bind:name={$app.unusedInlineScripts[unusedInlineScript].name}
bind:inlineScript={$app.unusedInlineScripts[unusedInlineScript].inlineScript}
on:delete={() => {
// remove the script from the array at the index
$app.unusedInlineScripts.splice(unusedInlineScript, 1)
$app.unusedInlineScripts = [...$app.unusedInlineScripts]
}}
/>
{/key}
{:else if hiddenInlineScript > -1}
{#key hiddenInlineScript}
{#if $app.hiddenInlineScripts?.[hiddenInlineScript]}
<InlineScriptHiddenRunnable
on:createScriptFromInlineScript={(e) => {
createScriptFromInlineScript(gridItem?.id ?? 'unknown', e.detail)
createScriptFromInlineScript(BG_PREFIX + hiddenInlineScript, e.detail)
}}
bind:gridItem
/>
{/key}
{:else if unusedInlineScript > -1 && $app.unusedInlineScripts?.[unusedInlineScript]}
{#key unusedInlineScript}
<InlineScriptEditor
on:createScriptFromInlineScript={() =>
sendUserToast('Cannot save to workspace unused scripts', true)}
id={`unused-${unusedInlineScript}`}
bind:name={$app.unusedInlineScripts[unusedInlineScript].name}
bind:inlineScript={$app.unusedInlineScripts[unusedInlineScript].inlineScript}
on:delete={() => {
// remove the script from the array at the index
$app.unusedInlineScripts.splice(unusedInlineScript, 1)
$app.unusedInlineScripts = [...$app.unusedInlineScripts]
}}
/>
{/key}
{:else if hiddenInlineScript > -1}
{#key hiddenInlineScript}
{#if $app.hiddenInlineScripts?.[hiddenInlineScript]}
<InlineScriptHiddenRunnable
on:createScriptFromInlineScript={(e) => {
createScriptFromInlineScript(BG_PREFIX + hiddenInlineScript, e.detail)
}}
transformer={$selectedComponentInEditor?.endsWith('_transformer')}
on:delete={() => deleteBackgroundScript(hiddenInlineScript)}
id={BG_PREFIX + hiddenInlineScript}
bind:runnable={$app.hiddenInlineScripts[hiddenInlineScript]}
/>{/if}{/key}
{:else}
<div class="text-sm text-tertiary text-center py-8 px-2">
No script found at id {$selectedComponentInEditor}
</div>
{/if}
</Pane>
</Splitpanes>
</SplitPanesWrapper>
transformer={$selectedComponentInEditor?.endsWith('_transformer')}
on:delete={() => deleteBackgroundScript(hiddenInlineScript)}
id={BG_PREFIX + hiddenInlineScript}
bind:runnable={$app.hiddenInlineScripts[hiddenInlineScript]}
/>{/if}{/key}
{:else}
<div class="text-sm text-tertiary text-center py-8 px-2">
No script found at id {$selectedComponentInEditor}
</div>
{/if}
</Pane>
</Splitpanes>
@@ -18,13 +18,18 @@
export let defaultUserInput = false
export let hideCreateScript = false
export let onlyFlow = false
let tab: Tab = onlyFlow ? 'workspaceflows' : 'inlinescripts'
let filter: string = ''
let picker: Drawer
export let rawApps = false
const { app, workspace } = getContext<AppViewerContext>('AppViewerContext')
let tab: Tab = onlyFlow
? 'workspaceflows'
: $app?.unusedInlineScripts?.length > 0
? 'inlinescripts'
: 'workspacescripts'
let filter: string = ''
let picker: Drawer
const dispatch = createEventDispatcher<{
pick: {
runnable: Runnable
@@ -138,15 +143,17 @@
<div class="max-w-6xl">
<Tabs bind:selected={tab}>
{#if !onlyFlow}
<Tab size="sm" value="inlinescripts">
<div class="flex gap-2 items-center my-1">
<Building size={18} strokeWidth={1.5} />
Detached Inline Scripts
</div>
</Tab>
{#if !rawApps}
<Tab size="sm" value="inlinescripts">
<div class="flex gap-2 items-center my-1">
<Building size={18} strokeWidth={1.5} />
Detached Inline Scripts
</div>
</Tab>
{/if}
<Tab size="sm" value="workspacescripts">
<div class="flex gap-2 items-center my-1">
<Building size={18} strokeWidth={1.5}/>
<Building size={18} strokeWidth={1.5} />
Workspace Scripts
</div>
</Tab>
@@ -14,7 +14,7 @@
import { workspaceStore } from '$lib/stores'
import WorkflowTimeline from '../WorkflowTimeline.svelte'
import Popover from '../Popover.svelte'
import { truncateRev } from '$lib/utils'
import { isFlowPreview, isScriptPreview, truncateRev } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import { ListFilter } from 'lucide-svelte'
@@ -154,7 +154,7 @@
{/if}
<div class=" w-full rounded-md min-h-full">
{#if job?.is_flow_step == false && job?.flow_status && (job?.job_kind == 'preview' || job?.job_kind == 'script') && !(typeof job.flow_status == 'object' && '_metadata' in job.flow_status)}
{#if job?.is_flow_step == false && job?.flow_status && (isScriptPreview(job?.job_kind) || job?.job_kind == 'script') && !(typeof job.flow_status == 'object' && '_metadata' in job.flow_status)}
<WorkflowTimeline
flow_status={asWorkflowStatus(job.flow_status)}
flowDone={job.type == 'CompletedJob'}
@@ -165,14 +165,14 @@
<Tabs bind:selected={viewTab}>
<Tab size="xs" value="result">Result</Tab>
<Tab size="xs" value="logs">Logs</Tab>
{#if job?.job_kind == 'preview'}
{#if isScriptPreview(job?.job_kind)}
<Tab size="xs" value="code">Code</Tab>
{/if}
</Tabs>
<Skeleton loading={!job} layout={[[5]]} />
{#if job}
{#if viewTab == 'result' && (job?.job_kind == 'flow' || job?.job_kind == 'flowpreview')}
{#if viewTab == 'result' && (job?.job_kind == 'flow' || isFlowPreview(job?.job_kind))}
<div class="flex flex-col gap-2">
<div class="w-full mt-10 mb-20">
<FlowStatusViewer jobId={job.id} workspaceId={job.workspace_id} />
@@ -215,7 +215,7 @@
{/if}
{/if}
{:else if job && `running` in job ? job.running : false}
{#if job?.job_kind == 'flow' || job?.job_kind == 'flowpreview'}
{#if job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)}
<div class="flex flex-col gap-2 w-full">
<FlowProgressBar {job} class="py-4" />
<FlowStatusViewer jobId={job.id} workspaceId={job.workspace_id} />
+11 -5
View File
@@ -2,7 +2,15 @@
import { base } from '$lib/base'
import { goto } from '$lib/navigation'
import type { Job } from '$lib/gen'
import { displayDate, msToReadableTime, truncateHash, truncateRev, isJobCancelable } from '$lib/utils'
import {
displayDate,
msToReadableTime,
truncateHash,
truncateRev,
isJobCancelable,
isFlowPreview,
isScriptPreview
} from '$lib/utils'
import { Badge, Button } from '../common'
import ScheduleEditor from '../ScheduleEditor.svelte'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
@@ -38,7 +46,6 @@
let scheduleEditor: ScheduleEditor
$: isExternal = job && job.id === '-'
</script>
<Portal name="run-row">
@@ -110,7 +117,7 @@
{#if job && 'duration_ms' in job && job.duration_ms != undefined}
(Ran in {msToReadableTime(
job.duration_ms
)}{#if job.job_kind == 'flow' || job.job_kind == 'flowpreview'}&nbsp;total{/if})
)}{#if job.job_kind == 'flow' || isFlowPreview(job.job_kind)}&nbsp;total{/if})
{/if}
{#if job && (job.self_wait_time_ms || job.aggregate_wait_time_ms)}
<WaitTimeWarning
@@ -123,7 +130,6 @@
Scheduled for {displayDate(job.scheduled_for)}
{:else if job.canceled}
Cancelling job... (created <TimeAgo agoOnlyIfRecent date={job.created_at || ''} />)
{:else}
Waiting for executor (created <TimeAgo agoOnlyIfRecent date={job.created_at || ''} />)
{/if}
@@ -176,7 +182,7 @@
</Button>
{/if}
</div>
{:else if 'job_kind' in job && job.job_kind == 'preview'}
{:else if 'job_kind' in job && isScriptPreview(job.job_kind)}
<a href="{base}/run/{job.id}?workspace={job.workspace_id}">Preview without path </a>
{:else if 'job_kind' in job && job.job_kind == 'dependencies'}
<a href="{base}/run/{job.id}?workspace={job.workspace_id}">
+10
View File
@@ -1089,3 +1089,13 @@ export function validateFileExtension(ext: string) {
const validExtensionRegex = /^[a-zA-Z0-9]+([._][a-zA-Z0-9]+)*$/
return validExtensionRegex.test(ext)
}
export function isFlowPreview(job_kind: Job['job_kind'] | undefined) {
return !!job_kind && (job_kind === 'flowpreview' || job_kind === 'flownode')
}
export function isScriptPreview(job_kind: Job['job_kind'] | undefined) {
return (
!!job_kind && (job_kind === 'preview' || job_kind === 'flowscript' || job_kind === 'appscript')
)
}
@@ -59,6 +59,7 @@
import autosize from '$lib/autosize'
import EditableSchemaWrapper from '$lib/components/schema/EditableSchemaWrapper.svelte'
import ResourceEditorDrawer from '$lib/components/ResourceEditorDrawer.svelte'
import GfmMarkdown from '$lib/components/GfmMarkdown.svelte'
type ResourceW = ListableResource & { canWrite: boolean; marked?: string }
type ResourceTypeW = ResourceType & { canWrite: boolean }
@@ -437,7 +438,7 @@
<div>
<h1 class="mb-8 mt-4"><IconedResourceType name={resourceTypeViewerObj.rt} formatExtension={resourceTypeViewerObj.formatExtension} /></h1>
<div class="py-2 box prose mb-8 text-secondary">
{resourceTypeViewerObj.description ?? ''}
<GfmMarkdown md={resourceTypeViewerObj.description ?? ''} />
</div>
{#if resourceTypeViewerObj.formatExtension}
<Alert type="info" title="Plain text file resource (.{resourceTypeViewerObj.formatExtension})">
@@ -19,6 +19,8 @@
displayDate,
emptyString,
encodeState,
isFlowPreview,
isScriptPreview,
truncateHash,
truncateRev
} from '$lib/utils'
@@ -288,7 +290,7 @@
}
function forkPreview() {
if (job?.job_kind == 'flowpreview') {
if (isFlowPreview(job?.job_kind)) {
$initialArgsStore = job?.args
const state = {
flow: { value: job?.raw_flow },
@@ -355,7 +357,7 @@
<ScheduleEditor bind:this={scheduleEditor} />
{#if (job?.job_kind == 'flow' || job?.job_kind == 'flowpreview') && job?.['running'] && job?.parent_job == undefined}
{#if (job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)) && job?.['running'] && job?.parent_job == undefined}
<Drawer bind:this={debugViewer} size="800px">
<DrawerContent title="Debug Detail" on:close={debugViewer.closeDrawer}>
<svelte:fragment slot="actions">
@@ -472,7 +474,7 @@
{@const stem = `/${job?.job_kind}s`}
{@const isScript = job?.job_kind === 'script'}
{@const viewHref = `${stem}/get/${isScript ? job?.script_hash : job?.script_path}`}
{#if (job?.job_kind == 'flow' || job?.job_kind == 'flowpreview') && job?.['running'] && job?.parent_job == undefined}
{#if (job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)) && job?.['running'] && job?.parent_job == undefined}
<div class="inline">
<ButtonDropdown hasPadding={false}>
<svelte:fragment slot="buttonReplacement">
@@ -488,7 +490,7 @@
</ButtonDropdown>
</div>
{/if}
{#if job?.job_kind === 'flowpreview' || job?.job_kind === 'preview'}
{#if isFlowPreview(job?.job_kind) || isScriptPreview(job?.job_kind)}
<Button
color="dark"
size="md"
@@ -496,7 +498,7 @@
startIcon={{ icon: GitBranch }}
on:click={forkPreview}
>
Fork {job?.job_kind == 'flowpreview' ? 'flow' : 'code'} preview
Fork {isFlowPreview(job?.job_kind) ? 'flow' : 'code'} preview
</Button>
{/if}
{#if persistentScriptDefinition !== undefined}
@@ -833,8 +835,8 @@
<h2 class="mt-10">Scheduled to be executed later: {displayDate(job?.['scheduled_for'])}</h2>
</div>
{/if}
{#if job?.job_kind !== 'flow' && job?.job_kind !== 'flowpreview' && job?.job_kind !== 'singlescriptflow' && job?.job_kind !== 'flownode'}
{#if ['python3', 'bun', 'deno'].includes(job?.language ?? '') && (job?.job_kind == 'script' || job?.job_kind == 'preview')}
{#if job?.job_kind !== 'flow' && job?.job_kind !== 'singlescriptflow' && !isFlowPreview(job?.job_kind)}
{#if ['python3', 'bun', 'deno'].includes(job?.language ?? '') && (job?.job_kind == 'script' || isScriptPreview(job?.job_kind))}
<ExecutionDuration bind:job bind:longRunning={currentJobIsLongRunning} />
{/if}
<div class="max-w-7xl mx-auto w-full px-4 mb-10">
@@ -854,7 +856,7 @@
<Tab value="result">Result</Tab>
<Tab value="logs">Logs</Tab>
<Tab value="stats">Metrics</Tab>
{#if job?.job_kind == 'preview'}
{#if isScriptPreview(job?.job_kind)}
<Tab value="code">Code</Tab>
{/if}
</Tabs>
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.440.3"
wmill_pg = ">=1.440.3"
wmill = ">=1.441.0"
wmill_pg = ">=1.441.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.440.3
version: 1.441.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.440.3'
ModuleVersion = '1.441.0'
# Supported PSEditions
# CompatiblePSEditions = @()
+24 -4
View File
@@ -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
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
npx @redocly/openapi-cli@latest bundle openapi-decycled.yaml --ext json -d > openapi-deref.json
@@ -20,9 +26,19 @@ rm -rf openapi/
rm openapi*
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 "This is the raw autogenerated api client. You are most likely more interested \
@@ -33,8 +49,12 @@ user friendly experience. We use \
echo "" >> windmill-api/README.md.tmp
if [[ "$OSTYPE" == "darwin"* ]]; then
tail -r windmill-api/README.md | tail -n +14 | tail -r >> windmill-api/README.md.tmp
else
head -n -13 windmill-api/README.md >> windmill-api/README.md.tmp
fi
head -n -13 windmill-api/README.md >> windmill-api/README.md.tmp
mv windmill-api/README.md.tmp windmill-api/README.md
cd windmill-api && poetry build
+2 -2
View File
@@ -1,6 +1,6 @@
#! /usr/bin/env nu
let cache = "/tmp/windmill/cache/pip/"
let cache = "/tmp/windmill/cache/python_311/"
# Clean cache
def "main clean" [] {
@@ -42,7 +42,7 @@ def main [
rm -rf ($cache ++ wmill*/wmill/*)
# 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|
let path = $i | get name;
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.440.3"
version = "1.441.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+53
View File
@@ -623,6 +623,46 @@ class Windmill:
params={"approver": approver},
).json()
def request_interactive_slack_approval(
self,
slack_resource_path: str,
channel_id: str,
message: str = None,
approver: str = 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
"""
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
if slack_resource_path:
params["slack_resource_path"] = slack_resource_path
if channel_id:
params["channel_id"] = channel_id
if os.environ.get("WM_FLOW_STEP_ID"):
params["flow_step_id"] = os.environ.get("WM_FLOW_STEP_ID")
self.get(
f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}",
params=params,
)
def username_to_email(self, username: str) -> str:
"""
Get email from workspace username
@@ -972,6 +1012,19 @@ def get_state_path() -> str:
def get_resume_urls(approver: str = None) -> dict:
return _client.get_resume_urls(approver)
@init_global_client
def request_interactive_slack_approval(
slack_resource_path: str,
channel_id: str,
message: str = None,
approver: str = None,
) -> None:
return _client.request_interactive_slack_approval(
slack_resource_path=slack_resource_path,
channel_id=channel_id,
message=message,
approver=approver,
)
@init_global_client
def cancel_running() -> dict:
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.440.3"
version = "1.441.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -2
View File
@@ -1,8 +1,7 @@
# Generate windmill-client bundle
```bash
./node_modules/.bin/esbuild src/index.ts --b
undle --outfile=windmill.js --format=esm
./node_modules/.bin/esbuild src/index.ts --bundle --outfile=windmill.js --format=esm --platform=node
```
# Generate d.ts bundle
+1 -1
View File
@@ -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 } 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"
+10 -5
View File
@@ -22,10 +22,15 @@ const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://loc
const baseUrlApi = (baseUrl ?? '') + "/api";
EOF
sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/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
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/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' 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
fi
@@ -34,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 } 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"
+1 -1
View File
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
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_2 = require("./index");
var index_3 = require("./index");
+61 -7
View File
@@ -395,15 +395,15 @@ export async function setState(state: any): Promise<void> {
*/
export async function setProgress(percent: number, jobId?: any): Promise<void> {
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) {
const job = await JobService.getJob({
id: jobId ?? "NO_JOB_ID",
workspace,
noLogs: true
});
noLogs: true,
});
// Could be actual flowId or undefined
flowId = job.parent_job;
@@ -415,22 +415,22 @@ export async function setProgress(percent: number, jobId?: any): Promise<void> {
requestBody: {
// In case user inputs float, it should be converted to int
percent: Math.floor(percent),
flow_job_id: (flowId == "") ? undefined : flowId,
}
flow_job_id: flowId == "" ? undefined : flowId,
},
});
}
/**
* Get the progress
* @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> {
// TODO: Delete or set to 100 completed job metrics
return await MetricsService.getJobProgress({
id: jobId ?? getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
workspace: getWorkspace(),
});
});
}
/**
@@ -846,3 +846,57 @@ export async function usernameToEmail(username: string): Promise<string> {
const workspace = getWorkspace();
return await UserService.usernameToEmail({ username, workspace });
}
interface SlackApprovalOptions {
slackResourcePath: string;
channelId: string;
message?: string;
approver?: string;
}
export async function requestInteractiveSlackApproval({
slackResourcePath,
channelId,
message,
approver,
}: SlackApprovalOptions): 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;
slackResourcePath: string;
channelId: string;
flowStepId: string;
} = {
slackResourcePath,
channelId,
flowStepId,
};
if (message) {
params.message = message;
}
if (approver) {
params.approver = approver;
}
await JobService.getSlackApprovalPayload({
workspace,
...params,
id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
});
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.440.3",
"version": "1.441.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.440.3",
"version": "1.441.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.440.3
1.441.0