Compare commits

..
3 Commits
Author SHA1 Message Date
Ruben Fiszel 39f0752525 all 2024-08-19 21:43:22 +02:00
Ruben Fiszel 1ee78da4bf Merge branch 'main' into rf/foo2 2024-08-19 12:43:08 +02:00
Ruben Fiszel 0a336467d8 all 2024-08-19 12:32:43 +02:00
70 changed files with 988 additions and 1620 deletions
-49
View File
@@ -1,54 +1,5 @@
# Changelog
## [1.383.0](https://github.com/windmill-labs/windmill/compare/v1.382.2...v1.383.0) (2024-08-22)
### Features
* add native html select support + fix mobile scroll on app text component ([d604b6f](https://github.com/windmill-labs/windmill/commit/d604b6f2a0e5755c949f302c0490de47a8d662ca))
* add wrap_body header to webhooks ([9226d6c](https://github.com/windmill-labs/windmill/commit/9226d6cbc119f6a1b16936ebb855ff4f50c0e122))
* improve early stop ([#4257](https://github.com/windmill-labs/windmill/issues/4257)) ([bcde2e6](https://github.com/windmill-labs/windmill/commit/bcde2e62d7846822cfe7f8ef89a9484938829903))
### Bug Fixes
* **frontend:** fix large JSON viewer ([#4273](https://github.com/windmill-labs/windmill/issues/4273)) ([b3eabff](https://github.com/windmill-labs/windmill/commit/b3eabffb76e266269b2f5ff471a053fa065da4d0))
* **python-client:** only require httpx to be >= 0.24 instead of ^0.24 ([fc12aeb](https://github.com/windmill-labs/windmill/commit/fc12aeb3961b8a1a42a8493e3774815c940b75af))
## [1.382.2](https://github.com/windmill-labs/windmill/compare/v1.382.1...v1.382.2) (2024-08-20)
### Bug Fixes
* **app:** database studio/empty table count reset ([d6d3389](https://github.com/windmill-labs/windmill/commit/d6d33898a765f910f66cd0cfbd70a439ab107528))
* **frontend:** Fix initial FlowGraph rendering on Chrome ([#4268](https://github.com/windmill-labs/windmill/issues/4268)) ([84d4e2c](https://github.com/windmill-labs/windmill/commit/84d4e2cb956ab4947b364d64c464fd5cf3c1d16f))
* handle more gracefully worker without tags ([7e1f280](https://github.com/windmill-labs/windmill/commit/7e1f28071a7d6f3f34df417ea746fc9b5dca5157))
## [1.382.1](https://github.com/windmill-labs/windmill/compare/v1.382.0...v1.382.1) (2024-08-20)
### Bug Fixes
* **frontend:** Fix flow graph step preview ([#4264](https://github.com/windmill-labs/windmill/issues/4264)) ([0a6832a](https://github.com/windmill-labs/windmill/commit/0a6832aa4561468fb0150f9eb435b7ad857fc841))
## [1.382.0](https://github.com/windmill-labs/windmill/compare/v1.381.0...v1.382.0) (2024-08-20)
### Features
* **frontend:** improve versions history by adding a diff viewer with… ([#4261](https://github.com/windmill-labs/windmill/issues/4261)) ([c19df12](https://github.com/windmill-labs/windmill/commit/c19df12292241c20c12e3de5d880c04df9b74324))
### Bug Fixes
* add FORCE_WORKER_TAGS & fix workers page when default worker group is missing ([09c6af0](https://github.com/windmill-labs/windmill/commit/09c6af05cd6e0b3b30f771a125b1fa330a938101))
* **bun:** disable large transpiling cache ([1a0e32b](https://github.com/windmill-labs/windmill/commit/1a0e32b40b5f1ac5dcfe3639e697affb6e79a558))
* **cli:** improve error message of cli ([f3bcadb](https://github.com/windmill-labs/windmill/commit/f3bcadbfb1e42b56996e273800aa0dcb01a6868b))
* frontend/package.json & frontend/package-lock.json to reduce vulnerabilities ([#4255](https://github.com/windmill-labs/windmill/issues/4255)) ([7fe0442](https://github.com/windmill-labs/windmill/commit/7fe0442a814ed42b1775386be5f4defdcb5c0a0f))
* **frontend:** nit worker limits ([#4258](https://github.com/windmill-labs/windmill/issues/4258)) ([bb92824](https://github.com/windmill-labs/windmill/commit/bb9282427399f305c3e8874204e6478272c43ff8))
* improve resource picker handling of objects ([2502377](https://github.com/windmill-labs/windmill/commit/250237793b3c2b1afd5ee122b147301bc487eeeb))
* **typescript-client:** runFlowAsync by default assume job doesn't outlive flow ([010e0fd](https://github.com/windmill-labs/windmill/commit/010e0fdc2352d740a72de124cc26eea8fc1915af))
## [1.381.0](https://github.com/windmill-labs/windmill/compare/v1.380.0...v1.381.0) (2024-08-16)
+178 -298
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.383.0"
version = "1.381.0"
authors.workspace = true
edition.workspace = true
@@ -25,7 +25,7 @@ members = [
]
[workspace.package]
version = "1.383.0"
version = "1.381.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
-8
View File
@@ -1113,7 +1113,6 @@ async fn test_deno_flow(db: Pool<Postgres>) {
}
.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
@@ -1153,7 +1152,6 @@ async fn test_deno_flow(db: Pool<Postgres>) {
}
.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
@@ -1168,7 +1166,6 @@ async fn test_deno_flow(db: Pool<Postgres>) {
}
.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
@@ -1273,7 +1270,6 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
concurrency_time_window_s: None,
}.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
@@ -1323,7 +1319,6 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
concurrency_time_window_s: None,
}.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
@@ -1359,7 +1354,6 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
concurrency_time_window_s: None,
}.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
@@ -1375,7 +1369,6 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
],
}.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
@@ -1418,7 +1411,6 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
concurrency_time_window_s: None,
}.into(),
stop_after_if: Default::default(),
stop_after_all_iters_if: Default::default(),
summary: Default::default(),
suspend: Default::default(),
retry: None,
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.383.0
version: 1.381.0
title: Windmill API
contact:
+241 -153
View File
@@ -248,78 +248,6 @@ async fn fix_flow_versioning_migration(
Ok(())
}
macro_rules! run_windmill_migration {
($migration_job_name:expr, $db:expr, $code:block) => {
{
let migration_job_name = $migration_job_name;
let db: &Pool<Postgres> = $db;
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
$code
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
}
};
}
async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
// let has_done_migration = sqlx::query_scalar!(
// "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'fix_job_completed_index')"
@@ -362,124 +290,284 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
// tx.commit().await?;
// }
run_windmill_migration!("fix_job_completed_index_2", &db, {
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)"
// ).execute(db).await?;
let migration_job_name = "fix_job_completed_index_2";
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_started_at_new ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, started_at DESC)"
// ).execute(db).await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at")
.execute(db)
.await?;
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new",
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.execute(db)
.await?;
});
.fetch_one(db)
.await?
.unwrap_or(false);
run_windmill_migration!("fix_job_completed_index_3", &db, {
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_job_on_schedule_path")
if !has_done_migration {
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)"
// ).execute(db).await?;
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_started_at_new ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, started_at DESC)"
// ).execute(db).await?;
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at",
)
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS concurrency_limit_stats_queue")
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new",
)
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index")
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
}
let migration_job_name = "fix_job_completed_index_3";
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_job_on_schedule_path")
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS concurrency_limit_stats_queue")
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index")
.execute(db)
.await?;
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_on_created")
.execute(db)
.await?;
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
}
let migration_job_name = "fix_job_completed_index_4";
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
let mut i = 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_3 ON completed_job (workspace_id, created_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_8 ON completed_job (workspace_id, created_at DESC) where job_kind in ('deploymentcallback') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS index_completed_on_created")
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_9 ON completed_job (workspace_id, created_at DESC) where job_kind in ('dependencies', 'flowdependencies', 'appdependencies') AND parent_job IS NULL")
.execute(db)
.await?;
});
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
run_windmill_migration!("fix_job_completed_index_4", &db, {
let migration_job_name = "fix_job_completed_index_4";
let mut i = 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_3 ON completed_job (workspace_id, created_at DESC)")
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_5 ON completed_job (workspace_id, created_at DESC) where job_kind in ('preview', 'flowpreview') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_8 ON completed_job (workspace_id, created_at DESC) where job_kind in ('deploymentcallback') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_9 ON completed_job (workspace_id, created_at DESC) where job_kind in ('dependencies', 'flowdependencies', 'appdependencies') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_5 ON completed_job (workspace_id, created_at DESC) where job_kind in ('preview', 'flowpreview') AND parent_job IS NULL")
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_6 ON completed_job (workspace_id, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_6 ON completed_job (workspace_id, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_7 ON completed_job (workspace_id, success, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_7 ON completed_job (workspace_id, success, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_started_at_new_2 ON completed_job (workspace_id, started_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_started_at_new_2 ON completed_job (workspace_id, started_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists root_job_index_by_path_2 ON completed_job (workspace_id, script_path, created_at desc) WHERE parent_job IS NULL")
sqlx::query("create index concurrently if not exists root_job_index_by_path_2 ON completed_job (workspace_id, script_path, created_at desc) WHERE parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_created_at ON completed_job (created_at DESC)")
sqlx::query("create index concurrently if not exists ix_completed_job_created_at ON completed_job (created_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2",
)
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new",
)
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path")
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2",
)
.execute(db)
.await?;
});
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new",
)
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path")
.execute(db)
.await?;
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
}
Ok(())
}
-4
View File
@@ -1144,7 +1144,6 @@ mod tests {
tag_override: None,
}),
stop_after_if: None,
stop_after_all_iters_if: None,
summary: None,
suspend: Default::default(),
retry: None,
@@ -1173,7 +1172,6 @@ mod tests {
expr: "foo = 'bar'".to_string(),
skip_if_stopped: false,
}),
stop_after_all_iters_if: None,
summary: None,
suspend: Default::default(),
retry: None,
@@ -1200,7 +1198,6 @@ mod tests {
expr: "previous.isEmpty()".to_string(),
skip_if_stopped: false,
}),
stop_after_all_iters_if: None,
summary: None,
suspend: Default::default(),
retry: None,
@@ -1226,7 +1223,6 @@ mod tests {
expr: "previous.isEmpty()".to_string(),
skip_if_stopped: false,
}),
stop_after_all_iters_if: None,
summary: None,
suspend: Default::default(),
retry: None,
-3
View File
@@ -236,8 +236,6 @@ pub struct FlowModule {
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_after_if: Option<StopAfterIf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_after_all_iters_if: Option<StopAfterIf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suspend: Option<Suspend>,
@@ -582,7 +580,6 @@ pub fn add_virtual_items_if_necessary(modules: &mut Vec<FlowModule>) {
id: format!("{}-v", modules[modules.len() - 1].id),
value: crate::worker::to_raw_value(&FlowModuleValue::Identity),
stop_after_if: None,
stop_after_all_iters_if: None,
summary: Some("Virtual module needed for suspend/sleep when last module".to_string()),
mock: None,
retry: None,
-20
View File
@@ -86,10 +86,6 @@ lazy_static::lazy_static! {
}
pub async fn make_suspended_pull_query(wc: &WorkerConfig) {
if wc.worker_tags.len() == 0 {
tracing::error!("Empty tags in worker tags, skipping");
return;
}
let query = format!(
"UPDATE queue
SET running = true
@@ -118,10 +114,6 @@ pub async fn make_suspended_pull_query(wc: &WorkerConfig) {
pub async fn make_pull_query(wc: &WorkerConfig) {
let mut queries = vec![];
for tags in wc.priority_tags_sorted.iter() {
if tags.tags.len() == 0 {
tracing::error!("Empty tags in priority tags, skipping");
continue;
}
let query = format!("UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
@@ -571,18 +563,6 @@ pub async fn load_worker_config(
config.worker_tags = Some(all_tags);
}
if let Some(force_worker_tags) = std::env::var("FORCE_WORKER_TAGS")
.ok()
.filter(|x| !x.is_empty())
.map(|x| x.split(',').map(|x| x.to_string()).collect::<Vec<String>>())
{
tracing::info!(
"Detected FORCE_WORKER_TAGS, forcing worker tags to: {:#?}",
force_worker_tags
);
config.worker_tags = Some(force_worker_tags);
}
// set worker_tags using default if none. If priority tags is set, compute the sorted priority tags as well
let worker_tags = config
.worker_tags
+4 -18
View File
@@ -1967,11 +1967,6 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
* or suspend_until <= now() if it has timed out */
let query = WORKER_SUSPENDED_PULL_QUERY.read().await;
if query.is_empty() {
tracing::warn!("No suspended pull queries available");
return Ok(None);
}
let r = if suspend_first {
// tracing::info!("Pulling job with query: {}", query);
sqlx::query_as::<_, QueuedJob>(&query)
@@ -1987,11 +1982,6 @@ async fn pull_single_job_and_mark_as_running_no_concurrency_limit<
let queries = WORKER_PULL_QUERIES.read().await;
if queries.is_empty() {
tracing::warn!("No pull queries available");
return Ok(None);
}
for query in queries.iter() {
// tracing::info!("Pulling job with query: {}", query);
let r = sqlx::query_as::<_, QueuedJob>(query)
@@ -2598,7 +2588,6 @@ impl DecodeQueries {
#[derive(Deserialize)]
pub struct RequestQuery {
pub raw: Option<bool>,
pub wrap_body: Option<bool>,
pub include_header: Option<String>,
}
@@ -2633,14 +2622,13 @@ impl PushArgsOwned {
async fn from_json(
mut extra: HashMap<String, Box<RawValue>>,
use_raw: bool,
force_wrap_body: bool,
str: String,
) -> Result<Self, Response> {
if use_raw {
extra.insert("raw_string".to_string(), to_raw_value(&str));
}
let wrap_body = force_wrap_body || str.len() > 0 && str.chars().next().unwrap() != '{';
let wrap_body = str.len() > 0 && str.chars().next().unwrap() != '{';
if wrap_body {
let args = serde_json::from_str::<Option<Box<RawValue>>>(&str)
@@ -2745,7 +2733,7 @@ where
req: Request<axum::body::Body>,
_state: &S,
) -> Result<Self, Self::Rejection> {
let (content_type, mut extra, use_raw, wrap_body) = {
let (content_type, mut extra, use_raw) = {
let headers_map = req.headers();
let content_type_header = headers_map.get(CONTENT_TYPE);
let content_type = content_type_header.and_then(|value| value.to_str().ok());
@@ -2757,8 +2745,7 @@ where
extra.extend(queries);
}
let raw = query.raw.as_ref().is_some_and(|x| *x);
let wrap_body = query.wrap_body.as_ref().is_some_and(|x| *x);
(content_type, extra, raw, wrap_body)
(content_type, extra, raw)
};
if content_type.is_none() || content_type.unwrap().starts_with("application/json") {
@@ -2768,7 +2755,7 @@ where
let str = String::from_utf8(bytes.to_vec())
.map_err(|e| Error::BadRequest(format!("invalid utf8: {}", e)).into_response())?;
PushArgsOwned::from_json(extra, use_raw, wrap_body, str).await
PushArgsOwned::from_json(extra, use_raw, str).await
} else if content_type
.unwrap()
.starts_with("application/cloudevents+json")
@@ -3349,7 +3336,6 @@ pub async fn push<'c, 'd, R: rsmq_async::RsmqConnection + Send + 'c>(
},
),
stop_after_if: None,
stop_after_all_iters_if: None,
summary: None,
suspend: None,
mock: None,
@@ -571,7 +571,6 @@ pub fn copy_recursively(
}
}
}
let original = entry.path();
if filetype.is_dir() {
@@ -1291,10 +1290,6 @@ pub async fn get_common_bun_proc_envs(base_internal_url: &str) -> HashMap<String
String::from("BUN_INSTALL_CACHE_DIR"),
BUN_CACHE_DIR.to_string(),
),
(
String::from("BUN_RUNTIME_TRANSPILER_CACHE_PATH"),
"0".to_string(),
),
]);
if let Some(ref node_path) = NODE_PATH.as_ref() {
+4 -72
View File
@@ -232,27 +232,22 @@ pub async fn update_flow_status_after_job_completion_internal<
// "UPDATE FLOW STATUS 2: {module_index:#?} {module_status:#?} {old_status:#?} "
// );
let (is_loop, skip_loop_failures, parallelism) = if matches!(
let (skip_loop_failures, parallelism) = if matches!(
module_status,
FlowStatusModule::InProgress { iterator: Some(_), .. }
) {
let (loop_failures, parallelism) =
compute_skip_loop_failures_and_parallelism(flow, old_status.step, db).await?;
(true, loop_failures.unwrap_or(false), parallelism)
(loop_failures.unwrap_or(false), parallelism)
} else {
(false, false, None)
(false, None)
};
let is_branch_all = matches!(
module_status,
FlowStatusModule::InProgress { branchall: Some(_), .. }
);
// 0 length flows are not failure steps
let is_failure_step =
old_status.step >= old_status.modules.len() as i32 && old_status.modules.len() > 0;
let (mut stop_early, mut skip_if_stop_early, continue_on_error) = if let Some(se) =
let (mut stop_early, skip_if_stop_early, continue_on_error) = if let Some(se) =
stop_early_override
{
//do not stop early if module is a flow step
@@ -285,18 +280,7 @@ pub async fn update_flow_status_after_job_completion_internal<
.map_err(|e| Error::InternalErr(format!("retrieval of stop_early_expr from state: {e:#}")))?;
let stop_early = success
&& !is_branch_all
&& if let Some(expr) = r.stop_early_expr.clone() {
let all_iters = match &module_status {
FlowStatusModule::InProgress { flow_jobs: Some(flow_jobs), .. }
if expr.contains("all_iters") =>
{
Some(Arc::new(
retrieve_flow_jobs_results(db, w_id, flow_jobs).await?,
))
}
_ => None,
};
compute_bool_from_expr(
expr,
Marc::new(
@@ -306,7 +290,6 @@ pub async fn update_flow_status_after_job_completion_internal<
.to_owned(),
),
result.clone(),
all_iters,
None,
Some(client),
None,
@@ -752,50 +735,6 @@ pub async fn update_flow_status_after_job_completion_internal<
_ => result.clone(),
};
match &new_status {
Some(FlowStatusModule::Success { .. }) if is_loop || is_branch_all => {
let r_after_all_iters = sqlx::query_as::<_, SkipIfStopped>(
"SELECT
raw_flow->'modules'->$1::int->'stop_after_all_iters_if'->>'expr' as stop_early_expr,
(raw_flow->'modules'->$1::int->'stop_after_all_iters_if'->>'skip_if_stopped')::bool as skip_if_stopped,
NULL as continue_on_error,
args
FROM queue
WHERE id = $2"
)
.bind(old_status.step)
.bind(flow)
.fetch_one(db)
.await
.map_err(|e| Error::InternalErr(format!("retrieval of stop_early_expr from state: {e:#}")))?;
if let Some(expr) = r_after_all_iters.stop_early_expr {
let should_stop = compute_bool_from_expr(
expr,
Marc::new(
r_after_all_iters
.args
.map(|x| x.0)
.unwrap_or_else(|| serde_json::from_str("{}").unwrap())
.to_owned(),
),
nresult.clone(),
None,
None,
Some(client),
None,
None,
)
.await?;
if should_stop {
stop_early = should_stop;
skip_if_stop_early = r_after_all_iters.skip_if_stopped.unwrap_or(false);
}
}
}
_ => {}
}
if old_status.retry.fail_count > 0
&& matches!(&new_status, Some(FlowStatusModule::Success { .. }))
{
@@ -1247,7 +1186,6 @@ async fn compute_bool_from_expr(
expr: String,
flow_args: Marc<HashMap<String, Box<RawValue>>>,
result: Arc<Box<RawValue>>,
all_iters: Option<Arc<Box<RawValue>>>,
by_id: Option<IdContext>,
client: Option<&AuthedClient>,
resumes: Option<(Arc<Box<RawValue>>, Arc<Box<RawValue>>, Arc<Box<RawValue>>)>,
@@ -1255,9 +1193,6 @@ async fn compute_bool_from_expr(
) -> error::Result<bool> {
let mut context = HashMap::with_capacity(if resumes.is_some() { 7 } else { 3 });
context.insert("result".to_string(), result.clone());
if let Some(all_iters) = all_iters {
context.insert("all_iters".to_string(), all_iters);
}
context.insert("previous_result".to_string(), result.clone());
if let Some(resumes) = resumes {
@@ -1633,7 +1568,6 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
arc_flow_job_args.clone(),
Arc::new(to_raw_value(&json!("{}"))),
None,
None,
Some(client),
None,
Some(vec![(
@@ -3048,7 +2982,6 @@ async fn compute_next_flow_transform(
b.expr.to_string(),
arc_flow_job_args.clone(),
arc_last_job_result.clone(),
None,
Some(idcontext.clone()),
Some(client),
Some((resumes.clone(), resume.clone(), approvers.clone())),
@@ -3325,7 +3258,6 @@ fn is_simple_modules(modules: &Vec<FlowModule>, flow: &FlowValue) -> bool {
&& modules[0].cache_ttl.is_none()
&& modules[0].retry.is_none()
&& modules[0].stop_after_if.is_none()
&& modules[0].stop_after_all_iters_if.is_none()
&& (modules[0].mock.is_none() || modules[0].mock.as_ref().is_some_and(|m| !m.enabled))
&& flow.failure_module.is_none();
is_simple
+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.383.0";
export const VERSION = "v1.381.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+33 -18
View File
@@ -12,7 +12,7 @@ import {
yamlParse,
} from "./deps.ts";
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
import { resolve, track_job } from "./script.ts";
import { findGlobalDeps, resolve, track_job } from "./script.ts";
import { defaultFlowDefinition } from "./bootstrap/flow_bootstrap.ts";
import { generateFlowLockInternal } from "./metadata.ts";
import { SyncOptions, mergeConfigWithConfigFile } from "./conf.ts";
@@ -115,20 +115,14 @@ export async function pushFlow(
});
} else {
log.info(colors.bold.yellow("Creating new flow..."));
try {
await FlowService.createFlow({
workspace: workspace,
requestBody: {
path: remotePath.replaceAll(SEP, "/"),
deployment_message: message,
...localFlow,
},
});
} catch (e) {
throw new Error(
`Failed to create flow ${remotePath}: ${e.body ?? e.message}`
);
}
await FlowService.createFlow({
workspace: workspace,
requestBody: {
path: remotePath.replaceAll(SEP, "/"),
deployment_message: message,
...localFlow,
},
});
}
}
@@ -243,8 +237,15 @@ async function generateLocks(
await requireLogin(opts);
opts = await mergeConfigWithConfigFile(opts);
if (folder) {
const globalDeps = await findGlobalDeps();
// read script metadata file
await generateFlowLockInternal(folder, false, workspace);
await generateFlowLockInternal(
folder,
false,
workspace,
globalDeps,
opts.defaultTs
);
} else {
const ignore = await ignoreF(opts);
const elems = Object.keys(
@@ -264,8 +265,16 @@ async function generateLocks(
).map((x) => x.substring(0, x.lastIndexOf(SEP)));
let hasAny = false;
const globalDeps = await findGlobalDeps();
for (const folder of elems) {
const candidate = await generateFlowLockInternal(folder, true, workspace);
const candidate = await generateFlowLockInternal(
folder,
true,
workspace,
globalDeps,
opts.defaultTs
);
if (candidate) {
hasAny = true;
log.info(colors.green(`+ ${candidate}`));
@@ -287,7 +296,13 @@ async function generateLocks(
return;
}
for (const folder of elems) {
await generateFlowLockInternal(folder, false, workspace);
await generateFlowLockInternal(
folder,
false,
workspace,
globalDeps,
opts.defaultTs
);
}
}
}
+2 -1
View File
@@ -92,7 +92,8 @@ export async function pushFolder(
},
});
} catch (e) {
throw Error(`Failed to create folder ${name}: ${e.body ?? e.message}`);
console.error(e.body);
throw e;
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ addEventListener("error", (event) => {
}
});
export const VERSION = "v1.383.0";
export const VERSION = "v1.381.0";
let command: any = new Command()
.name("wmill")
+115 -44
View File
@@ -46,7 +46,7 @@ import { FlowFile, replaceInlineScripts } from "./flow.ts";
export async function generateAllMetadata() {}
function findClosestRawReqs(
lang: "bun" | "python3" | "php" | undefined,
lang: "bun" | "python3" | "php" | string | undefined,
remotePath: string,
globalDeps: GlobalDeps
): string | undefined {
@@ -84,20 +84,56 @@ function findClosestRawReqs(
}
const TOP_HASH = "__flow_hash";
async function generateFlowHash(folder: string) {
async function generateFlowHash(
folder: string,
globalDeps: GlobalDeps,
defaultTs: "bun" | "deno" | undefined
): Promise<{
hashes: Record<string, string>;
pathsWithRawReqs: string[];
}> {
const elems = await FSFSElement(path.join(Deno.cwd(), folder), []);
const hashes: Record<string, string> = {};
const pathsWithRawReqs: string[] = [];
for await (const f of elems.getChildren()) {
if (exts.some((e) => f.path.endsWith(e))) {
hashes[f.path] = await generateHash(await f.getContentText());
const found_ext = exts.find((e) => f.path.endsWith(e));
if (found_ext) {
const lang = inferContentTypeFromFilePath(f.path, defaultTs);
// log.info(remote_path + "/script" + ext);
const raw = findClosestRawReqs(
lang as "bun" | "php" | "python3" | undefined,
path.join(folder, f.path),
globalDeps
);
// if (raw) {
// rawReqs[ext] = raw;
// }
let text = await f.getContentText();
if (raw) {
text = raw + text;
pathsWithRawReqs.push(f.path);
}
hashes[f.path] = await generateHash(text);
}
}
return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) };
return {
hashes: {
...hashes,
[TOP_HASH]: await generateHash(JSON.stringify(hashes)),
},
pathsWithRawReqs,
};
}
export async function generateFlowLockInternal(
folder: string,
dryRun: boolean,
workspace: Workspace,
globalDeps: GlobalDeps,
defaultTs: "bun" | "deno" | undefined,
justUpdateMetadataLock?: boolean
): Promise<string | undefined> {
if (folder.endsWith(SEP)) {
@@ -110,7 +146,11 @@ export async function generateFlowLockInternal(
log.info(`Generating lock for flow ${folder} at ${remote_path}`);
}
let hashes = await generateFlowHash(folder);
let { hashes, pathsWithRawReqs } = await generateFlowHash(
folder,
globalDeps,
defaultTs
);
const conf = await readLockfile();
if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) {
@@ -126,6 +166,7 @@ export async function generateFlowLockInternal(
await Deno.readTextFile(folder! + SEP + "flow.yaml")
) as FlowFile;
const fullReqs: Partial<Record<ScriptLanguage, string>> = {};
if (!justUpdateMetadataLock) {
const changedScripts = [];
//find hashes that do not correspond to previous hashes
@@ -139,6 +180,19 @@ export async function generateFlowLockInternal(
}
log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`);
for (const script of changedScripts) {
if (pathsWithRawReqs.includes(script)) {
const lang = inferContentTypeFromFilePath(script, defaultTs);
log.info(`Inline script ${script} has raw requirements`);
let req = fullReqs[lang];
if (!req) {
const rawReq = findClosestRawReqs(lang, script, globalDeps) ?? "";
const fullReq = await getLockfileFromWindmill();
fullReqs[lang] = req;
}
}
}
replaceInlineScripts(
flowValue.value.modules,
folder + SEP!,
@@ -161,7 +215,7 @@ export async function generateFlowLockInternal(
});
}
hashes = await generateFlowHash(folder);
hashes = (await generateFlowHash(folder, globalDeps, defaultTs)).hashes;
for (const [path, hash] of Object.entries(hashes)) {
await updateMetadataGlobalLock(folder, hash, path);
@@ -308,27 +362,13 @@ export async function updateScriptSchema(
metadataContent.schema = newSchema;
}
async function updateScriptLock(
async function getLockfileFromWindmill(
workspace: Workspace,
scriptContent: string,
language: ScriptLanguage,
remotePath: string,
metadataContent: Record<string, any>,
rawDeps: string | undefined
): Promise<void> {
if (
!(
language == "bun" ||
language == "python3" ||
language == "go" ||
language == "deno" ||
language == "php"
)
) {
return;
}
// generate the script lock running a dependency job in Windmill and update it inplace
// TODO: update this once the client is released
): Promise<{ lock: string | undefined }> {
const rawResponse = await fetch(
`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/dependencies`,
{
@@ -354,27 +394,7 @@ async function updateScriptLock(
let responseText = "reading response failed";
try {
responseText = await rawResponse.text();
const response = JSON.parse(responseText);
const lock = response.lock;
if (lock === undefined) {
throw new Error(
`Failed to generate lockfile. Full response was: ${JSON.stringify(
response
)}`
);
}
const lockPath = remotePath + ".script.lock";
if (lock != "") {
await Deno.writeTextFile(lockPath, lock);
metadataContent.lock = "!inline " + lockPath.replaceAll(SEP, "/");
} else {
try {
if (await Deno.stat(lockPath)) {
await Deno.remove(lockPath);
}
} catch {}
metadataContent.lock = "";
}
return JSON.parse(responseText);
} catch (e) {
throw new Error(
`Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}`
@@ -382,6 +402,57 @@ async function updateScriptLock(
}
}
async function updateScriptLock(
workspace: Workspace,
scriptContent: string,
language: ScriptLanguage,
remotePath: string,
metadataContent: Record<string, any>,
rawDeps: string | undefined
): Promise<void> {
if (
!(
language == "bun" ||
language == "python3" ||
language == "go" ||
language == "deno" ||
language == "php"
)
) {
return;
}
// generate the script lock running a dependency job in Windmill and update it inplace
// TODO: update this once the client is released
const response = getLockfileFromWindmill(
workspace,
scriptContent,
language,
remotePath,
rawDeps
);
const lock = response.lock;
if (lock === undefined) {
throw new Error(
`Failed to generate lockfile. Full response was: ${JSON.stringify(
response
)}`
);
}
const lockPath = remotePath + ".script.lock";
if (lock != "") {
await Deno.writeTextFile(lockPath, lock);
metadataContent.lock = "!inline " + lockPath.replaceAll(SEP, "/");
} else {
try {
if (await Deno.stat(lockPath)) {
await Deno.remove(lockPath);
}
} catch {}
metadataContent.lock = "";
}
}
export async function updateFlow(
workspace: Workspace,
flow_value: FlowValue,
+5 -13
View File
@@ -366,19 +366,11 @@ async function createScript(
workspace: Workspace
) {
if (!bundleContent) {
try {
// no parent hash
await ScriptService.createScript({
workspace: workspaceId,
requestBody: body,
});
} catch (e) {
throw Error(
`Script creation for ${body.path} with parent ${
body.parent_hash
} was not successful: ${e.body ?? e.message}`
);
}
// no parent hash
await ScriptService.createScript({
workspace: workspaceId,
requestBody: body,
});
} else {
const form = new FormData();
form.append("script", JSON.stringify(body));
+8 -1
View File
@@ -1070,7 +1070,14 @@ export async function pull(opts: GlobalOptions & SyncOptions) {
}
for (const change of changedFlows) {
log.info(`Updating lock for flow ${change}`);
await generateFlowLockInternal(change, false, workspace, true);
await generateFlowLockInternal(
change,
false,
workspace,
globalDeps,
opts.defaultTs,
true
);
}
log.info(
colors.bold.green.underline(
+25 -48
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.383.0",
"version": "1.381.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.383.0",
"version": "1.381.0",
"license": "AGPL-3.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",
@@ -16,7 +16,7 @@
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.9.9",
"@windmill-labs/svelte-dnd-action": "latest",
"@xyflow/svelte": "^0.1.15",
"@xyflow/svelte": "^0.1.14",
"ag-charts-community": "^9.0.1",
"ag-charts-enterprise": "^9.0.1",
"ag-grid-community": "^31.0.0",
@@ -2294,8 +2294,7 @@
"node_modules/@types/pug": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.9.tgz",
"integrity": "sha512-Yg4LkgFYvn1faISbDNWmcAC1XoDT8IoMUFspp5mnagKk+UvD2N0IWt5A7GRdMubsNWqgCLmrkf8rXkzNqb4szA==",
"dev": true
"integrity": "sha512-Yg4LkgFYvn1faISbDNWmcAC1XoDT8IoMUFspp5mnagKk+UvD2N0IWt5A7GRdMubsNWqgCLmrkf8rXkzNqb4szA=="
},
"node_modules/@types/semver": {
"version": "7.5.5",
@@ -2516,24 +2515,23 @@
}
},
"node_modules/@xyflow/svelte": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-0.1.15.tgz",
"integrity": "sha512-QGM7rlBkoLG6jhHA9U8ZyMtqL62Cpn/T4OElTAUuKTgwuLTdzWqwSCdXpFTs8T6eiJD/Y4jYbVYsmQKDaB9rOw==",
"license": "MIT",
"version": "0.1.14",
"resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-0.1.14.tgz",
"integrity": "sha512-DdlFz+JKW1sQt8Q8O1DoSsroTvFk1RzmpcEZcGARt1REbnLy4BYG/03piaiclaC4iHK6feaPDvxpF057CkMCTw==",
"dependencies": {
"@svelte-put/shortcut": "^3.1.0",
"@xyflow/system": "0.0.38",
"classcat": "^5.0.4"
"@xyflow/system": "0.0.37",
"classcat": "^5.0.4",
"svelte-preprocess": "^5.1.3"
},
"peerDependencies": {
"svelte": "^3.0.0 || ^4.0.0"
}
},
"node_modules/@xyflow/system": {
"version": "0.0.38",
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.38.tgz",
"integrity": "sha512-auJU8djbT59S5Afb9lFds1lQJvKIb0zUoHhO+il/ogDDG5BbFds6D8g5a8Q3oHdyR6dy0TaD1oZq9s7Ydhn41g==",
"license": "MIT",
"version": "0.0.37",
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.37.tgz",
"integrity": "sha512-hSIhezhxgftPUpC+xiQVIorcRILZUOWlLjpYPTyGWRu8s4RJvM4GqvrsFmD5OnMKXLgpU7/PqqUibDVO67oWQQ==",
"dependencies": {
"@types/d3-drag": "^3.0.7",
"@types/d3-selection": "^3.0.10",
@@ -3006,7 +3004,6 @@
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"dev": true,
"engines": {
"node": "*"
}
@@ -3346,8 +3343,7 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"devOptional": true
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/confbox": {
"version": "0.1.6",
@@ -3959,7 +3955,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
"integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -4186,8 +4181,7 @@
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
"dev": true
"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg=="
},
"node_modules/esbuild": {
"version": "0.21.5",
@@ -4881,8 +4875,7 @@
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"devOptional": true
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.3",
@@ -5000,7 +4993,6 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"devOptional": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -5032,7 +5024,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"devOptional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5042,7 +5033,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"devOptional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -5159,8 +5149,7 @@
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/graphemer": {
"version": "1.4.0",
@@ -5468,7 +5457,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"devOptional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -5477,8 +5465,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"devOptional": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ini": {
"version": "1.3.8",
@@ -5958,7 +5945,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"dev": true,
"devOptional": true,
"engines": {
"node": ">=10"
}
@@ -7037,7 +7024,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"engines": {
"node": ">=4"
}
@@ -7126,7 +7112,6 @@
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"dependencies": {
"minimist": "^1.2.6"
},
@@ -7239,7 +7224,7 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"devOptional": true,
"funding": [
{
"type": "github",
@@ -7548,7 +7533,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"devOptional": true,
"dependencies": {
"wrappy": "1"
}
@@ -7745,7 +7729,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"devOptional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -7833,7 +7816,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"dev": true
"devOptional": true
},
"node_modules/picomatch-browser": {
"version": "2.2.6",
@@ -7923,7 +7906,7 @@
"version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
"dev": true,
"devOptional": true,
"funding": [
{
"type": "opencollective",
@@ -8098,7 +8081,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz",
"integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==",
"dev": true,
"devOptional": true,
"dependencies": {
"lilconfig": "^2.0.5",
"yaml": "^2.1.1"
@@ -9065,7 +9048,6 @@
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
"integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
"dev": true,
"dependencies": {
"es6-promise": "^3.1.2",
"graceful-fs": "^4.1.3",
@@ -9077,7 +9059,6 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
@@ -9247,7 +9228,6 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
"integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.14",
"buffer-crc32": "^0.2.5",
@@ -9905,7 +9885,6 @@
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz",
"integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@types/pug": "^2.0.6",
@@ -9967,7 +9946,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"dependencies": {
"min-indent": "^1.0.0"
},
@@ -10347,7 +10325,7 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10810,8 +10788,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"devOptional": true
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/write-file-atomic": {
"version": "5.0.1",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.383.0",
"version": "1.381.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -97,7 +97,7 @@
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.9.9",
"@windmill-labs/svelte-dnd-action": "latest",
"@xyflow/svelte": "^0.1.15",
"@xyflow/svelte": "^0.1.14",
"ag-charts-community": "^9.0.1",
"ag-charts-enterprise": "^9.0.1",
"ag-grid-community": "^31.0.0",
+1 -1
View File
@@ -1 +1 @@
export let base: string = import.meta.env.VITE_BASE_URL ?? ''
export const base: string = import.meta.env.VITE_BASE_URL ?? ''
+38 -49
View File
@@ -45,7 +45,6 @@
import CodeCompletionStatus from './copilot/CodeCompletionStatus.svelte'
import Popover from './Popover.svelte'
import ResourceEditorDrawer from './ResourceEditorDrawer.svelte'
import type { EditorBarUi } from './custom_ui'
export let lang: SupportedLanguage | 'bunnative' | undefined
export let editor: Editor | undefined
@@ -69,7 +68,6 @@
export let args: Record<string, any>
export let noHistory = false
export let saveToWorkspace = false
export let customUi: EditorBarUi = {}
let contextualVariablePicker: ItemPicker
let variablePicker: ItemPicker
@@ -315,7 +313,6 @@
{/if}
</DrawerContent>
</Drawer>
<ItemPicker
bind:this={contextualVariablePicker}
pickCallback={(path, name) => {
@@ -516,7 +513,7 @@ $res = json_decode(curl_exec($ch));`)
class="rounded-full w-2 h-2 mx-2 {validCode ? 'bg-green-300' : 'bg-red-300'}"
/>
<div class="flex items-center gap-0.5">
{#if showContextVarPicker && customUi?.contextVar != false}
{#if showContextVarPicker}
<Button
title="Add context variable"
color="light"
@@ -529,7 +526,7 @@ $res = json_decode(curl_exec($ch));`)
>+Context Var
</Button>
{/if}
{#if showVarPicker && customUi?.variable != false}
{#if showVarPicker}
<Button
title="Add variable"
color="light"
@@ -559,7 +556,7 @@ $res = json_decode(curl_exec($ch));`)
</Button>
{/if}
{#if showResourceTypePicker && customUi?.type != false}
{#if showResourceTypePicker}
<Button
title="Add resource type"
btnClasses="!font-medium text-tertiary"
@@ -587,41 +584,37 @@ $res = json_decode(curl_exec($ch));`)
Reset
</Button>
{#if customUi?.assistants != false}
{#if lang == 'deno' || lang == 'python3' || lang == 'go' || lang == 'bash'}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
on:click={() => editor?.reloadWebsocket()}
startIcon={{
icon: RotateCw,
classes: websocketAlive[lang] == false ? 'animate-spin' : ''
}}
title="Reload assistants"
>
{#if !iconOnly}
Assistants
{#if lang == 'deno' || lang == 'python3' || lang == 'go' || lang == 'bash'}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
on:click={() => editor?.reloadWebsocket()}
startIcon={{
icon: RotateCw,
classes: websocketAlive[lang] == false ? 'animate-spin' : ''
}}
title="Reload assistants"
>
{#if !iconOnly}
Assistants
{/if}
<span class="-my-1">
{#if lang == 'deno'}
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
{:else if lang == 'go'}
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
{:else if lang == 'python3'}
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
<span class={websocketAlive.ruff ? 'green' : 'text-red-700'}>Ruff</span>)
{:else if lang == 'bash'}
(<span class={websocketAlive.shellcheck ? 'green' : 'text-red-700'}>Shellcheck</span>)
{/if}
<span class="-my-1">
{#if lang == 'deno'}
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
{:else if lang == 'go'}
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
{:else if lang == 'python3'}
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
<span class={websocketAlive.ruff ? 'green' : 'text-red-700'}>Ruff</span>)
{:else if lang == 'bash'}
(<span class={websocketAlive.shellcheck ? 'green' : 'text-red-700'}>Shellcheck</span
>)
{/if}
</span>
</Button>
{/if}
</span>
</Button>
{/if}
{#if collabMode && customUi?.multiplayer != false}
{#if collabMode}
<div class="flex items-center px-3">
<Toggle
options={{ right: '' }}
@@ -655,15 +648,11 @@ $res = json_decode(curl_exec($ch));`)
</div>
{/if}
{#if customUi?.autoformatting != false}
<FormatOnSave />
{/if}
{#if customUi?.aiGen != false}
<ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} />
{/if}
{#if customUi?.aiFix != false}
<CodeCompletionStatus />
{/if}
<FormatOnSave />
<ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} />
<CodeCompletionStatus />
</div>
</div>
@@ -682,7 +671,7 @@ $res = json_decode(curl_exec($ch));`)
History
</Button>
{/if}
{#if SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS && customUi?.library != false}
{#if SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
+15 -21
View File
@@ -80,7 +80,7 @@
import { twMerge } from 'tailwind-merge'
import CustomPopover from './CustomPopover.svelte'
import Summary from './Summary.svelte'
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui'
import type { WhitelabelCustomUi } from './custom_ui'
export let initialPath: string = ''
export let pathStoreInit: string | undefined = undefined
@@ -96,7 +96,7 @@
})
| undefined = undefined
export let diffDrawer: DiffDrawer | undefined = undefined
export let customUi: FlowBuilderWhitelabelCustomUi = {}
export let customUi: WhitelabelCustomUi = {}
$: setContext('customUi', customUi)
@@ -205,16 +205,13 @@
draft?: Flow
}
let savedAtNewPath = false
if (newFlow) {
dispatch('saveInitial', $pathStore)
} else if (savedFlow?.draft_only && $pathStore !== initialPath) {
savedAtNewPath = true
initialPath = $pathStore
// this is so we can use the flow builder outside of sveltekit
dispatch('saveDraftOnlyAtNewPath', { path: $pathStore, selectedId: getSelectedId() })
}
dispatch('saveDraft', { path: $pathStore, savedAtNewPath, newFlow })
sendUserToast('Saved as draft')
} catch (error) {
sendUserToast(`Error while saving the flow as a draft: ${error.body || error.message}`, true)
@@ -403,8 +400,7 @@
testStepStore,
saveDraft,
initialPath,
flowInputsStore: writable<FlowInput>({}),
customUi
flowInputsStore: writable<FlowInput>({})
})
async function loadSchedule() {
@@ -490,20 +486,18 @@
onClick: () => void
}> = []
if (customUi.topBar?.extraDeployOptions != false) {
if (savedFlow?.draft_only === false || savedFlow?.draft_only === undefined) {
dropdownItems.push({
label: 'Exit & see details',
onClick: () => dispatch('details', $pathStore)
})
}
if (savedFlow?.draft_only === false || savedFlow?.draft_only === undefined) {
dropdownItems.push({
label: 'Exit & see details',
onClick: () => dispatch('details', $pathStore)
})
}
if (!newFlow) {
dropdownItems.push({
label: 'Fork',
onClick: () => window.open(`/flows/add?template=${initialPath}`)
})
}
if (!newFlow) {
dropdownItems.push({
label: 'Fork',
onClick: () => window.open(`/flows/add?template=${initialPath}`)
})
}
let flowCopilotContext: FlowCopilotContext = {
@@ -1029,7 +1023,7 @@
$: onCustomUiChange(customUi)
function onCustomUiChange(customUi: FlowBuilderWhitelabelCustomUi | undefined) {
function onCustomUiChange(customUi: WhitelabelCustomUi | undefined) {
moreItems = [
...(customUi?.topBar?.history != false
? [
@@ -6,7 +6,6 @@
import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
import FlowGraphV2 from './graph/FlowGraphV2.svelte'
import { dfs } from './flows/dfs'
export let flow: {
summary: string
@@ -14,7 +13,6 @@
value: FlowValue
schema?: any
}
export let overflowAuto = false
export let noSide = false
export let download = false
@@ -37,8 +35,7 @@
modules={flow?.value?.modules}
failureModule={flow?.value?.failure_module}
on:select={(e) => {
const mod = dfs(flow?.value?.modules ?? [], (m) => m).find((m) => m?.id === e?.detail)
stepDetail = mod ?? e.detail
stepDetail = e.detail
dispatch('select', stepDetail)
}}
/>
@@ -12,9 +12,6 @@
export let flowStateStore: Writable<FlowState> = writable({})
export let selectedJobStep: string | undefined = undefined
export let hideFlowResult = false
export let hideTimeline = false
export let hideDownloadInGraph = false
export let hideNodeDefinition = false
export let isOwner = false
export let wideResults = false
@@ -26,10 +23,7 @@
setContext<FlowStatusViewerContext>('FlowStatusViewer', {
flowStateStore,
suspendStatus,
retryStatus,
hideDownloadInGraph,
hideNodeDefinition,
hideTimeline
retryStatus
})
function loadOwner(path: string) {
@@ -32,14 +32,8 @@
const dispatch = createEventDispatcher()
let {
flowStateStore,
retryStatus,
suspendStatus,
hideDownloadInGraph,
hideTimeline,
hideNodeDefinition
} = getContext<FlowStatusViewerContext>('FlowStatusViewer')
let { flowStateStore, retryStatus, suspendStatus } =
getContext<FlowStatusViewerContext>('FlowStatusViewer')
export let jobId: string
export let workspaceId: string | undefined = undefined
@@ -782,7 +776,7 @@
{/if}
{/each}
</div>
{:else if innerModules.length > 0 && (job.raw_flow?.modules.length ?? 0) > 0}
{:else if innerModules.length > 0}
<ul class="w-full">
<h3 class="text-md leading-6 font-bold text-primary border-b mb-4 py-2">
Step-by-step
@@ -902,8 +896,6 @@
</li>
{/each}
</ul>
{:else}
<div class="p-2 text-tertiary text-sm italic">Empty flow</div>
{/if}
</div>
</div>
@@ -930,7 +922,7 @@
</div>
<FlowGraphV2
download={!hideDownloadInGraph}
download
minHeight={wrapperHeight}
success={jobId != undefined && isSuccess(job?.['success'])}
flowModuleStates={$localModuleStates}
@@ -970,14 +962,9 @@
class="border-l border-tertiary-inverse pt-1 overflow-auto min-h-[700px] flex flex-col z-0 h-full"
>
<Tabs bind:selected={rightColumnSelect}>
{#if !hideTimeline}
<Tab value="timeline"><span class="font-semibold text-md">Timeline</span></Tab>
{/if}
<Tab value="timeline"><span class="font-semibold text-md">Timeline</span></Tab>
<Tab value="node_status"><span class="font-semibold">Node status</span></Tab>
{#if !hideNodeDefinition}
<Tab value="node_definition"><span class="font-semibold">Node definition</span></Tab
>
{/if}
<Tab value="node_definition"><span class="font-semibold">Node definition</span></Tab>
{#if Object.keys(job?.flow_status?.user_states ?? {}).length > 0}
<Tab value="user_states"><span class="font-semibold">User States</span></Tab>
{/if}
+48 -7
View File
@@ -1,12 +1,17 @@
<script lang="ts">
import Highlight from 'svelte-highlight'
import json from 'svelte-highlight/languages/json'
import type { FlowValue } from '$lib/gen'
import { Tab, Tabs, TabContent } from './common'
import { Tab, Tabs, TabContent, Button } from './common'
import SchemaViewer from './SchemaViewer.svelte'
import FieldHeader from './FieldHeader.svelte'
import { copyToClipboard } from '../utils'
import FlowGraphViewer from './FlowGraphViewer.svelte'
import { Clipboard } from 'lucide-svelte'
import YAML from 'yaml'
import { yaml } from 'svelte-highlight/languages'
import HighlightTheme from './HighlightTheme.svelte'
import FlowViewerInner from './FlowViewerInner.svelte'
export let flow: {
summary: string
@@ -17,11 +22,19 @@
export let initialOpen: number | undefined = undefined
export let noSide = false
$: flowFiltered = {
summary: flow.summary,
description: flow.description,
value: flow.value,
schema: flow.schema
}
export let noGraph: boolean = false
export let tab: 'ui' | 'raw' | 'schema' = noGraph ? 'schema' : 'ui'
export let noSummary = false
export let noGraphDownload = false
let rawType: 'json' | 'yaml' = 'yaml'
let open: { [id: number]: boolean } = {}
if (initialOpen) {
@@ -74,12 +87,40 @@
{:else}
<div class="text-secondary text-xs italic mb-4">No inputs</div>
{/if}
<FlowGraphViewer download={!noGraphDownload} {noSide} {flow} overflowAuto />
<FlowGraphViewer download {noSide} {flow} overflowAuto />
</div>
</TabContent>
<TabContent value="raw">
<FlowViewerInner {flow} />
<TabContent value="raw"
><Tabs bind:selected={rawType} wrapperClass="mt-4">
<Tab value="yaml">YAML</Tab>
<Tab value="json">JSON</Tab>
<svelte:fragment slot="content">
<div class="relative pt-2">
<Button
on:click={() =>
copyToClipboard(
rawType === 'yaml'
? YAML.stringify(flowFiltered)
: JSON.stringify(flowFiltered, null, 4)
)}
color="light"
variant="border"
size="xs"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight
class="overflow-auto px-1"
language={rawType === 'yaml' ? yaml : json}
code={rawType === 'yaml'
? YAML.stringify(flowFiltered)
: JSON.stringify(flowFiltered, null, 4)}
/>
</div>
</svelte:fragment>
</Tabs>
</TabContent>
<TabContent value="schema">
<div class="my-4" />
@@ -1,132 +0,0 @@
<script lang="ts">
import Highlight from 'svelte-highlight'
import json from 'svelte-highlight/languages/json'
import type { FlowValue } from '$lib/gen'
import { Tab, Tabs, Button } from './common'
import { copyToClipboard } from '../utils'
import { ArrowDown, Clipboard } from 'lucide-svelte'
import YAML from 'yaml'
import { yaml } from 'svelte-highlight/languages'
import HighlightTheme from './HighlightTheme.svelte'
export let flow: {
summary: string
description?: string
value: FlowValue
schema?: any
}
$: flowFiltered = {
summary: flow.summary,
description: flow.description,
value: flow.value,
schema: flow.schema
}
let rawType: 'json' | 'yaml' = 'yaml'
function trimStringToLines(inputString: string, maxLines: number = 100): string {
const lines = inputString?.split('\n') ?? []
const linesToKeep = lines.slice(0, maxLines)
return linesToKeep.join('\n')
}
let code: string = ''
function computeCode() {
const str =
rawType === 'json' ? JSON.stringify(flowFiltered, null, 4) : YAML.stringify(flowFiltered)
const numberOfLines = str.split('\n').length
if (numberOfLines > maxLines) {
shouldDisplayLoadMore = true
}
code = str
}
let shouldDisplayLoadMore = false
$: flowFiltered && rawType && computeCode()
let maxLines = 100
</script>
<HighlightTheme />
<div>
<Tabs
bind:selected={rawType}
on:selected={() => {
maxLines = 100
}}
>
<Tab value="yaml">YAML</Tab>
<Tab value="json">JSON</Tab>
<svelte:fragment slot="content">
<div class="relative pt-2">
<Button
on:click={() =>
copyToClipboard(
rawType === 'yaml'
? YAML.stringify(flowFiltered)
: JSON.stringify(flowFiltered, null, 4)
)}
color="light"
variant="border"
size="xs"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<div class={shouldDisplayLoadMore ? 'code-container' : ''}>
<Highlight
class="overflow-auto px-1"
language={rawType === 'yaml' ? yaml : json}
code={trimStringToLines(code, maxLines)}
/>
</div>
{#if shouldDisplayLoadMore}
<Button
on:click={() => {
maxLines += 500
// If the code is less than the max lines, we don't need to show the button
if (maxLines >= code?.split('\n').length) {
shouldDisplayLoadMore = false
}
}}
color="light"
size="xs"
btnClasses="mb-2 mx-2"
startIcon={{ icon: ArrowDown }}
>
Show more
</Button>
{/if}
</div>
</svelte:fragment>
</Tabs>
</div>
<style>
.code-container {
position: relative;
overflow: hidden;
}
.code-container::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 100px;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgb(var(--color-surface)));
pointer-events: none;
}
</style>
@@ -67,7 +67,6 @@
import { createEventDispatcher } from 'svelte'
import CustomPopover from './CustomPopover.svelte'
import Summary from './Summary.svelte'
import type { ScriptBuilderWhitelabelCustomUi } from './custom_ui'
export let script: NewScript
export let fullyLoaded: boolean = true
@@ -82,7 +81,6 @@
export let disableHistoryChange = false
export let replaceStateFn: (url: string) => void = (url) =>
window.history.replaceState(null, '', url)
export let customUi: ScriptBuilderWhitelabelCustomUi = {}
let metadataOpen =
showMeta ||
@@ -425,7 +423,7 @@
function computeDropdownItems(initialPath: string) {
let dropdownItems: { label: string; onClick: () => void }[] =
initialPath != '' && customUi?.topBar?.extraDeployOptions != false
initialPath != ''
? [
{
label: 'Deploy & Stay here',
@@ -1052,34 +1050,32 @@
{$scheduleStore.cron ?? ''}
</Button>
{/if}
{#if customUi?.topBar?.path != false}
<div class="flex justify-start w-full border rounded-md overflow-hidden">
<div>
<button
on:click={async () => {
metadataOpen = true
}}
>
<Badge
color="gray"
class="center-center !bg-surface-secondary !text-tertiary !h-[28px] !w-[70px] rounded-none hover:!bg-surface-hover transition-all"
>
<Pen size={12} class="mr-2" /> Path
</Badge>
</button>
</div>
<input
type="text"
readonly
value={script.path}
size={script.path?.length || 50}
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none !border-0 !shadow-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
<div class="flex justify-start w-full border rounded-md overflow-hidden">
<div>
<button
on:click={async () => {
metadataOpen = true
}}
/>
>
<Badge
color="gray"
class="center-center !bg-surface-secondary !text-tertiary !h-[28px] !w-[70px] rounded-none hover:!bg-surface-hover transition-all"
>
<Pen size={12} class="mr-2" /> Path
</Badge>
</button>
</div>
{/if}
<input
type="text"
readonly
value={script.path}
size={script.path?.length || 50}
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none !border-0 !shadow-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
}}
/>
</div>
</div>
{#if $enterpriseLicense && initialPath != ''}
@@ -1110,19 +1106,17 @@
<span class="hidden lg:flex"> Diff </span>
</div>
</Button>
{#if customUi?.topBar?.settings != false}
<Button
color="light"
variant="border"
size="xs"
on:click={() => {
metadataOpen = true
}}
startIcon={{ icon: Settings }}
>
<span class="hidden lg:flex"> Settings </span>
</Button>
{/if}
<Button
color="light"
variant="border"
size="xs"
on:click={() => {
metadataOpen = true
}}
startIcon={{ icon: Settings }}
>
<span class="hidden lg:flex"> Settings </span>
</Button>
<Button
loading={loadingDraft}
size="xs"
@@ -1176,7 +1170,6 @@
</div>
<ScriptEditor
{customUi}
collabMode
edit={initialPath != ''}
on:format={() => {
@@ -23,7 +23,6 @@
import DiffEditor from './DiffEditor.svelte'
import { Clipboard, CornerDownLeft, Github, Play } from 'lucide-svelte'
import { setLicense } from '$lib/enterpriseUtils'
import type { ScriptEditorWhitelabelCustomUi } from './custom_ui'
// Exported
export let schema: Schema | any = emptySchema()
@@ -44,7 +43,6 @@
export let noHistory = false
export let saveToWorkspace = false
export let watchChanges = false
export let customUi: ScriptEditorWhitelabelCustomUi = {}
let websocketAlive = {
pyright: false,
@@ -245,7 +243,6 @@
setCollaborationMode()
}
}}
customUi={customUi?.editorBar}
collabLive={wsProvider?.shouldConnect}
{collabMode}
{validCode}
@@ -263,7 +260,7 @@
{noHistory}
{saveToWorkspace}
/>
{#if !noSyncFromGithub && customUi?.editorBar?.useVsCode != false}
{#if !noSyncFromGithub}
<div class="py-1">
<Button
target="_blank"
@@ -8,9 +8,7 @@
import FlowModuleScript from './flows/content/FlowModuleScript.svelte'
import { createEventDispatcher } from 'svelte'
import Button from './common/button/Button.svelte'
import { ExternalLink, Pencil, ArrowRight, X, Diff, Code } from 'lucide-svelte'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import { ExternalLink, Pencil, ArrowRight, X } from 'lucide-svelte'
const dispatch = createEventDispatcher()
@@ -21,7 +19,6 @@
let deploymentMsgUpdate: string | undefined = undefined
let selectedVersion: ScriptHistory | undefined = undefined
let selectedVersionIndex: number | undefined = undefined
let versions: ScriptHistory[] | undefined = undefined
let loading: boolean = false
@@ -56,9 +53,6 @@
}
loadVersions()
let showDiff: boolean = false
let previousHash: string | undefined = undefined
</script>
<Splitpanes class="!overflow-visible">
@@ -68,34 +62,17 @@
{#if !loading}
{#if versions && versions.length > 0}
<div class="flex gap-2 flex-col">
{#each versions ?? [] as version, versionIndex}
{#each versions ?? [] as version}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={classNames(
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer ',
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
selectedVersion?.script_hash == version.script_hash
? 'bg-surface-selected'
: '',
'hover:bg-surface-hover'
? 'bg-blue-100 text-blue-600'
: ''
)}
on:click={() => {
selectedVersion = version
selectedVersionIndex = versionIndex
if (showDiff && versions && selectedVersionIndex === versions.length - 1) {
showDiff = false
}
const availableVersions = versions?.slice(selectedVersionIndex + 1)
if (
previousHash &&
!availableVersions?.find((v) => v.script_hash === previousHash)
) {
previousHash = availableVersions?.[0]?.script_hash
}
deploymentMsgUpdate = undefined
deploymentMsgUpdateMode = false
}}
@@ -132,7 +109,7 @@
<div class="h-full w-full overflow-auto">
{#if selectedVersion}
{#key selectedVersion}
<div class="flex flex-col min-h-full">
<div class="flex flex-col">
<span class="flex flex-row text-sm p-2 text-tertiary">
{#if deploymentMsgUpdateMode}
<div class="flex w-full">
@@ -191,41 +168,7 @@
</button>
{/if}
</span>
{#if selectedVersionIndex !== undefined && versions?.slice(selectedVersionIndex + 1).length}
<div class="p-2 flex flex-row items-center gap-2 h-8">
<div class="w-min">
<ToggleButtonGroup bind:selected={showDiff}>
<ToggleButton light small value={false} label="Code" icon={Code} />
<ToggleButton light small value={true} label="Diff" icon={Diff} />
</ToggleButtonGroup>
</div>
{#if showDiff}
<div class="text-xs">Versions:</div>
<select bind:value={previousHash} class="!text-xs !w-40">
{#each versions?.slice(selectedVersionIndex + 1) ?? [] as version}
<option
value={version.script_hash}
selected={version.script_hash === selectedVersion.script_hash}
class="!text-xs"
>
{version.deployment_msg ?? version.script_hash}
</option>
{/each}
</select>
{/if}
</div>
{:else}
<div class="p-2 text-xs text-secondary"> No previous version found </div>
{/if}
<FlowModuleScript
showDate
path={scriptPath}
hash={selectedVersion.script_hash}
{previousHash}
{showDiff}
/>
<FlowModuleScript showDate path={scriptPath} hash={selectedVersion.script_hash} />
</div>
{/key}
{:else}
@@ -30,12 +30,6 @@
configuration
)
$: editorMode && onEditorMode()
function onEditorMode() {
autosize()
setTimeout(() => autosize(), 50)
}
const { app, worldStore, mode, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
@@ -137,7 +131,6 @@
el.style.cssText = 'height:auto; padding:0'
el.style.cssText = 'height:' + el.scrollHeight + 'px'
}
// console.log(el, el?.scrollHeight)
}, 0)
}
</script>
@@ -170,6 +163,7 @@
if (!editorMode) {
editorMode = true
document.getElementById(`text-${id}`)?.focus()
autosize()
}
}}
on:keydown|stopPropagation
@@ -178,7 +172,7 @@
<AlignWrapper {verticalAlignment}>
<textarea
class={twMerge(
'whitespace-pre-wrap !outline-none !border-0 !bg-transparent !resize-none !ring-0 !p-0',
'whitespace-pre-wrap !outline-none !border-0 !bg-transparent !resize-none !overflow-hidden !ring-0 !p-0',
css?.text?.class,
'wm-text',
classes,
@@ -220,7 +214,7 @@
class="flex flex-wrap gap-2 pb-0.5 w-full {$mode === 'dnd' &&
(componentInput?.type == 'template' || componentInput?.type == 'templatev2')
? 'cursor-text'
: 'overflow-auto'}"
: ''}"
>
<svelte:element
this={component}
@@ -35,12 +35,10 @@
let localColumnDefs = columnDefs
let lastTable = table
$: lastTable != undefined && table && onTableChange()
function onTableChange() {
$: {
if (table !== lastTable) {
lastTable = table
localColumnDefs = []
lastTable = table
}
}
@@ -136,12 +136,6 @@
}
}
function onNativeChange(e: Event) {
const target = e.target as HTMLSelectElement
const value = target.value
setValue(value)
}
function setValue(nvalue: any) {
let result: any = undefined
try {
@@ -237,46 +231,35 @@
}}
>
{#if Array.isArray(listItems) && listItems.every((x) => x && typeof x == 'object' && typeof x['label'] == 'string' && `value` in x)}
{#if resolvedConfig.nativeHtmlSelect}
<select class={css?.input?.class} style={css?.input?.style} on:change={onNativeChange}>
{#if resolvedConfig.placeholder}
<option value="" disabled selected>{resolvedConfig.placeholder}</option>
{/if}
{#each listItems as item (item.value)}
<option value={item.value} selected={item.value === value}>{item.label}</option>
{/each}
</select>
{:else}
<Select
inAppEditor={true}
--border-radius="0.250rem"
--clear-icon-color="#6b7280"
--border={$darkMode ? '1px solid #6b7280' : '1px solid #d1d5db'}
bind:filterText
on:filter={handleFilter}
on:clear={onClear}
on:change={onChange}
items={listItems}
listAutoWidth={resolvedConfig.fullWidth}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={($darkMode
? SELECT_INPUT_DEFAULT_STYLE.containerStylesDark
: SELECT_INPUT_DEFAULT_STYLE.containerStyles) + css?.input?.style}
{value}
class={css?.input?.class}
placeholder={resolvedConfig.placeholder}
disabled={resolvedConfig.disabled}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
}}
>
<svelte:fragment slot="item" let:item
>{#if resolvedConfig.create}{item.created ? 'Add new: ' : ''}{/if}{item.label}
</svelte:fragment>
</Select>
{/if}
<Select
inAppEditor={true}
--border-radius="0.250rem"
--clear-icon-color="#6b7280"
--border={$darkMode ? '1px solid #6b7280' : '1px solid #d1d5db'}
bind:filterText
on:filter={handleFilter}
on:clear={onClear}
on:change={onChange}
items={listItems}
listAutoWidth={resolvedConfig.fullWidth}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={($darkMode
? SELECT_INPUT_DEFAULT_STYLE.containerStylesDark
: SELECT_INPUT_DEFAULT_STYLE.containerStyles) + css?.input?.style}
{value}
class={css?.input?.class}
placeholder={resolvedConfig.placeholder}
disabled={resolvedConfig.disabled}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
}}
>
<svelte:fragment slot="item" let:item
>{#if resolvedConfig.create}{item.created ? 'Add new: ' : ''}{/if}{item.label}
</svelte:fragment>
</Select>
{:else}
<Popover notClickable placement="bottom" popupClass="!bg-surface border w-96">
<div
@@ -230,11 +230,6 @@
input: getCountInput(resourceValue, tableValue, dbType, columnDefs, whereClause),
id: x.id + '_count'
})
console.log(
x.id,
getCountInput(resourceValue, tableValue, dbType, columnDefs, whereClause),
columnDefs
)
r.push({
input: getInsertInput(tableValue, columnDefs, resourceValue, dbType),
id: x.id + '_insert'
@@ -2106,13 +2106,6 @@ This is a paragraph.
tooltip: 'Preselect first item in the options if no default value is set'
},
nativeHtmlSelect: {
type: 'static',
fieldType: 'boolean',
value: false,
tooltip: 'Use a native html select instead of the Windmill select component'
},
fullWidth: {
type: 'static',
fieldType: 'boolean',
+1 -38
View File
@@ -1,6 +1,6 @@
import type { SupportedLanguage } from '$lib/common'
export type FlowBuilderWhitelabelCustomUi = {
export type WhitelabelCustomUi = {
topBar?: {
path?: boolean
export?: boolean
@@ -8,18 +8,8 @@ export type FlowBuilderWhitelabelCustomUi = {
aiBuilder?: boolean
tutorials?: boolean
diff?: boolean
extraDeployOptions?: boolean
}
settingsPanel?: boolean
settingsTabs?: {
schedule?: boolean
sharedDiretory?: boolean
earlyStop?: boolean
earlyReturn?: boolean
workerGroup?: boolean
concurrency?: boolean
cache?: boolean
}
triggers?: boolean
flowNode?: boolean
hub?: boolean
@@ -28,31 +18,4 @@ export type FlowBuilderWhitelabelCustomUi = {
stepAdvancedSettings?: boolean
languages?: (SupportedLanguage | 'docker' | 'bunnative')[]
scriptFork?: boolean
editorBar?: EditorBarUi
}
export type EditorBarUi = {
contextVar?: boolean
variable?: boolean
type?: boolean
assistants?: boolean
multiplayer?: boolean
autoformatting?: boolean
aiGen?: boolean
aiFix?: boolean
library?: boolean
useVsCode?: boolean
}
export type ScriptEditorWhitelabelCustomUi = {
editorBar?: EditorBarUi
}
export type ScriptBuilderWhitelabelCustomUi = {
topBar?: {
path?: boolean
settings?: boolean
extraDeployOptions?: boolean
}
editorBar?: EditorBarUi
}
@@ -1,10 +1,13 @@
<script lang="ts">
import { Tabs, Tab, TabContent } from '$lib/components/common'
import { CalendarCheck2, MailIcon, Terminal, Webhook } from 'lucide-svelte'
import { Tabs, Tab, TabContent, Button } from '$lib/components/common'
import { copyToClipboard } from '$lib/utils'
import { CalendarCheck2, Clipboard, MailIcon, Terminal, Webhook } from 'lucide-svelte'
import { Highlight } from 'svelte-highlight'
import { yaml } from 'svelte-highlight/languages'
import json from 'svelte-highlight/languages/json'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import YAML from 'yaml'
import HighlightTheme from '../HighlightTheme.svelte'
import FlowViewerInner from '../FlowViewerInner.svelte'
export let triggerSelected: 'webhooks' | 'email' | 'schedule' | 'cli' = 'webhooks'
export let flow_json: any | undefined = undefined
@@ -14,6 +17,8 @@
export let selected: string
let rawType: 'json' | 'yaml' = 'yaml'
$: if (hasStepDetails) {
selected = 'flow_step'
}
@@ -92,7 +97,36 @@
</Splitpanes>
</TabContent>
<TabContent value="raw" class="flex flex-col flex-1 h-full overflow-auto">
<FlowViewerInner flow={flow_json} />
<Tabs bind:selected={rawType} wrapperClass="mt-4">
<Tab value="yaml">YAML</Tab>
<Tab value="json">JSON</Tab>
<svelte:fragment slot="content">
<div class="relative pt-2">
<Button
on:click={() =>
copyToClipboard(
rawType === 'yaml'
? YAML.stringify(flow_json)
: JSON.stringify(flow_json, null, 4)
)}
color="light"
variant="border"
size="xs"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight
class="overflow-auto px-1"
language={rawType === 'yaml' ? yaml : json}
code={rawType === 'yaml'
? YAML.stringify(flow_json)
: JSON.stringify(flow_json, null, 4)}
/>
</div>
</svelte:fragment>
</Tabs>
</TabContent>
<TabContent value="flow_step" class="flex flex-col flex-1 h-full">
<slot name="flow_step" />
@@ -60,10 +60,10 @@
}
function computeFlowWebhooks(path: string) {
let webhooksBase = `${$page.url.origin}${base}/api/w/${$workspaceStore}/jobs`
let base = `${$page.url.origin}/api/w/${$workspaceStore}/jobs`
let urlAsync = `${webhooksBase}/run/f/${path}`
let urlSync = `${webhooksBase}/run_wait_result/f/${path}`
let urlAsync = `${base}/run/f/${path}`
let urlSync = `${base}/run_wait_result/f/${path}`
return {
async: {
path: urlAsync
@@ -91,7 +91,6 @@
on:close={() => {
drawer?.closeDrawer()
}}
noPadding
>
<Splitpanes class="!overflow-visible">
<Pane size={20}>
@@ -17,7 +17,7 @@
import { defaultScriptLanguages, processLangs } from '$lib/scripts'
import type { SupportedLanguage } from '$lib/common'
import DefaultScripts from '$lib/components/DefaultScripts.svelte'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import type { WhitelabelCustomUi } from '$lib/components/custom_ui'
export let failureModule: boolean
export let shouldDisableTriggerScripts: boolean = false
@@ -55,7 +55,7 @@
return kind == 'script' && !failureModule
}
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
let customUi: undefined | WhitelabelCustomUi = getContext('customUi')
</script>
<div class="p-4 h-full flex flex-col" id="flow-editor-flow-inputs">
@@ -55,8 +55,7 @@
flowStore,
pathStore,
saveDraft,
flowInputsStore,
customUi
flowInputsStore
} = getContext<FlowEditorContext>('FlowEditorContext')
export let flowModule: FlowModule
@@ -266,7 +265,6 @@
{#if flowModule.value.type === 'rawscript' && !noEditor}
<div class="border-b-2 shadow-sm px-1">
<EditorBar
customUi={customUi?.editorBar}
{validCode}
{editor}
{diffEditor}
@@ -404,12 +402,7 @@
{#if !$selectedId.includes('failure')}
<Tab value="runtime">Runtime</Tab>
<Tab value="cache" active={Boolean(flowModule.cache_ttl)}>Cache</Tab>
<Tab
value="early-stop"
active={Boolean(
flowModule.stop_after_if || flowModule.stop_after_all_iters_if
)}
>
<Tab value="early-stop" active={Boolean(flowModule.stop_after_if)}>
Early Stop
</Tab>
<Tab value="suspend" active={Boolean(flowModule.suspend)}>Suspend</Tab>
@@ -2,14 +2,13 @@
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import PropPickerWrapper from '$lib/components/flows/propPicker/PropPickerWrapper.svelte'
import type { Flow, FlowModule } from '$lib/gen'
import type { FlowModule } from '$lib/gen'
import Tooltip from '$lib/components/Tooltip.svelte'
import type { FlowEditorContext } from '../types'
import { getContext } from 'svelte'
import { NEVER_TESTED_THIS_FAR } from '../models'
import Section from '$lib/components/Section.svelte'
import { getStepPropPicker } from '../previousResults'
import { dfs } from '../previousResults'
const { flowStateStore, flowStore, previewArgs } =
getContext<FlowEditorContext>('FlowEditorContext')
@@ -27,207 +26,80 @@
false
)
function checkIfParentLoop(flowStore: typeof $flowStore): string | null {
const flow: Flow = JSON.parse(JSON.stringify(flowStore))
const parents = dfs(flowModule.id, flow, true)
for (const parent of parents.slice(1)) {
if (parent.value.type === 'forloopflow' || parent.value.type === 'whileloopflow') {
return parent.id
}
}
return null
}
$: isLoop = flowModule.value.type === 'forloopflow' || flowModule.value.type === 'whileloopflow'
$: isBranchAll = flowModule.value.type === 'branchall'
$: isStopAfterIfEnabled = Boolean(flowModule.stop_after_if)
$: isStopAfterAllIterationsEnabled = Boolean(flowModule.stop_after_all_iters_if)
$: result = $flowStateStore[flowModule.id]?.previewResult ?? NEVER_TESTED_THIS_FAR
$: parentLoopId = checkIfParentLoop($flowStore)
</script>
<div class="flex flex-col items-start space-y-2 {$$props.class}">
{#if !isBranchAll}
<Section
label={(isLoop
? 'Break loop'
: parentLoopId
? 'Break parent loop module ' + parentLoopId
: 'Stop flow early') + (isLoop ? ' (evaluated after each iteration)' : '')}
class="w-full"
>
<svelte:fragment slot="header">
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/early_stop">
If defined, at the end of the step, the predicate expression will be evaluated to decide
if the flow should stop early or break if inside a for/while loop.
</Tooltip>
</svelte:fragment>
<Section label="Early stop/Break" class="w-full">
<svelte:fragment slot="header">
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/early_stop">
If defined, at the end of the step, the predicate expression will be evaluated to decide if
the flow should stop early.
</Tooltip>
</svelte:fragment>
<Toggle
checked={isStopAfterIfEnabled}
on:change={() => {
if (isStopAfterIfEnabled && flowModule.stop_after_if) {
flowModule.stop_after_if = undefined
} else {
flowModule.stop_after_if = {
expr: 'result == undefined',
skip_if_stopped: false
}
<Toggle
checked={isStopAfterIfEnabled}
on:change={() => {
if (isStopAfterIfEnabled && flowModule.stop_after_if) {
flowModule.stop_after_if = undefined
} else {
flowModule.stop_after_if = {
expr: 'result == undefined',
skip_if_stopped: false
}
}}
options={{
right: isLoop
? 'Break loop'
: parentLoopId
? 'Break parent loop module'
: 'Stop flow' + ' if condition met'
}}
/>
}
}}
options={{
right: 'Early stop or Break if condition met'
}}
/>
<div
class="w-full border p-2 flex flex-col {flowModule.stop_after_if
? ''
: 'bg-surface-secondary'}"
>
{#if flowModule.stop_after_if}
{@const earlyStopResult = isLoop
? Array.isArray(result) && result.length > 0
? result[result.length - 1]
: result === NEVER_TESTED_THIS_FAR
? result
: undefined
: result}
{#if !parentLoopId && !isLoop}
<Toggle
size="xs"
bind:checked={flowModule.stop_after_if.skip_if_stopped}
options={{
right: 'Label flow as "skipped" if stopped'
}}
/>
{/if}
<span class="mt-2 text-xs font-bold">Stop condition expression</span>
<div class="border w-full">
<PropPickerWrapper
notSelectable
flow_input={stepPropPicker.pickableProperties.flow_input}
pickableProperties={undefined}
result={earlyStopResult}
extraResults={isLoop ? { all_iters: result } : undefined}
on:select={({ detail }) => {
editor?.insertAtCursor(detail)
editor?.focus()
}}
>
<SimpleEditor
bind:this={editor}
lang="javascript"
bind:code={flowModule.stop_after_if.expr}
class="few-lines-editor"
extraLib={`declare const result = ${JSON.stringify(earlyStopResult)};` +
(isLoop ? `\ndeclare const all_iters = ${JSON.stringify(result)};` : '')}
/>
</PropPickerWrapper>
</div>
{:else}
{#if !parentLoopId && !isLoop}
<Toggle
disabled
size="xs"
options={{
right: 'Label flow as "skipped" if stopped'
}}
/>
{/if}
<span class="mt-2 text-xs font-bold">Stop condition expression</span>
<textarea disabled rows="3" class="min-h-[80px]" />
{/if}
</div>
</Section>
{/if}
{#if isLoop || isBranchAll}
<Section
label={(parentLoopId ? 'Break parent loop module ' + parentLoopId : 'Stop flow early') +
(isBranchAll
? ' (evaluated after all branches have been run)'
: ' (evaluated after all iterations)')}
class="w-full"
<div
class="w-full border p-2 flex flex-col {flowModule.stop_after_if
? ''
: 'bg-surface-secondary'}"
>
<svelte:fragment slot="header">
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/early_stop">
If defined, at the end of the step, the predicate expression will be evaluated to decide
if the flow should stop early or break if inside a for/while loop.
</Tooltip>
</svelte:fragment>
<Toggle
checked={isStopAfterAllIterationsEnabled}
on:change={() => {
if (isStopAfterAllIterationsEnabled && flowModule.stop_after_all_iters_if) {
flowModule.stop_after_all_iters_if = undefined
} else {
flowModule.stop_after_all_iters_if = {
expr: 'result == undefined',
skip_if_stopped: false
}
}
}}
options={{
right: (parentLoopId ? 'Break parent loop module' : 'Stop flow') + ' if condition met'
}}
/>
<div
class="w-full border p-2 flex flex-col {flowModule.stop_after_all_iters_if
? ''
: 'bg-surface-secondary'}"
>
{#if flowModule.stop_after_all_iters_if}
{#if !parentLoopId}
<Toggle
size="xs"
bind:checked={flowModule.stop_after_all_iters_if.skip_if_stopped}
options={{
right: 'Label flow as "skipped" if stopped'
}}
{#if flowModule.stop_after_if}
<Toggle
size="xs"
bind:checked={flowModule.stop_after_if.skip_if_stopped}
options={{
right: 'Label flow as "skipped" if stopped'
}}
/>
<span class="mt-2 text-xs font-bold">Stop condition expression</span>
<div class="border w-full">
<PropPickerWrapper
notSelectable
flow_input={stepPropPicker.pickableProperties.flow_input}
pickableProperties={undefined}
{result}
on:select={({ detail }) => {
editor?.insertAtCursor(detail)
editor?.focus()
}}
>
<SimpleEditor
bind:this={editor}
lang="javascript"
bind:code={flowModule.stop_after_if.expr}
class="few-lines-editor"
extraLib={`declare const result = ${JSON.stringify(result)};`}
/>
{/if}
<span class="mt-2 text-xs font-bold">Stop condition expression</span>
<div class="border w-full">
<PropPickerWrapper
notSelectable
flow_input={stepPropPicker.pickableProperties.flow_input}
pickableProperties={undefined}
{result}
on:select={({ detail }) => {
editor?.insertAtCursor(detail)
editor?.focus()
}}
>
<SimpleEditor
bind:this={editor}
lang="javascript"
bind:code={flowModule.stop_after_all_iters_if.expr}
class="few-lines-editor"
extraLib={`declare const result = ${JSON.stringify(result)};`}
/>
</PropPickerWrapper>
</div>
{:else}
{#if !parentLoopId}
<Toggle
disabled
size="xs"
options={{
right: 'Label flow as "skipped" if stopped'
}}
/>
{/if}
<span class="mt-2 text-xs font-bold">Stop condition expression</span>
<textarea disabled rows="3" class="min-h-[80px]" />
{/if}
</div>
</Section>
{/if}
</PropPickerWrapper>
</div>
{:else}
<Toggle
disabled
size="xs"
options={{
right: 'Label flow as "skipped" if stopped'
}}
/> <span class="mt-2 text-xs font-bold">Stop condition expression</span>
<textarea disabled rows="3" class="min-h-[80px]" />
{/if}
</div>
</Section>
</div>
@@ -19,14 +19,14 @@
import { sendUserToast } from '$lib/utils'
import { workerTags } from '$lib/stores'
import { getLatestHashForScript } from '$lib/scripts'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import type { WhitelabelCustomUi } from '$lib/components/custom_ui'
export let module: FlowModule
const { scriptEditorDrawer, flowStore, selectedId } =
getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
let customUi: undefined | WhitelabelCustomUi = getContext('customUi')
loadWorkerGroups()
@@ -71,7 +71,7 @@
<svelte:fragment slot="text">Cache</svelte:fragment>
</Popover>
{/if}
{#if module.stop_after_if || module.stop_after_all_iters_if}
{#if module.stop_after_if}
<Popover
placement="bottom"
class="center-center rounded p-2 bg-blue-100 text-blue-800 border border-blue-300 hover:bg-blue-200 dark:bg-frost-700 dark:text-frost-100 dark:border-frost-600"
@@ -1,20 +1,17 @@
<script lang="ts">
import type { SupportedLanguage } from '$lib/common'
import DiffEditor from '$lib/components/DiffEditor.svelte'
import HighlightCode from '$lib/components/HighlightCode.svelte'
import TimeAgo from '$lib/components/TimeAgo.svelte'
import { ScriptService } from '$lib/gen'
import { getScriptByPath, scriptLangToEditorLang } from '$lib/scripts'
import { getScriptByPath } from '$lib/scripts'
import { workspaceStore } from '$lib/stores'
export let path: string
export let hash: string | undefined = undefined
export let previousHash: string | undefined = undefined
export let showDate = false
export let showAllCode: boolean = true
let code: string
let previousCode: string
let language: SupportedLanguage
let lock: string | undefined = undefined
let date: string | undefined = undefined
@@ -27,7 +24,6 @@
? await ScriptService.getScriptByHash({ workspace: $workspaceStore!, hash })
: await getScriptByPath(path!)
code = script.content
language = script.language
lock = script.lock
date = script.created_at
@@ -37,26 +33,11 @@
}
}
async function loadPreviousCode(previousHash: string) {
try {
const previousScript = await ScriptService.getScriptByHash({
workspace: $workspaceStore!,
hash: previousHash
})
previousCode = previousScript.content
} catch (e) {
console.error(e)
}
}
$: path && loadCode(path, hash)
$: path && previousHash && loadPreviousCode(previousHash)
function toggleShowAll() {
showAllCode = !showAllCode
}
export let showDiff: boolean = false
</script>
<div class="flex flex-col flex-1 h-full overflow-auto p-2">
@@ -66,20 +47,7 @@
{#if notFound}
<div class="text-red-400">script not found at {path} in workspace {$workspaceStore}</div>
{:else if showAllCode}
{#if showDiff}
{#key previousCode + code}
<DiffEditor
class="h-screen"
readOnly
automaticLayout
defaultLang={scriptLangToEditorLang(language)}
defaultOriginal={previousCode}
defaultModified={code}
/>
{/key}
{:else}
<HighlightCode {language} {code} />
{/if}
<HighlightCode {language} {code} />
{:else}
<div class="code-container h-full">
<HighlightCode {language} code={code?.split('\n').slice(0, 10).join('\n')} />
@@ -29,7 +29,7 @@
export let noEditor: boolean
const { selectedId, flowStore, initialPath, previewArgs, pathStore, schedule, customUi } =
const { selectedId, flowStore, initialPath, previewArgs, pathStore, schedule } =
getContext<FlowEditorContext>('FlowEditorContext')
let hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
@@ -52,33 +52,22 @@
<div class="h-full flex-1">
<Tabs bind:selected={$selectedId}>
<Tab value="settings-metadata">Metadata</Tab>
{#if !noEditor && customUi?.settingsTabs?.schedule != false}
{#if !noEditor}
<Tab value="settings-schedule" active={$schedule.enabled}>Schedule</Tab>
{/if}
{#if customUi?.settingsTabs?.sharedDiretory != false}
<Tab value="settings-same-worker" active={$flowStore.value.same_worker}>
Shared Directory
</Tab>
{/if}
{#if customUi?.settingsTabs?.earlyStop != false}
<Tab value="settings-early-stop" active={Boolean($flowStore.value.skip_expr)}>
Early Stop
</Tab>
{/if}
{#if customUi?.settingsTabs?.earlyReturn != false}
<Tab value="settings-early-return" active={Boolean($flowStore.value.early_return)}>
Early Return
</Tab>
{/if}
{#if customUi?.settingsTabs?.workerGroup != false}
<Tab value="settings-worker-group">Worker Group</Tab>
{/if}
{#if customUi?.settingsTabs?.concurrency != false}
<Tab value="settings-concurrency">Concurrency</Tab>
{/if}
{#if customUi?.settingsTabs?.cache != false}
<Tab value="settings-cache" active={Boolean($flowStore.value.cache_ttl)}>Cache</Tab>
{/if}
<Tab value="settings-same-worker" active={$flowStore.value.same_worker}>
Shared Directory
</Tab>
<Tab value="settings-early-stop" active={Boolean($flowStore.value.skip_expr)}>
Early Stop
</Tab>
<Tab value="settings-early-return" active={Boolean($flowStore.value.early_return)}>
Early Return
</Tab>
<Tab value="settings-worker-group">Worker Group</Tab>
<Tab value="settings-concurrency">Concurrency</Tab>
<Tab value="settings-cache" active={Boolean($flowStore.value.cache_ttl)}>Cache</Tab>
<svelte:fragment slot="content">
<TabContent value="settings-metadata" class="p-4 h-full overflow-auto">
<Section label="Metadata">
@@ -85,9 +85,6 @@ function getModuleExprs(x: FlowModule): string[] {
if (x.stop_after_if?.expr) {
exprs.push(x.stop_after_if.expr)
}
if (x.stop_after_all_iters_if?.expr) {
exprs.push(x.stop_after_all_iters_if.expr)
}
exprs.push(...getExpr(x.sleep))
}
return exprs
@@ -5,7 +5,7 @@
import StepGen from '$lib/components/copilot/StepGen.svelte'
import type { FlowModule } from '$lib/gen'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import type { WhitelabelCustomUi } from '$lib/components/custom_ui'
import { twMerge } from 'tailwind-merge'
const dispatch = createEventDispatcher()
@@ -18,7 +18,7 @@
export let disableAi = false
$: !open && (funcDesc = '')
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
let customUi: undefined | WhitelabelCustomUi = getContext('customUi')
</script>
<Menu
@@ -45,7 +45,7 @@
$: itemProps = {
selected: $selectedId === mod.id,
retry: mod.retry?.constant != undefined || mod.retry?.exponential != undefined,
earlyStop: mod.stop_after_if != undefined || mod.stop_after_all_iters_if != undefined,
earlyStop: mod.stop_after_if != undefined,
suspend: Boolean(mod.suspend),
sleep: Boolean(mod.sleep),
cache: Boolean(mod.cache_ttl),
@@ -28,7 +28,6 @@
export let pickableProperties: PickableProperties | undefined
export let result: any = undefined
export let extraResults: any = undefined
export let flow_input: any = undefined
export let error: boolean = false
export let displayContext = true
@@ -74,7 +73,6 @@
{#if result}
<PropPickerResult
{result}
{extraResults}
{flow_input}
on:select={({ detail }) => {
if (!notSelectable && !$propPickerConfig) {
@@ -4,7 +4,6 @@ import type { Writable } from 'svelte/store'
import type ScriptEditorDrawer from './content/ScriptEditorDrawer.svelte'
import type { FlowState } from './flowState'
import type { Schedule } from './scheduleUtils'
import type { FlowBuilderWhitelabelCustomUi } from '../custom_ui'
export type FlowInput = Record<
string,
@@ -40,5 +39,4 @@ export type FlowEditorContext = {
saveDraft: () => void
initialPath: string
flowInputsStore: Writable<FlowInput>
customUi: FlowBuilderWhitelabelCustomUi
}
@@ -1,7 +1,7 @@
<script lang="ts">
import { type FlowModule } from '../../gen'
import { NODE, type GraphModuleState } from '.'
import { createEventDispatcher, onMount, setContext } from 'svelte'
import { createEventDispatcher, setContext } from 'svelte'
import { writable, type Writable } from 'svelte/store'
import '@xyflow/svelte/dist/style.css'
@@ -226,10 +226,6 @@
}
$: width && centerViewport(width)
onMount(() => {
centerViewport(width)
})
</script>
<div style={`height: ${height}px; max-height: ${maxHeight}px;`} bind:clientWidth={width}>
@@ -33,9 +33,6 @@ export type FlowStatusViewerContext = {
flowStateStore?: Writable<FlowState>
retryStatus: Writable<Record<string, number | undefined>>
suspendStatus: Writable<Record<string, { nb: number; job: Job }>>
hideDownloadInGraph?: boolean
hideTimeline?: boolean
hideNodeDefinition?: boolean
}
export type GraphModuleState = {
type: FlowStatusModule['type']
@@ -3,7 +3,6 @@
import ObjectViewer from './ObjectViewer.svelte'
export let result: any
export let extraResults: any = undefined
export let flow_input: any = undefined
const dispatch = createEventDispatcher()
@@ -12,11 +11,7 @@
<div class="w-full px-2">
<span class="font-bold text-sm">Result</span>
<div class="overflow-y-auto mb-2 w-full">
<ObjectViewer
allowCopy={false}
json={{ result, ...(extraResults ? extraResults : {}) }}
on:select
/>
<ObjectViewer allowCopy={false} json={{ result }} on:select />
</div>
{#if flow_input}
<span class="font-bold text-sm">Flow Input</span>
@@ -5,11 +5,6 @@ export type Changelog = {
}
const changelogs: Changelog[] = [
{
label: 'Continue on disapproval/timeout',
href: 'https://www.windmill.dev/changelog/continue-on-disapproval',
date: '2024-08-14'
},
{
label: 'Nativets runtime supports npm packages and relative imports',
href: 'https://www.windmill.dev/changelog/native-runtime-imports',
@@ -437,7 +437,7 @@
overflowAuto
noSide={true}
on:select={(e) => {
if (e.detail) {
if (e.detail.id) {
stepDetail = e.detail
} else {
stepDetail = undefined
@@ -475,7 +475,7 @@
{#if script}
<Drawer bind:open={versionsDrawerOpen} size="1200px">
<DrawerContent title="Versions History" on:close={() => (versionsDrawerOpen = false)} noPadding>
<DrawerContent title="Versions History" on:close={() => (versionsDrawerOpen = false)}>
<ScriptVersionHistory
scriptPath={script.path}
openDetails
@@ -189,10 +189,10 @@
let queueMetricsDrawer: Drawer
let selectedTab: string = 'default'
$: groupedWorkers && selectedTab == 'default' && updateSelectedTabIfDefaultDoesNotExist()
$: workerGroups && selectedTab == 'default' && updateSelectedTabIfDefaultDoesNotExist()
function updateSelectedTabIfDefaultDoesNotExist() {
if (selectedTab == 'default' && !groupedWorkers.some((x) => x[0] == 'default')) {
if (selectedTab == 'default' && !workerGroups?.hasOwnProperty('default')) {
selectedTab = Object.keys(workerGroups ?? {})[0] ?? 'default'
}
}
@@ -567,7 +567,7 @@
<Cell>
<div class="flex flex-col gap-1">
<div>
{vcpus ? (vcpus / 100000).toFixed(2) + ' vCPUs' : '--'}
{vcpus ? (vcpus / 100000).toFixed(1) + ' vCPUs' : '--'}
</div>
<div>
{memory ? Math.round(memory / 1024 / 1024) + 'MB' : '--'}
+1 -2
View File
@@ -96,8 +96,7 @@
testStepStore,
saveDraft: () => {},
initialPath: '',
flowInputsStore: writable<FlowInput>({}),
customUi: {}
flowInputsStore: writable<FlowInput>({})
})
type LastEdit = {
+18 -57
View File
@@ -1,61 +1,22 @@
<script lang="ts">
// import EditableSchemaWrapper from '$lib/components/schema/EditableSchemaWrapper.svelte'
import EditableSchemaWrapper from '$lib/components/schema/EditableSchemaWrapper.svelte'
// import FlowBuilder from '$lib/components/FlowBuilder.svelte'
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
// import type { OpenFlow } from '$lib/gen'
// import { writable, type Writable } from 'svelte/store'
// let schema = {
// type: 'object',
// properties: {
// foo: {
// type: 'string',
// description: '',
// required: [],
// nullable: false,
// default: '',
// disableVariablePicker: false,
// disableCreate: false,
// password: false
// }
// },
// order: ['foo']
// }
// let flowStore: Writable<OpenFlow> = writable({
// summary: 'foo',
// value: {
// modules: []
// }
// })
// let flowStateStore: Writable<Record<string, any>> = writable({})
let schema = {
type: 'object',
properties: {
foo: {
type: 'string',
description: '',
required: [],
nullable: false,
default: '',
disableVariablePicker: false,
disableCreate: false,
password: false
}
},
order: ['foo']
}
</script>
<!-- <EditableSchemaWrapper on:change={() => console.log('FOO')} {schema} /> -->
<!-- <FlowBuilder
newFlow={false}
initialPath="u/admin/foo"
selectedId={undefined}
{flowStore}
{flowStateStore}
customUi={{
topBar: {
extraDeployOptions: false
},
settingsTabs: {
workerGroup: false
},
settingsPanel: false
}}
/> -->
<ScriptBuilder
script={{
summary: 'foo',
path: 'u/admin/foo',
description: 'foo',
language: 'python3',
content: 'print("foo")'
}}
/>
<EditableSchemaWrapper on:change={() => console.log('FOO')} {schema} />
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.383.0"
wmill_pg = ">=1.383.0"
wmill = ">=1.381.0"
wmill_pg = ">=1.381.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -10
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.383.0
version: 1.381.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -103,15 +103,6 @@ components:
type: string
required:
- expr
stop_after_all_iters_if:
type: object
properties:
skip_if_stopped:
type: boolean
expr:
type: string
required:
- expr
sleep:
$ref: "#/components/schemas/InputTransform"
cache_ttl:
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.383.0'
ModuleVersion = '1.381.0'
# Supported PSEditions
# CompatiblePSEditions = @()
+2 -2
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.383.0"
version = "1.381.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
@@ -16,7 +16,7 @@ include = ["wmill/py.typed"]
[tool.poetry.dependencies]
python = "^3.7"
httpx = ">=0.24"
httpx = "^0.24"
[build-system]
requires = ["poetry>=1.0.2", "poetry-dynamic-versioning"]
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.383.0"
version = "1.381.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+5 -8
View File
@@ -134,7 +134,7 @@ export async function runFlow(
console.info(`running \`${path}\` synchronously with args:`, args);
}
const jobId = await runFlowAsync(path, args, null, false);
const jobId = await runFlowAsync(path, args);
return await waitJob(jobId, verbose);
}
@@ -272,8 +272,7 @@ export async function runScriptAsync(
export async function runFlowAsync(
path: string | null,
args: Record<string, any> | null,
scheduledInSeconds: number | null = null,
flowOutlivesParent: boolean = true
scheduledInSeconds: number | null = null
): Promise<string> {
// Create a script job and return its job id.
@@ -284,11 +283,9 @@ export async function runFlowAsync(
params["scheduled_in_secs"] = scheduledInSeconds;
}
if (!flowOutlivesParent) {
let parentJobId = getEnv("WM_JOB_ID");
if (parentJobId !== undefined) {
params["parent_job"] = parentJobId;
}
let parentJobId = getEnv("WM_JOB_ID");
if (parentJobId !== undefined) {
params["parent_job"] = parentJobId;
}
let rootJobId = getEnv("WM_ROOT_FLOW_JOB_ID");
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.383.0",
"version": "1.381.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.383.0",
"version": "1.381.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.383.0
1.381.0