diff --git a/.github/workflows/aider-after-review.yaml b/.github/workflows/aider-after-review.yaml index 8347b1f9f2..649ef52724 100644 --- a/.github/workflows/aider-after-review.yaml +++ b/.github/workflows/aider-after-review.yaml @@ -140,9 +140,9 @@ jobs: - name: Run Aider with review prompt run: | aider \ - --read .cursor/rules/rust-best-practices.mdc \ - --read .cursor/rules/svelte5-best-practices.mdc \ - --read .cursor/rules/windmill-overview.mdc \ + --read CLAUDE.md \ + --read backend/CLAUDE.md \ + --read frontend/CLAUDE.md \ ${{ env.FILES_TO_EDIT }} \ --model gemini/gemini-2.5-pro-preview-05-06 \ --message-file .github/aider/review-prompt.txt \ diff --git a/.github/workflows/aider.yaml b/.github/workflows/aider.yaml index 82d5f8b63e..e97b75bf22 100644 --- a/.github/workflows/aider.yaml +++ b/.github/workflows/aider.yaml @@ -264,9 +264,9 @@ jobs: run: | echo "Files identified by probe-chat: ${{ env.FILES_TO_EDIT }}" aider \ - --read .cursor/rules/rust-best-practices.mdc \ - --read .cursor/rules/svelte5-best-practices.mdc \ - --read .cursor/rules/windmill-overview.mdc \ + --read CLAUDE.md \ + --read backend/CLAUDE.md \ + --read frontend/CLAUDE.md \ ${{ env.FILES_TO_EDIT }} \ --model gemini/gemini-2.5-pro-preview-05-06 \ --message-file .github/aider/issue-prompt.txt \ diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 75484f7aa0..77a96d6119 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -1,4 +1,4 @@ -name: Claude Code +name: Claude PR Assistant on: issue_comment: @@ -11,15 +11,17 @@ on: types: [submitted] jobs: - claude: + claude-code-action: if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/aider')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/aider')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/aider')) || + (github.event_name == 'issues' && contains(github.event.issue.body, '/aider')) + runs-on: ubicloud-standard-8 permissions: contents: read + pull-requests: read + issues: read id-token: write steps: - name: Checkout repository @@ -27,9 +29,11 @@ jobs: with: fetch-depth: 1 - - name: Run Claude Code - id: claude + - name: Run Claude PR Action uses: anthropics/claude-code-action@beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - + timeout_minutes: "60" + allowed_tools: "mcp__github__create_pull_request" + custom_instructions: "IMPORTANT: Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a PR from that branch to main, with the title starting with [Claude PR]" + trigger_phrase: "/aider" diff --git a/.github/workflows/linear-claude.yaml b/.github/workflows/linear-claude.yaml new file mode 100644 index 0000000000..c74cfeba1d --- /dev/null +++ b/.github/workflows/linear-claude.yaml @@ -0,0 +1,38 @@ +name: Claude PR Assistant + +on: + repository_dispatch: + types: [external_claude_issue_fix] + +jobs: + claude-code-action: + runs-on: ubicloud-standard-8 + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Process inputs + id: process_inputs + shell: bash + run: | + ISSUE_TITLE="${{ github.event.client_payload.issue_title }}" + INSTRUCTION="${{ github.event.client_payload.instruction }}" + ISSUE_BODY=$(printf '%q' "${{ github.event.client_payload.issue_body }}") + BASE_PROMPT="Try to fix the following issue based on the instruction given. You are provided with the issue title, issue body, and instruction. You are to fix the issue based on the instruction. You are to create a pull request to fix the issue." + CUSTOM_PROMPT=$(printf -v PROMPT "%s\n\nISSUE_TITLE: %s\n\nISSUE_BODY: %s\n\nINSTRUCTION: %s" "$BASE_PROMPT" "$ISSUE_TITLE" "$ISSUE_BODY" "$INSTRUCTION") + echo "CUSTOM_PROMPT=$CUSTOM_PROMPT" >> $GITHUB_OUTPUT + + - name: Run Claude PR Action + uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + timeout_minutes: "60" + allowed_tools: "mcp__github__create_pull_request" + direct_prompt: ${{ steps.process_inputs.outputs.CUSTOM_PROMPT }} diff --git a/.github/workflows/linear-issue.yaml b/.github/workflows/linear-issue.yaml index e3fbeb7f39..7dd217e141 100644 --- a/.github/workflows/linear-issue.yaml +++ b/.github/workflows/linear-issue.yaml @@ -151,9 +151,9 @@ jobs: run: | echo "Files identified by probe-chat: ${{ env.FILES_TO_EDIT }}" aider \ - --read .cursor/rules/rust-best-practices.mdc \ - --read .cursor/rules/svelte5-best-practices.mdc \ - --read .cursor/rules/windmill-overview.mdc \ + --read CLAUDE.md \ + --read backend/CLAUDE.md \ + --read frontend/CLAUDE.md \ ${{ env.FILES_TO_EDIT }} \ --model gemini/gemini-2.5-pro-preview-05-06 \ --message-file .github/aider/issue-prompt.txt \ diff --git a/backend/.sqlx/query-551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9.json b/backend/.sqlx/query-551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9.json new file mode 100644 index 0000000000..7f1cdf4aa2 --- /dev/null +++ b/backend/.sqlx/query-551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT fv.id\n FROM flow f\n INNER JOIN flow_version fv ON fv.id = f.versions[array_upper(f.versions, 1)]\n WHERE fv.value->'preprocessor_module'->'value'->>'path' = $1 AND f.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "551fee7919fdeb911e3f9cc5852e158ea47e3db4895c2b2b1d3cb6b16fceeda9" +} diff --git a/backend/.sqlx/query-72916f8e490f8252e0a51b7f562ccc3be832b12102eb86a07d8405a4fa9287d5.json b/backend/.sqlx/query-e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d.json similarity index 63% rename from backend/.sqlx/query-72916f8e490f8252e0a51b7f562ccc3be832b12102eb86a07d8405a4fa9287d5.json rename to backend/.sqlx/query-e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d.json index 5321de8ccb..000329b19f 100644 --- a/backend/.sqlx/query-72916f8e490f8252e0a51b7f562ccc3be832b12102eb86a07d8405a4fa9287d5.json +++ b/backend/.sqlx/query-e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow \n WHERE workspace_id = $1\n AND path = $2", + "query": "SELECT\n value->'preprocessor_module'->'value' as \"preprocessor_module: _\",\n schema as \"schema: _\"\n FROM flow_version\n WHERE \n path = $1\n AND workspace_id = $2\n ORDER BY created_at DESC\n LIMIT 1", "describe": { "columns": [ { @@ -25,5 +25,5 @@ true ] }, - "hash": "72916f8e490f8252e0a51b7f562ccc3be832b12102eb86a07d8405a4fa9287d5" + "hash": "e7348225a27bbdc9607d7c799e7192cd6ce4088467d91a5cbdc019430320d26d" } diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 164c48393f..383553a690 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -14424,6 +14424,7 @@ dependencies = [ "sha2 0.10.9", "size", "sqlx", + "strum", "systemstat", "tikv-jemalloc-ctl", "tikv-jemalloc-sys", @@ -14646,6 +14647,8 @@ dependencies = [ "sha2 0.10.9", "size", "sqlx", + "strum", + "strum_macros", "systemstat", "tar", "tempfile", @@ -14662,7 +14665,9 @@ dependencies = [ "tracing-subscriber", "uuid", "windmill-macros", + "windmill-parser-py", "windmill-parser-sql", + "windmill-parser-ts", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 0ed1c2b0e5..896925da9f 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -137,6 +137,7 @@ v8 = { workspace = true, optional = true } rustls.workspace = true systemstat.workspace = true size.workspace = true +strum.workspace = true [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = { optional = true, workspace = true } @@ -389,3 +390,5 @@ tree-sitter-c-sharp = "0.23.0" tree-sitter-java = "0.23.0" oracle = { version = "0.6.3", features = ["chrono"] } rumqttc = { version = "0.24.0", features = ["use-native-tls"]} +strum = "^0" +strum_macros = "^0" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 81d012d243..71591f02b5 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -11917062c8a5ea230f27fe750cbde1dbdc0512f9 \ No newline at end of file +0f0df9dd99a44baf890f24323f0a2eb2ee1120ce \ No newline at end of file diff --git a/backend/migrations/20250520104210_workspace_preprocessor_notify.down.sql b/backend/migrations/20250520104210_workspace_preprocessor_notify.down.sql new file mode 100644 index 0000000000..2cdc8d698b --- /dev/null +++ b/backend/migrations/20250520104210_workspace_preprocessor_notify.down.sql @@ -0,0 +1,12 @@ +-- Add down migration script here +CREATE OR REPLACE FUNCTION notify_runnable_version_change() +RETURNS TRIGGER AS $$ +DECLARE + source_type TEXT; +BEGIN + source_type := TG_ARGV[0]; + + PERFORM pg_notify('notify_runnable_version_change', NEW.workspace_id || ':' || source_type || ':' || NEW.path); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/migrations/20250520104210_workspace_preprocessor_notify.up.sql b/backend/migrations/20250520104210_workspace_preprocessor_notify.up.sql new file mode 100644 index 0000000000..bb1ebe0a40 --- /dev/null +++ b/backend/migrations/20250520104210_workspace_preprocessor_notify.up.sql @@ -0,0 +1,19 @@ +-- Add up migration script here +CREATE OR REPLACE FUNCTION notify_runnable_version_change() +RETURNS TRIGGER AS $$ +DECLARE + source_type TEXT; + kind TEXT; +BEGIN + source_type := TG_ARGV[0]; + + IF source_type = 'script' THEN + kind := NEW.kind; + ELSE + kind := 'flow'; + END IF; + + PERFORM pg_notify('notify_runnable_version_change', NEW.workspace_id || ':' || source_type || ':' || NEW.path || ':' || kind); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 821b209048..8c451f63a0 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -5,7 +5,6 @@ * Please see the included NOTICE for copyright information and * LICENSE-AGPL for a copy of the license. */ - use anyhow::Context; use monitor::{ load_base_url, load_otel, reload_critical_alerts_on_db_oversize, @@ -23,6 +22,7 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, time::{Duration, Instant}, }; +use strum::IntoEnumIterator; use tokio::{fs::File, io::AsyncReadExt, task::JoinHandle}; use uuid::Uuid; use windmill_api::HTTP_CLIENT; @@ -50,6 +50,7 @@ use windmill_common::{ }, scripts::ScriptLang, stats_ee::schedule_stats, + triggers::TriggerKind, utils::{hostname, rd_string, Mode, GIT_VERSION, MODE_AND_ADDONS}, worker::{ reload_custom_tags_setting, Connection, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP, @@ -791,11 +792,37 @@ Windmill Community Edition {GIT_VERSION} let payload = n.payload(); tracing::info!("Runnable version change detected: {}", payload); match payload.split(':').collect::>().as_slice() { - [workspace_id, source_type, path] => { + [workspace_id, source_type, path, kind] => { let key = (workspace_id.to_string(), path.to_string()); match source_type { &"script" => { windmill_common::DEPLOYED_SCRIPT_HASH_CACHE.remove(&key); + match kind { + &"preprocessor" => { + match sqlx::query_scalar!( + "SELECT fv.id + FROM flow f + INNER JOIN flow_version fv ON fv.id = f.versions[array_upper(f.versions, 1)] + WHERE fv.value->'preprocessor_module'->'value'->>'path' = $1 AND f.workspace_id = $2", + path, + workspace_id + ).fetch_all(&db).await { + Ok(flow_versions) => { + tracing::debug!("Workspace preprocessor {} changed, removing runnable format version cache for flow versions {:?}", path, flow_versions); + for version in flow_versions { + for trigger_kind in TriggerKind::iter() { + let key = (windmill_common::triggers::HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()), version, trigger_kind); + windmill_common::triggers::RUNNABLE_FORMAT_VERSION_CACHE.remove(&key); + } + } + } + Err(e) => { + tracing::error!("Error fetching flow paths: {e:#}"); + } + } + }, + _ => {} + } } &"flow" => { windmill_common::FLOW_VERSION_CACHE.remove(&key); diff --git a/backend/windmill-api/src/args.rs b/backend/windmill-api/src/args.rs index f75deb91f8..e3a2adb38f 100644 --- a/backend/windmill-api/src/args.rs +++ b/backend/windmill-api/src/args.rs @@ -10,12 +10,17 @@ use http::{header::CONTENT_TYPE, request::Parts, StatusCode}; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; use sqlx::types::JsonRawValue; -use windmill_common::{error::Error, worker::to_raw_value, DB}; -use windmill_queue::{PushArgsOwned, TriggerKind}; +use windmill_common::{ + error::Error, + triggers::{RunnableFormat, RunnableFormatVersion, TriggerKind}, + worker::to_raw_value, + DB, +}; +use windmill_queue::PushArgsOwned; use crate::{ db::ApiAuthed, - trigger_helpers::{get_runnable_format, RunnableFormat, RunnableFormatVersion, RunnableId}, + trigger_helpers::{get_runnable_format, RunnableId}, }; #[derive(Debug)] diff --git a/backend/windmill-api/src/capture.rs b/backend/windmill-api/src/capture.rs index 12a36e30a7..07127e53a1 100644 --- a/backend/windmill-api/src/capture.rs +++ b/backend/windmill-api/src/capture.rs @@ -57,7 +57,6 @@ use { use crate::{ args::RawWebhookArgs, db::{ApiAuthed, DB}, - trigger_helpers::{RunnableFormat, RunnableFormatVersion}, users::fetch_api_authed, utils::RunnableKind, }; @@ -76,11 +75,12 @@ use sqlx::types::Json as SqlxJson; use windmill_common::{ db::UserDB, error::{JsonResult, Result}, + triggers::{RunnableFormat, RunnableFormatVersion, TriggerKind}, utils::{not_found_if_none, paginate, Pagination, StripPath}, worker::{to_raw_value, CLOUD_HOSTED}, }; -use windmill_queue::{PushArgs, PushArgsOwned, TriggerKind}; +use windmill_queue::{PushArgs, PushArgsOwned}; const KEEP_LAST: i64 = 20; diff --git a/backend/windmill-api/src/gcp_triggers_ee.rs b/backend/windmill-api/src/gcp_triggers_ee.rs index 6e39cfe07c..0dc672580c 100644 --- a/backend/windmill-api/src/gcp_triggers_ee.rs +++ b/backend/windmill-api/src/gcp_triggers_ee.rs @@ -11,9 +11,9 @@ use windmill_common::db::UserDB; use windmill_common::worker::to_raw_value; use windmill_common::{ error::{Error as WindmillError, Result as WindmillResult}, + triggers::TriggerKind, utils::empty_as_none, }; -use windmill_queue::TriggerKind; #[derive(sqlx::Type, Debug, Deserialize, Serialize)] #[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))] @@ -86,7 +86,7 @@ pub async fn manage_google_subscription( _subscription_mode: SubscriptionMode, _create_update_config: Option, _trigger_mode: bool, - _is_flow: bool + _is_flow: bool, ) -> WindmillResult { Ok(CreateUpdateConfig::default()) } diff --git a/backend/windmill-api/src/http_trigger_args.rs b/backend/windmill-api/src/http_trigger_args.rs index 828948b114..6aab75e46e 100644 --- a/backend/windmill-api/src/http_trigger_args.rs +++ b/backend/windmill-api/src/http_trigger_args.rs @@ -6,13 +6,17 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; -use windmill_common::{error::Error, worker::to_raw_value, DB}; +use windmill_common::{ + error::Error, + triggers::{RunnableFormat, RunnableFormatVersion}, + worker::to_raw_value, + DB, +}; use windmill_queue::PushArgsOwned; use crate::{ args::{try_from_request_body, Body, RawWebhookArgs, WebhookArgs, WebhookArgsMetadata}, db::ApiAuthed, - trigger_helpers::{RunnableFormat, RunnableFormatVersion}, }; pub struct RawHttpTriggerArgs(pub RawWebhookArgs); diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index 82d1fd8b0c..b17c8205ad 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -41,11 +41,11 @@ use windmill_common::{ db::UserDB, error::{self, JsonResult}, s3_helpers::S3Object, + triggers::TriggerKind, utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, worker::CLOUD_HOSTED, }; use windmill_git_sync::handle_deployment_metadata; -use windmill_queue::TriggerKind; lazy_static::lazy_static! { static ref ROUTE_PATH_KEY_RE: regex::Regex = regex::Regex::new(r"/?:[-\w]+").unwrap(); diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 1a3e161d5d..5c2041fd85 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -3473,7 +3473,7 @@ pub async fn run_flow_by_path( &authed, &db, &w_id, - RunnableId::from_flow_path(&flow_path.0), + RunnableId::from_flow_path(flow_path.to_path()), run_query.skip_preprocessor, ) .await?; @@ -3672,7 +3672,7 @@ pub async fn run_script_by_path( &authed, &db, &w_id, - RunnableId::from_script_path(&script_path.0), + RunnableId::from_script_path(script_path.to_path()), run_query.skip_preprocessor, ) .await?; @@ -4351,17 +4351,18 @@ pub async fn run_wait_result_job_by_path_get( let mut args = args.process_args(&authed, &db, &w_id, None).await?; args.body = args::Body::HashMap(payload_args); + let script_path = script_path.to_path(); + let args = args .to_args_from_runnable( &db, &w_id, - RunnableId::from_script_path(&script_path.0), + RunnableId::from_script_path(script_path), run_query.skip_preprocessor, ) .await?; check_queue_too_long(&db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?; - let script_path = script_path.to_path(); check_scopes(&authed, || format!("run:script/{script_path}"))?; let mut tx = user_db.clone().begin(&authed).await?; @@ -4457,7 +4458,7 @@ pub async fn run_wait_result_flow_by_path_get( .to_args_from_runnable( &db, &w_id, - RunnableId::from_flow_path(&flow_path.0), + RunnableId::from_flow_path(flow_path.to_path()), run_query.skip_preprocessor, ) .await?; @@ -4482,7 +4483,7 @@ pub async fn run_wait_result_script_by_path( &authed, &db, &w_id, - RunnableId::from_script_path(&script_path.0), + RunnableId::from_script_path(script_path.to_path()), run_query.skip_preprocessor, ) .await?; @@ -4692,7 +4693,7 @@ pub async fn run_wait_result_flow_by_path( &authed, &db, &w_id, - RunnableId::from_flow_path(&flow_path.0), + RunnableId::from_flow_path(flow_path.to_path()), run_query.skip_preprocessor, ) .await?; diff --git a/backend/windmill-api/src/mqtt_triggers.rs b/backend/windmill-api/src/mqtt_triggers.rs index 16508def86..9f3fd19827 100644 --- a/backend/windmill-api/src/mqtt_triggers.rs +++ b/backend/windmill-api/src/mqtt_triggers.rs @@ -7,7 +7,6 @@ use crate::{ users::fetch_api_authed, }; use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; -use windmill_queue::TriggerKind; use axum::{ async_trait, @@ -44,6 +43,7 @@ use windmill_audit::{audit_ee::audit_log, ActionKind}; use windmill_common::{ db::UserDB, error::{self, JsonResult}, + triggers::TriggerKind, utils::{not_found_if_none, paginate, report_critical_error, Pagination, StripPath}, worker::{to_raw_value, CLOUD_HOSTED}, INSTANCE_NAME, diff --git a/backend/windmill-api/src/postgres_triggers/trigger.rs b/backend/windmill-api/src/postgres_triggers/trigger.rs index f340666977..aff7a30589 100644 --- a/backend/windmill-api/src/postgres_triggers/trigger.rs +++ b/backend/windmill-api/src/postgres_triggers/trigger.rs @@ -15,7 +15,6 @@ use crate::{ trigger_helpers::TriggerJobArgs, users::fetch_api_authed, }; -use windmill_queue::TriggerKind; use bytes::{BufMut, Bytes, BytesMut}; use chrono::TimeZone; @@ -30,7 +29,8 @@ use serde_json::value::RawValue; use sqlx::types::Json as SqlxJson; use windmill_common::{ - db::UserDB, error, utils::report_critical_error, worker::to_raw_value, INSTANCE_NAME, + db::UserDB, error, triggers::TriggerKind, utils::report_critical_error, worker::to_raw_value, + INSTANCE_NAME, }; use super::{ diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 080c07cc91..90da2187dc 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -223,7 +223,8 @@ async fn list_scripts( "draft_only", "ws_error_handler_muted", "no_main_func", - "codebase IS NOT NULL as use_codebase" + "codebase IS NOT NULL as use_codebase", + "kind" ]) .left() .join("favorite") @@ -298,7 +299,9 @@ async fn list_scripts( if let Some(it) = &lq.is_template { sqlb.and_where_eq("is_template", it); } - if let Some(lowercased_kinds) = lowercased_kinds { + if authed.is_operator { + sqlb.and_where_eq("kind", quote("script")); + } else if let Some(lowercased_kinds) = lowercased_kinds { let safe_kinds = lowercased_kinds .into_iter() .map(sql_builder::quote) @@ -683,36 +686,40 @@ async fn create_script_internal<'c>( let validate_schema = should_validate_schema(&ns.content, &ns.language); - let (no_main_func, has_preprocessor) = match lang { - ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { - let args = windmill_parser_ts::parse_deno_signature(&ns.content, true, true, None); - match args { - Ok(args) => (args.no_main_func, args.has_preprocessor), - Err(e) => { - tracing::warn!( - "Error parsing deno signature when deploying script {}: {:?}", - ns.path, - e - ); - (None, None) + let (no_main_func, has_preprocessor) = if matches!(ns.kind, Some(ScriptKind::Preprocessor)) { + (ns.no_main_func, ns.has_preprocessor) + } else { + match lang { + ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { + let args = windmill_parser_ts::parse_deno_signature(&ns.content, true, true, None); + match args { + Ok(args) => (args.no_main_func, args.has_preprocessor), + Err(e) => { + tracing::warn!( + "Error parsing deno signature when deploying script {}: {:?}", + ns.path, + e + ); + (None, None) + } } } - } - ScriptLang::Python3 => { - let args = windmill_parser_py::parse_python_signature(&ns.content, None, true); - match args { - Ok(args) => (args.no_main_func, args.has_preprocessor), - Err(e) => { - tracing::warn!( - "Error parsing python signature when deploying script {}: {:?}", - ns.path, - e - ); - (None, None) + ScriptLang::Python3 => { + let args = windmill_parser_py::parse_python_signature(&ns.content, None, true); + match args { + Ok(args) => (args.no_main_func, args.has_preprocessor), + Err(e) => { + tracing::warn!( + "Error parsing python signature when deploying script {}: {:?}", + ns.path, + e + ); + (None, None) + } } } + _ => (ns.no_main_func, ns.has_preprocessor), } - _ => (ns.no_main_func, ns.has_preprocessor), }; sqlx::query!( diff --git a/backend/windmill-api/src/trigger_helpers.rs b/backend/windmill-api/src/trigger_helpers.rs index 5d7112e0af..917fe48bfa 100644 --- a/backend/windmill-api/src/trigger_helpers.rs +++ b/backend/windmill-api/src/trigger_helpers.rs @@ -1,4 +1,3 @@ -use quick_cache::sync::Cache; use serde::Deserialize; use serde_json::value::RawValue; use std::collections::HashMap; @@ -6,31 +5,19 @@ use windmill_common::{ error::Result, flows::FlowModuleValue, get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, - scripts::{ScriptHash, ScriptLang}, + jobs::get_has_preprocessor_from_content_and_lang, + scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang}, + triggers::{ + HubOrWorkspaceId, RunnableFormat, RunnableFormatVersion, TriggerKind, + RUNNABLE_FORMAT_VERSION_CACHE, + }, + utils::StripPath, worker::to_raw_value, FlowVersionInfo, }; -use windmill_queue::{PushArgsOwned, TriggerKind}; +use windmill_queue::PushArgsOwned; -use crate::db::DB; - -type RunnableFormatCacheKey = (String, i64, TriggerKind); - -lazy_static::lazy_static! { - pub static ref RUNNABLE_FORMAT_VERSION_CACHE: Cache = Cache::new(1000); -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] -pub struct RunnableFormat { - pub version: RunnableFormatVersion, - pub has_preprocessor: bool, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] -pub enum RunnableFormatVersion { - V1, - V2, -} +use crate::{db::DB, HTTP_CLIENT}; struct ScriptInfo { has_preprocessor: Option, @@ -53,6 +40,7 @@ struct PartialSchema { pub enum RunnableId { FlowPath(String), ScriptId(ScriptId), + HubScript(String), } impl RunnableId { @@ -61,7 +49,11 @@ impl RunnableId { } pub fn from_script_path(path: &str) -> Self { - Self::ScriptId(ScriptId::ScriptPath(path.to_string())) + if path.starts_with("hub/") { + Self::HubScript(path.to_string()) + } else { + Self::ScriptId(ScriptId::ScriptPath(path.to_string())) + } } pub fn from_flow_path(path: &str) -> Self { @@ -156,6 +148,33 @@ struct FlowInfo { schema: Option>, } +fn get_preprocessor_args_from_content_and_language( + content: &str, + language: &ScriptLang, +) -> Result>> { + let args = match language { + ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { + let args = windmill_parser_ts::parse_deno_signature( + &content, + true, + false, + Some("preprocessor".to_string()), + )?; + Some(args.args) + } + ScriptLang::Python3 => { + let args = windmill_parser_py::parse_python_signature( + &content, + Some("preprocessor".to_string()), + false, + )?; + Some(args.args) + } + _ => None, + }; + Ok(args) +} + pub async fn get_runnable_format( runnable_id: RunnableId, workspace_id: &str, @@ -163,11 +182,65 @@ pub async fn get_runnable_format( trigger_kind: &TriggerKind, ) -> Result { let (key, preprocessor_info) = match runnable_id { + RunnableId::HubScript(path) => { + let Some(version) = path.split("/").nth(1) else { + return Err(windmill_common::error::Error::internal_err( + "Invalid hub script path".to_string(), + )); + }; + + let version = match version.parse::() { + Ok(version) => version, + Err(_) => { + return Err(windmill_common::error::Error::internal_err( + "Invalid hub script version".to_string(), + )); + } + }; + + let key = (HubOrWorkspaceId::Hub, version, trigger_kind.clone()); + + let runnable_format = RUNNABLE_FORMAT_VERSION_CACHE.get(&key); + + if let Some(runnable_format) = runnable_format { + tracing::debug!("Using cached runnable format for hub script {path}"); + return Ok(runnable_format); + } + + let hub_script = + get_full_hub_script_by_path(StripPath(path.to_string()), &HTTP_CLIENT, Some(db)) + .await?; + + let has_preprocessor = get_has_preprocessor_from_content_and_lang( + &hub_script.content, + &hub_script.language, + )?; + + let partial_schema = serde_json::from_str(hub_script.schema.get())?; + + ( + key, + if has_preprocessor { + PreprocessorInfo::Preprocessor { + content: hub_script.content, + language: hub_script.language, + } + } else { + PreprocessorInfo::NoPreprocessor { + schema: Some(sqlx::types::Json(partial_schema)), + } + }, + ) + } RunnableId::FlowPath(path) => { let FlowVersionInfo { version, .. } = get_latest_flow_version_info_for_path(db, workspace_id, &path, true).await?; - let key = (workspace_id.to_string(), version, trigger_kind.clone()); + let key = ( + HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()), + version, + trigger_kind.clone(), + ); let runnable_format = RUNNABLE_FORMAT_VERSION_CACHE.get(&key); @@ -181,11 +254,14 @@ pub async fn get_runnable_format( "SELECT value->'preprocessor_module'->'value' as \"preprocessor_module: _\", schema as \"schema: _\" - FROM flow - WHERE workspace_id = $1 - AND path = $2", + FROM flow_version + WHERE + path = $1 + AND workspace_id = $2 + ORDER BY created_at DESC + LIMIT 1", + path, workspace_id, - path ) .fetch_one(db) .await?; @@ -227,7 +303,11 @@ pub async fn get_runnable_format( } RunnableId::ScriptId(script_id) => { let hash = script_id.get_script_hash(workspace_id, db).await?; - let key = (workspace_id.to_string(), hash, trigger_kind.clone()); + let key = ( + HubOrWorkspaceId::WorkspaceId(workspace_id.to_string()), + hash, + trigger_kind.clone(), + ); let runnable_format = RUNNABLE_FORMAT_VERSION_CACHE.get(&key); if let Some(runnable_format) = runnable_format { @@ -256,30 +336,7 @@ pub async fn get_runnable_format( let runnable_format = match preprocessor_info { PreprocessorInfo::Preprocessor { content, language } => { - let args = match language { - ScriptLang::Bun - | ScriptLang::Bunnative - | ScriptLang::Deno - | ScriptLang::Nativets => { - let args = windmill_parser_ts::parse_deno_signature( - &content, - true, - false, - Some("preprocessor".to_string()), - )?; - Some(args.args) - } - ScriptLang::Python3 => { - let args = windmill_parser_py::parse_python_signature( - &content, - Some("preprocessor".to_string()), - false, - )?; - Some(args.args) - } - _ => None, - }; - + let args = get_preprocessor_args_from_content_and_language(&content, &language)?; runnable_format_from_preprocessor_args(args) } PreprocessorInfo::NoPreprocessor { schema } => { diff --git a/backend/windmill-api/src/websocket_triggers.rs b/backend/windmill-api/src/websocket_triggers.rs index f8eda49f83..8bd165844c 100644 --- a/backend/windmill-api/src/websocket_triggers.rs +++ b/backend/windmill-api/src/websocket_triggers.rs @@ -24,6 +24,7 @@ use windmill_audit::{audit_ee::audit_log, ActionKind}; use windmill_common::{ db::UserDB, error::{self, to_anyhow, JsonResult}, + triggers::TriggerKind, utils::{not_found_if_none, paginate, report_critical_error, Pagination, StripPath}, worker::{to_raw_value, CLOUD_HOSTED}, INSTANCE_NAME, @@ -31,8 +32,6 @@ use windmill_common::{ use windmill_git_sync::handle_deployment_metadata; use windmill_queue::PushArgsOwned; -use windmill_queue::TriggerKind; - use crate::{ capture::{insert_capture_payload, WebsocketTriggerConfig}, db::{ApiAuthed, DB}, diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index d3f7ed4c00..7eefe5b2cc 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -71,8 +71,12 @@ const_format.workspace = true crc.workspace = true windmill-macros.workspace = true windmill-parser-sql.workspace = true +windmill-parser-ts.workspace = true +windmill-parser-py.workspace = true jsonwebtoken.workspace = true backon.workspace = true +strum.workspace = true +strum_macros.workspace = true semver.workspace = true croner = "2.0.6" diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index 83752b125c..8966b59c72 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -18,8 +18,9 @@ use crate::{ flow_status::{FlowStatus, RestartedFrom}, flows::{FlowNodeId, FlowValue, Retry}, get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, - scripts::{ScriptHash, ScriptLang}, + scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang}, users::username_to_permissioned_as, + utils::{StripPath, HTTP_CLIENT}, worker::{to_raw_value, TMP_DIR}, FlowVersionInfo, ScriptHashInfo, }; @@ -270,6 +271,7 @@ impl CompletedJob { pub enum JobPayload { ScriptHub { path: String, + apply_preprocessor: bool, }, ScriptHash { hash: ScriptHash, @@ -387,6 +389,25 @@ pub struct OnBehalfOf { pub permissioned_as: String, } +pub fn get_has_preprocessor_from_content_and_lang( + content: &str, + language: &ScriptLang, +) -> error::Result { + let has_preprocessor = match language { + ScriptLang::Bun | ScriptLang::Bunnative | ScriptLang::Deno | ScriptLang::Nativets => { + let args = windmill_parser_ts::parse_deno_signature(&content, true, true, None)?; + args.has_preprocessor.unwrap_or(false) + } + ScriptLang::Python3 => { + let args = windmill_parser_py::parse_python_signature(&content, None, true)?; + args.has_preprocessor.unwrap_or(false) + } + _ => false, + }; + + Ok(has_preprocessor) +} + pub async fn script_path_to_payload<'e, A: sqlx::Acquire<'e, Database = Postgres> + Send>( script_path: &str, db: A, @@ -399,63 +420,74 @@ pub async fn script_path_to_payload<'e, A: sqlx::Acquire<'e, Database = Postgres Option, Option, )> { - let (job_payload, tag, delete_after_use, script_timeout, on_behalf_of) = - if script_path.starts_with("hub/") { - ( - JobPayload::ScriptHub { path: script_path.to_owned() }, - None, - None, - None, - None, - ) + let (job_payload, tag, delete_after_use, script_timeout, on_behalf_of) = if script_path + .starts_with("hub/") + { + let hub_script = + get_full_hub_script_by_path(StripPath(script_path.to_string()), &HTTP_CLIENT, None) + .await?; + + let has_preprocessor = + get_has_preprocessor_from_content_and_lang(&hub_script.content, &hub_script.language)?; + + ( + JobPayload::ScriptHub { + path: script_path.to_owned(), + apply_preprocessor: has_preprocessor && !skip_preprocessor.unwrap_or(false), + }, + None, + None, + None, + None, + ) + } else { + let ScriptHashInfo { + hash, + tag, + concurrency_key, + concurrent_limit, + concurrency_time_window_s, + cache_ttl, + language, + dedicated_worker, + priority, + delete_after_use, + timeout, + has_preprocessor, + on_behalf_of_email, + created_by, + .. + } = get_latest_deployed_hash_for_path(db, w_id, script_path).await?; + + let on_behalf_of = if let Some(email) = on_behalf_of_email { + Some(OnBehalfOf { + email, + permissioned_as: username_to_permissioned_as(created_by.as_str()), + }) } else { - let ScriptHashInfo { - hash, - tag, - concurrency_key, + None + }; + + ( + JobPayload::ScriptHash { + hash: ScriptHash(hash), + path: script_path.to_owned(), + custom_concurrency_key: concurrency_key, concurrent_limit, concurrency_time_window_s, - cache_ttl, + cache_ttl: cache_ttl, language, dedicated_worker, priority, - delete_after_use, - timeout, - has_preprocessor, - on_behalf_of_email, - created_by, - .. - } = get_latest_deployed_hash_for_path(db, w_id, script_path).await?; - - let on_behalf_of = if let Some(email) = on_behalf_of_email { - Some(OnBehalfOf { - email, - permissioned_as: username_to_permissioned_as(created_by.as_str()), - }) - } else { - None - }; - - ( - JobPayload::ScriptHash { - hash: ScriptHash(hash), - path: script_path.to_owned(), - custom_concurrency_key: concurrency_key, - concurrent_limit, - concurrency_time_window_s, - cache_ttl: cache_ttl, - language, - dedicated_worker, - priority, - apply_preprocessor: !skip_preprocessor.unwrap_or(false) - && has_preprocessor.unwrap_or(false), - }, - tag, - delete_after_use, - timeout, - on_behalf_of, - ) - }; + apply_preprocessor: !skip_preprocessor.unwrap_or(false) + && has_preprocessor.unwrap_or(false), + }, + tag, + delete_after_use, + timeout, + on_behalf_of, + ) + }; Ok(( job_payload, tag, diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index e1c2f4aa15..bc7f244787 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -62,6 +62,7 @@ pub mod utils; pub mod variables; pub mod worker; pub mod workspaces; +pub mod triggers; pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50; pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5; diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index 8b67284c4e..e0391767f8 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -247,6 +247,7 @@ pub struct ListableScript { #[sqlx(default)] #[serde(skip_serializing_if = "Option::is_none")] pub deployment_msg: Option, + pub kind: ScriptKind, } fn is_false(x: &bool) -> bool { diff --git a/backend/windmill-common/src/triggers.rs b/backend/windmill-common/src/triggers.rs new file mode 100644 index 0000000000..df68d0543c --- /dev/null +++ b/backend/windmill-common/src/triggers.rs @@ -0,0 +1,79 @@ +use quick_cache::sync::Cache; +use serde::{Deserialize, Serialize}; +use std::fmt; +use strum::EnumIter; + +#[derive(Eq, PartialEq, Hash)] +pub enum HubOrWorkspaceId { + Hub, + WorkspaceId(String), +} + +type RunnableFormatCacheKey = (HubOrWorkspaceId, i64, TriggerKind); + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] +pub struct RunnableFormat { + pub version: RunnableFormatVersion, + pub has_preprocessor: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)] +pub enum RunnableFormatVersion { + V1, + V2, +} + +lazy_static::lazy_static! { + pub static ref RUNNABLE_FORMAT_VERSION_CACHE: Cache = Cache::new(1000); +} + +#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, EnumIter)] +#[sqlx(type_name = "TRIGGER_KIND", rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum TriggerKind { + Webhook, + Http, + Websocket, + Kafka, + Email, + Nats, + Mqtt, + Sqs, + Postgres, + Gcp, +} + +impl TriggerKind { + pub fn to_key(&self) -> String { + match self { + TriggerKind::Webhook => "webhook".to_string(), + TriggerKind::Http => "http".to_string(), + TriggerKind::Websocket => "websocket".to_string(), + TriggerKind::Kafka => "kafka".to_string(), + TriggerKind::Email => "email".to_string(), + TriggerKind::Nats => "nats".to_string(), + TriggerKind::Mqtt => "mqtt".to_string(), + TriggerKind::Sqs => "sqs".to_string(), + TriggerKind::Postgres => "postgres".to_string(), + TriggerKind::Gcp => "gcp".to_string(), + } + } +} + +impl fmt::Display for TriggerKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + TriggerKind::Webhook => "webhook", + TriggerKind::Http => "http", + TriggerKind::Websocket => "websocket", + TriggerKind::Kafka => "kafka", + TriggerKind::Email => "email", + TriggerKind::Nats => "nats", + TriggerKind::Mqtt => "mqtt", + TriggerKind::Sqs => "sqs", + TriggerKind::Postgres => "postgres", + TriggerKind::Gcp => "gcp", + }; + write!(f, "{}", s) + } +} diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 7a3541d345..1ff103cd9a 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -6,7 +6,6 @@ * LICENSE-AGPL for a copy of the license. */ -use std::fmt; use std::{collections::HashMap, sync::Arc, vec}; use anyhow::Context; @@ -1944,40 +1943,6 @@ async fn handle_successful_schedule<'a, 'c, T: Serialize + Send + Sync>( Ok(()) } -#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)] -#[sqlx(type_name = "TRIGGER_KIND", rename_all = "lowercase")] -#[serde(rename_all = "lowercase")] -pub enum TriggerKind { - Webhook, - Http, - Websocket, - Kafka, - Email, - Nats, - Mqtt, - Sqs, - Postgres, - Gcp -} - - -impl TriggerKind { - pub fn to_key(&self) -> String { - match self { - TriggerKind::Webhook => "webhook".to_string(), - TriggerKind::Http => "http".to_string(), - TriggerKind::Websocket => "websocket".to_string(), - TriggerKind::Kafka => "kafka".to_string(), - TriggerKind::Email => "email".to_string(), - TriggerKind::Nats => "nats".to_string(), - TriggerKind::Mqtt => "mqtt".to_string(), - TriggerKind::Sqs => "sqs".to_string(), - TriggerKind::Postgres => "postgres".to_string(), - TriggerKind::Gcp => "gcp".to_string(), - } - } -} - #[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone)] #[sqlx(type_name = "JOB_TRIGGER_KIND", rename_all = "lowercase")] #[serde(rename_all = "lowercase")] @@ -1995,23 +1960,6 @@ pub enum JobTriggerKind { Gcp } -impl fmt::Display for TriggerKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - TriggerKind::Webhook => "webhook", - TriggerKind::Http => "http", - TriggerKind::Websocket => "websocket", - TriggerKind::Kafka => "kafka", - TriggerKind::Email => "email", - TriggerKind::Nats => "nats", - TriggerKind::Mqtt => "mqtt", - TriggerKind::Sqs => "sqs", - TriggerKind::Postgres => "postgres", - TriggerKind::Gcp => "gcp", - }; - write!(f, "{}", s) - } -} #[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)] pub struct MiniPulledJob { @@ -3587,7 +3535,7 @@ pub async fn push<'c, 'd>( None, None, ), - JobPayload::ScriptHub { path } => { + JobPayload::ScriptHub { path, apply_preprocessor } => { if path == "hub/7771/slack" || path == "hub/7836/slack" || path == "hub/9084/slack" { // these scripts send app reports to slack // they use the slack bot token and should therefore be run with permissions to access it @@ -3595,6 +3543,10 @@ pub async fn push<'c, 'd>( email = SUPERADMIN_NOTIFICATION_EMAIL; } + if apply_preprocessor { + preprocessed = Some(false); + } + let hub_script = get_full_hub_script_by_path(StripPath(path.clone()), &HTTP_CLIENT, Some(_db)) .await?; diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 235cf9472b..ede454281d 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -399,9 +399,10 @@ impl AuthedClient { ) .send() .await - .context(format!( - "Executing request from authed http client to {url} with query {query:?}", - )) + .map_err(|e| { + tracing::error!("Error executing get request from authed http client to {url} with query {query:?}: {e}"); + anyhow::anyhow!("Error executing get request from authed http client to {url} with query {query:?}: {e}") + }) } pub async fn get_id_token(&self, audience: &str) -> anyhow::Result { diff --git a/frontend/src/lib/components/ModulePreview.svelte b/frontend/src/lib/components/ModulePreview.svelte index 510637bf27..f5b8fb734d 100644 --- a/frontend/src/lib/components/ModulePreview.svelte +++ b/frontend/src/lib/components/ModulePreview.svelte @@ -68,7 +68,7 @@ val.path, script.content, script.language, - args, + mod.id === 'preprocessor' ? { _ENTRYPOINT_OVERRIDE: 'preprocessor', ...args } : args, $flowStore?.tag ?? (val.tag_override ? val.tag_override : script.tag), script.lock, val.hash ?? script.hash diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 31f3400ee3..6908cf66f3 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -430,9 +430,19 @@ } script.schema = script.schema ?? emptySchema() try { - const result = await inferArgs(script.language, script.content, script.schema as any) - script.no_main_func = result?.no_main_func || undefined - script.has_preprocessor = result?.has_preprocessor || undefined + const result = await inferArgs( + script.language, + script.content, + script.schema as any, + script.kind === 'preprocessor' ? 'preprocessor' : undefined + ) + if (script.kind === 'preprocessor') { + script.no_main_func = undefined + script.has_preprocessor = undefined + } else { + script.no_main_func = result?.no_main_func || undefined + script.has_preprocessor = result?.has_preprocessor || undefined + } } catch (error) { sendUserToast(`Could not parse code, are you sure it is valid?`, true) } @@ -532,8 +542,9 @@ if (!disableHistoryChange) { history.replaceState(history.state, '', `/scripts/edit/${script.path}`) } - if (stay) { + if (stay || script.kind !== 'script' || script.no_main_func) { script.parent_hash = newHash + sendUserToast('Deployed') } else { dispatch('deploy', newHash) } @@ -574,9 +585,19 @@ } script.schema = script.schema ?? emptySchema() try { - const result = await inferArgs(script.language, script.content, script.schema as any) - script.no_main_func = result?.no_main_func || undefined - script.has_preprocessor = result?.has_preprocessor || undefined + const result = await inferArgs( + script.language, + script.content, + script.schema as any, + script.kind === 'preprocessor' ? 'preprocessor' : undefined + ) + if (script.kind === 'preprocessor') { + script.no_main_func = undefined + script.has_preprocessor = undefined + } else { + script.no_main_func = result?.no_main_func || undefined + script.has_preprocessor = result?.has_preprocessor || undefined + } } catch (error) { sendUserToast(`Could not parse code, are you sure it is valid?`, true) } @@ -708,7 +729,7 @@ } ] : []), - ...(!script.draft_only + ...(!script.draft_only && script.kind === 'script' && !script.no_main_func ? [ { label: 'Exit & See details', diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index b464baece4..0ead505cfc 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -147,7 +147,9 @@ path, code, lang, - selectedTab === 'preprocessor' ? { _ENTRYPOINT_OVERRIDE: 'preprocessor', ...args } : args, + selectedTab === 'preprocessor' || kind === 'preprocessor' + ? { _ENTRYPOINT_OVERRIDE: 'preprocessor', ...args } + : args, tag ) setFocusToLogs() @@ -171,13 +173,20 @@ nlang ?? lang, code, nschema, - selectedTab === 'preprocessor' ? 'preprocessor' : undefined + selectedTab === 'preprocessor' || kind === 'preprocessor' ? 'preprocessor' : undefined ) - hasPreprocessor = - (selectedTab === 'preprocessor' ? !result?.no_main_func : result?.has_preprocessor) ?? false - if (!hasPreprocessor && selectedTab === 'preprocessor') { + if (kind === 'preprocessor') { + hasPreprocessor = false selectedTab = 'main' + } else { + hasPreprocessor = + (selectedTab === 'preprocessor' ? !result?.no_main_func : result?.has_preprocessor) ?? + false + + if (!hasPreprocessor && selectedTab === 'preprocessor') { + selectedTab = 'main' + } } validCode = true diff --git a/frontend/src/lib/components/common/table/ScriptRow.svelte b/frontend/src/lib/components/common/table/ScriptRow.svelte index 8330566bc5..d0bd0fadfe 100644 --- a/frontend/src/lib/components/common/table/ScriptRow.svelte +++ b/frontend/src/lib/components/common/table/ScriptRow.svelte @@ -16,7 +16,7 @@ import Row from './Row.svelte' import DraftBadge from '$lib/components/DraftBadge.svelte' import { sendUserToast } from '$lib/toast' - import { copyToClipboard, DELETE, isOwner } from '$lib/utils' + import { capitalize, copyToClipboard, DELETE, isOwner } from '$lib/utils' import { isDeployable } from '$lib/utils_deployable' import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte' @@ -92,7 +92,7 @@ {/if} archived + Archived {/if} - {#if script.no_main_func} + {#if script.no_main_func && script.kind !== 'preprocessor'} {/if} + {#if script.kind !== 'script'} + {capitalize(script.kind)} + {/if}
@@ -226,7 +229,7 @@ disabled: script.archived, hide: $userStore?.operator } - ] + ] : []), { displayName: 'View runs', @@ -301,7 +304,7 @@ disabled: !owner, hide: $userStore?.operator } - ] + ] : []), ...($userStore?.is_admin || $userStore?.is_super_admin ? [ @@ -321,7 +324,7 @@ disabled: !script.canWrite, hide: $userStore?.operator } - ] + ] : []) ] }}