diff --git a/backend/windmill-api-flows/src/flows.rs b/backend/windmill-api-flows/src/flows.rs index 4ca036c40c..f5d7b6cbe6 100644 --- a/backend/windmill-api-flows/src/flows.rs +++ b/backend/windmill-api-flows/src/flows.rs @@ -20,6 +20,7 @@ use windmill_api_auth::{ }; use windmill_common::workspaces::{check_deploy_rules, RuleCheckResult}; use windmill_common::{ + user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery}, utils::{WithStarredInfoQuery, HTTP_CLIENT}, webhook::{WebhookMessage, WebhookShared}, DB, @@ -1357,16 +1358,25 @@ async fn get_deployment_status( Ok(Json(deployment_status)) } +#[derive(Deserialize)] +struct GetFlowByPathQuery { + #[serde(flatten)] + starred: WithStarredInfoQuery, + #[serde(flatten)] + draft: WithDraftQuery, +} + async fn get_flow_by_path( authed: ApiAuthed, Extension(user_db): Extension, + Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, - Query(query): Query, -) -> JsonResult { + Query(query): Query, +) -> JsonResult { let path = path.to_path(); check_scopes(&authed, || format!("flows:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; - let flow_o = if query.with_starred_info.unwrap_or(false) { + let flow_o = if query.starred.with_starred_info.unwrap_or(false) { sqlx::query_as::<_, FlowWithStarred>( r#" SELECT @@ -1403,7 +1413,7 @@ async fn get_flow_by_path( "#, ) .bind(path) - .bind(w_id) + .bind(&w_id) .bind(&authed.username) .fetch_optional(&mut *tx) .await? @@ -1439,7 +1449,7 @@ async fn get_flow_by_path( "#, ) .bind(path) - .bind(w_id) + .bind(&w_id) .fetch_optional(&mut *tx) .await? }; @@ -1447,7 +1457,17 @@ async fn get_flow_by_path( tx.commit().await?; let flow = not_found_if_none(flow_o, "Flow", path)?; - Ok(Json(flow)) + let overlay = maybe_overlay_draft( + &db, + &w_id, + &authed.email, + UserDraftItemKind::Flow, + path, + query.draft.get_draft, + flow, + ) + .await?; + Ok(Json(overlay)) } async fn exists_flow_by_path( diff --git a/backend/windmill-api-schedule/src/lib.rs b/backend/windmill-api-schedule/src/lib.rs index 29fa215800..311fbf68d2 100644 --- a/backend/windmill-api-schedule/src/lib.rs +++ b/backend/windmill-api-schedule/src/lib.rs @@ -25,6 +25,7 @@ use windmill_common::{ db::UserDB, error::{Error, JsonResult, Result}, schedule::Schedule, + user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery}, utils::{ escape_ilike_pattern, not_found_if_none, paginate, Pagination, ScheduleType, StripPath, }, @@ -808,8 +809,10 @@ async fn list_schedule_with_jobs( async fn get_schedule( authed: ApiAuthed, Extension(user_db): Extension, + Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, -) -> JsonResult { + Query(q): Query, +) -> JsonResult { let path = path.to_path(); check_scopes(&authed, || format!("schedules:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; @@ -817,7 +820,17 @@ async fn get_schedule( let schedule_o = windmill_queue::schedule::get_schedule_opt(&mut *tx, &w_id, path).await?; let schedule = not_found_if_none(schedule_o, "Schedule", path)?; tx.commit().await?; - Ok(Json(schedule)) + let overlay = maybe_overlay_draft( + &db, + &w_id, + &authed.email, + UserDraftItemKind::TriggerSchedule, + path, + q.get_draft, + schedule, + ) + .await?; + Ok(Json(overlay)) } async fn exists_schedule( diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 55f44d4a58..8c20a3e9ae 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -5324,13 +5324,16 @@ paths: in: query schema: type: boolean + - $ref: "#/components/parameters/GetDraft" responses: "200": description: variable content: application/json: schema: - $ref: "#/components/schemas/ListableVariable" + allOf: + - $ref: "#/components/schemas/ListableVariable" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/variables/get_value/{path}: get: @@ -6647,13 +6650,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: resource content: application/json: schema: - $ref: "#/components/schemas/Resource" + allOf: + - $ref: "#/components/schemas/ListableResource" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/resources/get_value_interpolated/{path}: get: @@ -9676,13 +9682,16 @@ paths: in: query schema: type: boolean + - $ref: "#/components/parameters/GetDraft" responses: "200": description: flow details content: application/json: schema: - $ref: "#/components/schemas/Flow" + allOf: + - $ref: "#/components/schemas/Flow" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/flows/deployment_status/p/{path}: get: @@ -10459,13 +10468,16 @@ paths: in: query schema: type: boolean + - $ref: "#/components/parameters/GetDraft" responses: "200": description: app details content: application/json: schema: - $ref: "#/components/schemas/AppWithLastVersion" + allOf: + - $ref: "#/components/schemas/AppWithLastVersion" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/apps/get/lite/{path}: get: @@ -13604,13 +13616,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: schedule deleted content: application/json: schema: - $ref: "#/components/schemas/Schedule" + allOf: + - $ref: "#/components/schemas/Schedule" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/schedules/exists/{path}: get: @@ -13893,13 +13908,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: http trigger deleted content: application/json: schema: - $ref: "#/components/schemas/HttpTrigger" + allOf: + - $ref: "#/components/schemas/HttpTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/http_triggers/list: get: @@ -14099,13 +14117,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: websocket trigger deleted content: application/json: schema: - $ref: "#/components/schemas/WebsocketTrigger" + allOf: + - $ref: "#/components/schemas/WebsocketTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/websocket_triggers/list: get: @@ -14304,13 +14325,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: kafka trigger deleted content: application/json: schema: - $ref: "#/components/schemas/KafkaTrigger" + allOf: + - $ref: "#/components/schemas/KafkaTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/kafka_triggers/list: get: @@ -14550,13 +14574,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: nats trigger deleted content: application/json: schema: - $ref: "#/components/schemas/NatsTrigger" + allOf: + - $ref: "#/components/schemas/NatsTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/nats_triggers/list: get: @@ -14750,13 +14777,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: sqs trigger deleted content: application/json: schema: - $ref: "#/components/schemas/SqsTrigger" + allOf: + - $ref: "#/components/schemas/SqsTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/sqs_triggers/list: get: @@ -15543,13 +15573,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: mqtt trigger deleted content: application/json: schema: - $ref: "#/components/schemas/MqttTrigger" + allOf: + - $ref: "#/components/schemas/MqttTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/mqtt_triggers/list: get: @@ -15743,13 +15776,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: gcp trigger deleted content: application/json: schema: - $ref: "#/components/schemas/GcpTrigger" + allOf: + - $ref: "#/components/schemas/GcpTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/gcp_triggers/list: get: @@ -16010,13 +16046,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: azure trigger content: application/json: schema: - $ref: "#/components/schemas/AzureTrigger" + allOf: + - $ref: "#/components/schemas/AzureTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/azure_triggers/list: get: @@ -16553,13 +16592,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: get postgres trigger content: application/json: schema: - $ref: "#/components/schemas/PostgresTrigger" + allOf: + - $ref: "#/components/schemas/PostgresTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/postgres_triggers/list: get: @@ -16753,13 +16795,16 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/GetDraft" responses: "200": description: email trigger retrieved content: application/json: schema: - $ref: "#/components/schemas/EmailTrigger" + allOf: + - $ref: "#/components/schemas/EmailTrigger" + - $ref: "#/components/schemas/UserDraftOverlay" /w/{workspace}/email_triggers/list: get: diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 07b6916aec..553d5748bf 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -58,6 +58,7 @@ use windmill_common::{ get_payload_tag_from_prefixed_path, resolve_delete_after_secs, schedule_job_deletion, JobPayload, RawCode, }, + user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery}, users::username_to_permissioned_as, utils::{ http_get_from_hub, not_found_if_none, paginate, query_elems_from_hub, require_admin, @@ -535,17 +536,26 @@ async fn get_raw_app_data( // Ok(Json(version)) // } +#[derive(Deserialize)] +struct GetAppQuery { + #[serde(flatten)] + starred: WithStarredInfoQuery, + #[serde(flatten)] + draft: WithDraftQuery, +} + async fn get_app( authed: ApiAuthed, Extension(user_db): Extension, + Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, - Query(query): Query, -) -> JsonResult { + Query(query): Query, +) -> JsonResult { let path = path.to_path(); check_scopes(&authed, || format!("apps:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; - let app_o = if query.with_starred_info.unwrap_or(false) { + let app_o = if query.starred.with_starred_info.unwrap_or(false) { sqlx::query_as::<_, AppWithLastVersionAndStarred>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, app.extra_perms, app_version.value, @@ -554,9 +564,9 @@ async fn get_app( JOIN app_version ON app_version.id = app.versions[array_upper(app.versions, 1)] LEFT JOIN favorite - ON favorite.favorite_kind = 'app' - AND favorite.workspace_id = app.workspace_id - AND favorite.path = app.path + ON favorite.favorite_kind = 'app' + AND favorite.workspace_id = app.workspace_id + AND favorite.path = app.path AND favorite.usr = $3 WHERE app.path = $1 AND app.workspace_id = $2", ) @@ -581,7 +591,24 @@ async fn get_app( tx.commit().await?; let app = not_found_if_none(app_o, "App", path)?; - Ok(Json(app)) + // The same `app` table backs both regular apps and raw apps; the + // `raw_app` flag on the row picks which draft kind to look up. + let kind = if app.app.raw_app { + UserDraftItemKind::RawApp + } else { + UserDraftItemKind::App + }; + let overlay = maybe_overlay_draft( + &db, + &w_id, + &authed.email, + kind, + path, + query.draft.get_draft, + app, + ) + .await?; + Ok(Json(overlay)) } async fn get_app_lite( diff --git a/backend/windmill-store/src/resources.rs b/backend/windmill-store/src/resources.rs index 3e5292e805..aa6b8c1903 100644 --- a/backend/windmill-store/src/resources.rs +++ b/backend/windmill-store/src/resources.rs @@ -43,6 +43,7 @@ use windmill_common::{ db::{DbWithOptAuthed, UserDB}, error::{self, Error, JsonResult, Result}, get_database_url, + user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery}, utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath}, variables, worker::{CLOUD_HOSTED, WINDMILL_DIR}, @@ -360,7 +361,8 @@ async fn get_resource( Extension(user_db): Extension, Extension(db): Extension, Path((w_id, path)): Path<(String, StripPath)>, -) -> JsonResult { + Query(q): Query, +) -> JsonResult { let path = path.to_path(); check_scopes(&authed, || format!("resources:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; @@ -391,7 +393,17 @@ async fn get_resource( explain_resource_perm_error(&path, &w_id, &db, &authed).await?; } let resource = not_found_if_none(resource_o, "Resource", path)?; - Ok(Json(resource)) + let overlay = maybe_overlay_draft( + &db, + &w_id, + &authed.email, + UserDraftItemKind::Resource, + path, + q.get_draft, + resource, + ) + .await?; + Ok(Json(overlay)) } async fn exists_resource( diff --git a/backend/windmill-store/src/variables.rs b/backend/windmill-store/src/variables.rs index 2d767b393b..efdf941d94 100644 --- a/backend/windmill-store/src/variables.rs +++ b/backend/windmill-store/src/variables.rs @@ -35,6 +35,7 @@ use windmill_common::{ db::{DbWithOptAuthed, UserDB}, error::{Error, JsonResult, Result}, scripts::ScriptHash, + user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery}, utils::{not_found_if_none, paginate, Pagination, StripPath, WarnAfterExt}, variables::{ build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable, @@ -207,6 +208,8 @@ async fn list_variables( struct GetVariableQuery { decrypt_secret: Option, include_encrypted: Option, + #[serde(flatten)] + draft: WithDraftQuery, } async fn get_variable( @@ -215,7 +218,7 @@ async fn get_variable( Extension(db): Extension, Query(q): Query, Path((w_id, path)): Path<(String, StripPath)>, -) -> JsonResult { +) -> JsonResult { let path = path.to_path(); check_scopes(&authed, || format!("variables:read:{}", path))?; @@ -302,7 +305,17 @@ async fn get_variable( variable }; - Ok(Json(r)) + let overlay = maybe_overlay_draft( + &db, + &w_id, + &authed.email, + UserDraftItemKind::Variable, + path, + q.draft.get_draft, + r, + ) + .await?; + Ok(Json(overlay)) } #[derive(Deserialize)] diff --git a/backend/windmill-trigger/src/handler.rs b/backend/windmill-trigger/src/handler.rs index f425f97b53..f2745a1791 100644 --- a/backend/windmill-trigger/src/handler.rs +++ b/backend/windmill-trigger/src/handler.rs @@ -16,6 +16,7 @@ use windmill_api_auth::{check_scopes, ApiAuthed}; use windmill_common::{ db::UserDB, error::{Error, JsonResult, Result}, + user_drafts::{maybe_overlay_draft, UserDraftItemKind, WithDraftOverlay, WithDraftQuery}, utils::{paginate, Pagination, StripPath}, worker::CLOUD_HOSTED, DB, @@ -106,6 +107,29 @@ pub trait TriggerCrud: Send + Sync + 'static { &Self::ROUTE_PREFIX[1..] } + /// `UserDraftItemKind` for the per-user `draft` table lookup. Defaults + /// to derive from `TRIGGER_TYPE` so each impl gets it for free as long + /// as the string matches the canonical kind (e.g. `"http"` → + /// `TriggerHttp`). Override only when the mapping isn't 1:1. + fn user_draft_item_kind() -> UserDraftItemKind { + match Self::TRIGGER_TYPE { + "http" => UserDraftItemKind::TriggerHttp, + "websocket" => UserDraftItemKind::TriggerWebsocket, + "kafka" => UserDraftItemKind::TriggerKafka, + "nats" => UserDraftItemKind::TriggerNats, + "sqs" => UserDraftItemKind::TriggerSqs, + "mqtt" => UserDraftItemKind::TriggerMqtt, + "gcp" => UserDraftItemKind::TriggerGcp, + "azure" => UserDraftItemKind::TriggerAzure, + "postgres" => UserDraftItemKind::TriggerPostgres, + "email" => UserDraftItemKind::TriggerEmail, + other => panic!( + "TriggerCrud impl with TRIGGER_TYPE = {:?} must override user_draft_item_kind()", + other + ), + } + } + async fn create_trigger( &self, db: &DB, @@ -551,8 +575,10 @@ async fn get_trigger( Extension(handler): Extension>, authed: ApiAuthed, Extension(user_db): Extension, + Extension(db): Extension, Path((workspace_id, path)): Path<(String, StripPath)>, -) -> JsonResult { + Query(q): Query, +) -> JsonResult { let path = path.to_path(); check_scopes(&authed, || { format!("{}:read:{}", T::scope_domain_name(), &path) @@ -565,7 +591,17 @@ async fn get_trigger( tx.commit().await?; - Ok(Json(trigger)) + let overlay = maybe_overlay_draft( + &db, + &workspace_id, + &authed.email, + T::user_draft_item_kind(), + path, + q.get_draft, + trigger, + ) + .await?; + Ok(Json(overlay)) } async fn update_trigger( diff --git a/frontend/src/lib/components/ResourceEditor.svelte b/frontend/src/lib/components/ResourceEditor.svelte index 9a5c69dbd2..2ea6b21652 100644 --- a/frontend/src/lib/components/ResourceEditor.svelte +++ b/frontend/src/lib/components/ResourceEditor.svelte @@ -219,9 +219,7 @@ const s: ResourceState = { path: '', description: '', - args: (defaultValues && Object.keys(defaultValues).length > 0 - ? defaultValues - : {}) as any, + args: (defaultValues && Object.keys(defaultValues).length > 0 ? defaultValues : {}) as any, labels: undefined, wsSpecific: false } @@ -238,9 +236,12 @@ if (ws in states) return untrack(() => { Promise.all([ - ResourceService.getResource({ workspace: ws, path: initialPath }), + ResourceService.getResource({ workspace: ws, path: initialPath, getDraft: true }), getUserExt(ws) ]).then(([r, user]) => { + if (r.is_draft) { + sendUserToast('Loaded your saved draft') + } fetchedResources[ws] = r fetchedRev[ws] = r.edited_at const s: ResourceState = { diff --git a/frontend/src/lib/components/VariableEditor.svelte b/frontend/src/lib/components/VariableEditor.svelte index 962009e63d..2330762bd8 100644 --- a/frontend/src/lib/components/VariableEditor.svelte +++ b/frontend/src/lib/components/VariableEditor.svelte @@ -154,9 +154,17 @@ if (ws in states) return untrack(() => { Promise.all([ - VariableService.getVariable({ workspace: ws, path: p, decryptSecret: false }), + VariableService.getVariable({ + workspace: ws, + path: p, + decryptSecret: false, + getDraft: true + }), getUserExt(ws) ]).then(([v, user]) => { + if (v.is_draft) { + sendUserToast('Loaded your saved draft') + } fetchedRev[ws] = v.edited_at const s: VariableState = { path: v.path, diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index 6f2cd10e3b..f291e6cd9d 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -1081,10 +1081,7 @@ async function readWorkspaceItem( true ) case 'resource': - return resourceToItem( - (await ResourceService.getResource({ workspace, path })) as ListableResource, - true - ) + return resourceToItem(await ResourceService.getResource({ workspace, path }), true) case 'variable': // Never expose the value, even when read directly. Pass decryptSecret=false // to avoid materializing secret values server-side. @@ -2568,9 +2565,10 @@ async function patchAppFile( } async function recomputeAppPolicy(value: AppDraftValue): Promise { - const policy = (await updateRawAppPolicy(value.runnables as any, value.policy as any)) as NonNullable< - AppDraftValue['policy'] - > + const policy = (await updateRawAppPolicy( + value.runnables as any, + value.policy as any + )) as NonNullable if (!policy.execution_mode) { policy.execution_mode = 'publisher' } @@ -3045,7 +3043,8 @@ export function prepareGlobalUserMessage( (context) => context.type === 'workspace_script' || context.type === 'workspace_flow' ) const activeEditor = - options.activeEditor ?? (options.workspace ? getActiveGlobalEditorContext(options.workspace) : undefined) + options.activeEditor ?? + (options.workspace ? getActiveGlobalEditorContext(options.workspace) : undefined) let content = '' if (activeEditor) { diff --git a/frontend/src/lib/components/triggers/azure/AzureTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/azure/AzureTriggerEditorInner.svelte index e29ab8703c..215bda6226 100644 --- a/frontend/src/lib/components/triggers/azure/AzureTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/azure/AzureTriggerEditorInner.svelte @@ -191,8 +191,12 @@ try { const s = await AzureTriggerService.getAzureTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } catch (error) { sendUserToast(`Could not load Azure trigger: ${error.body}`, true) diff --git a/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte index 486dc64832..795d5174ae 100644 --- a/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/email/EmailTriggerEditorInner.svelte @@ -212,8 +212,12 @@ } else { const s = await EmailTriggerService.getEmailTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } diff --git a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte index f0c3f9150a..603ab34380 100644 --- a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorInner.svelte @@ -195,8 +195,12 @@ try { const s = await GcpTriggerService.getGcpTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } catch (error) { sendUserToast(`Could not load GCP Pub/Sub trigger: ${error.body}`, true) diff --git a/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte b/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte index fef0cdb11a..787d50451a 100644 --- a/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte +++ b/frontend/src/lib/components/triggers/http/RouteEditorInner.svelte @@ -345,8 +345,12 @@ } else { const s = await HttpTriggerService.getHttpTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } diff --git a/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte index c38ef2e84e..df29dad5df 100644 --- a/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/kafka/KafkaTriggerEditorInner.svelte @@ -251,8 +251,12 @@ } else { const s = await KafkaTriggerService.getKafkaTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } } diff --git a/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte index d8aeba8edc..4e8d760ecc 100644 --- a/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/mqtt/MqttTriggerEditorInner.svelte @@ -251,8 +251,12 @@ } else { const s = await MqttTriggerService.getMqttTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } } catch (error) { diff --git a/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte index 3646da068a..ae6529be66 100644 --- a/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/nats/NatsTriggerEditorInner.svelte @@ -233,8 +233,12 @@ } else { const s = await NatsTriggerService.getNatsTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } } diff --git a/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte index 2ca55c4da8..e17c3df8f6 100644 --- a/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/postgres/PostgresTriggerEditorInner.svelte @@ -377,8 +377,12 @@ } else { const s = await PostgresTriggerService.getPostgresTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } const publication_data = await PostgresTriggerService.getPostgresPublication({ path: s.postgres_resource_path, diff --git a/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte b/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte index fb5f998aad..113cc69801 100644 --- a/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte +++ b/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte @@ -285,10 +285,15 @@ try { let s: Schedule | undefined if (schedule_path) { - s = await ScheduleService.getSchedule({ + const resp = await ScheduleService.getSchedule({ workspace: $workspaceStore!, - path: schedule_path + path: schedule_path, + getDraft: true }) + if (resp.is_draft) { + sendUserToast('Loaded your saved draft') + } + s = resp initNewPath = true } else if (defaultValues) { s = defaultValues @@ -451,8 +456,12 @@ try { const s = await ScheduleService.getSchedule({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s.is_draft) { + sendUserToast('Loaded your saved draft') + } await loadScheduleCfg(s) } catch (err) { sendUserToast(`Could not load schedule: ${err}`, true) diff --git a/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte index 205db6f925..8dc2d4d009 100644 --- a/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/sqs/SqsTriggerEditorInner.svelte @@ -228,8 +228,12 @@ } else { const s = await SqsTriggerService.getSqsTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } } catch (error) { diff --git a/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte b/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte index 181e6ff2b3..d75669180a 100644 --- a/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte +++ b/frontend/src/lib/components/triggers/websocket/WebsocketTriggerEditorInner.svelte @@ -308,8 +308,12 @@ } else { const s = await WebsocketTriggerService.getWebsocketTrigger({ workspace: $workspaceStore!, - path: initialPath + path: initialPath, + getDraft: true }) + if (s?.is_draft) { + sendUserToast('Loaded your saved draft') + } loadTriggerConfig(s) } } diff --git a/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte index b436fedeaa..be959c7148 100644 --- a/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte @@ -90,9 +90,13 @@ const tok = ++loadAppToken const backendApp = await AppService.getAppByPath({ path: page.params.path ?? '', - workspace: $workspaceStore! + workspace: $workspaceStore!, + getDraft: true }) if (tok !== loadAppToken) return + if (backendApp.is_draft) { + sendUserToast('Loaded your saved draft') + } const backendApp_ = structuredClone(stateSnapshot(backendApp)) savedApp = { summary: backendApp_.summary, diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte index 0718e1754e..e30a8c28bd 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte @@ -177,9 +177,13 @@ const tok = ++loadAppToken const backendApp = await AppService.getAppByPath({ path: page.params.path ?? '', - workspace: $workspaceStore! + workspace: $workspaceStore!, + getDraft: true }) if (tok !== loadAppToken) return + if (backendApp.is_draft) { + sendUserToast('Loaded your saved draft') + } const backendApp_ = structuredClone(stateSnapshot(backendApp)) savedApp = { summary: backendApp_.summary, diff --git a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte index d47bf76ebb..335fdbca2e 100644 --- a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte @@ -145,9 +145,13 @@ const backendFlow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, - path: page.params.path ?? '' + path: page.params.path ?? '', + getDraft: true }) if (tok !== loadFlowToken) return + if (backendFlow.is_draft) { + sendUserToast('Loaded your saved draft') + } savedFlow = structuredClone($state.snapshot(backendFlow)) as Flow const localDraft = flowHandle.draft