diff --git a/backend/.sqlx/query-1461a023390f674b0d2e286b506c0838c7cad23619984f44fb4c88d377acffc7.json b/backend/.sqlx/query-1461a023390f674b0d2e286b506c0838c7cad23619984f44fb4c88d377acffc7.json new file mode 100644 index 0000000000..b275265938 --- /dev/null +++ b/backend/.sqlx/query-1461a023390f674b0d2e286b506c0838c7cad23619984f44fb4c88d377acffc7.json @@ -0,0 +1,47 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg,\n av.created_by as created_by, av.created_at as created_at\n FROM app a JOIN app_version av ON av.id = a.versions[array_upper(a.versions, 1)]\n LEFT JOIN deployment_metadata dm ON av.id = dm.app_version\n WHERE a.workspace_id = $1 AND a.path = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "app_id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "version_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "deployment_msg", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_by", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + true, + false, + false + ] + }, + "hash": "1461a023390f674b0d2e286b506c0838c7cad23619984f44fb4c88d377acffc7" +} diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index fabf9fd0cd..e4d79c00a4 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -1273,15 +1273,21 @@ async fn get_latest_version( let path = path.to_path(); check_scopes(&authed, || format!("apps:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; + // The head is the tail of `app.versions` — the version the runtime serves. Deploys + // append to it under the app row's lock, whereas `app_version.created_at` is the + // deploying transaction's start time, so two that overlap can carry it in either + // order and the newest timestamp is then not the one that landed last. let row = sqlx::query!( "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg, av.created_by as created_by, av.created_at as created_at - FROM app a LEFT JOIN app_version av ON a.id = av.app_id LEFT JOIN deployment_metadata dm ON av.id = dm.app_version - WHERE a.workspace_id = $1 AND a.path = $2 - ORDER BY av.created_at DESC", + FROM app a JOIN app_version av ON av.id = a.versions[array_upper(a.versions, 1)] + LEFT JOIN deployment_metadata dm ON av.id = dm.app_version + WHERE a.workspace_id = $1 AND a.path = $2", w_id, path, - ).fetch_optional(&mut *tx).await?; + ) + .fetch_optional(&mut *tx) + .await?; tx.commit().await?; if let Some(row) = row { diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte index 89db46f3be..8a0f11c5a1 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte @@ -393,13 +393,13 @@ custom_path: customPath, labels: $state.snapshot(labels) } - const appHistory = await AppService.getAppHistoryByPath({ + const head = await AppService.getAppLatestVersion({ workspace: $workspaceStore!, path: npath }) // `version` is what is deployed now, which is the deploy guard's fallback head; the // deploy's own answer is the base, and the two differ when another landed beside it. - version = appHistory[0]?.version + version = head?.version // Re-pin the fork base to the version just written: the editor stays open, so a // follow-up deploy (or a new edit) would otherwise compare against the now- // superseded base and falsely warn. parent_version is in @@ -410,8 +410,8 @@ onDeploy?.({ version: deployed.version, head: version, - headBy: appHistory[0]?.created_by, - headAt: appHistory[0]?.created_at + headBy: head?.created_by, + headAt: head?.created_at }) closeSaveDrawer() diff --git a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte index baaaa8ee65..43c042549f 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte @@ -548,14 +548,14 @@ custom_path: customPath, labels: $state.snapshot(labels) } - const appHistory = await AppService.getAppHistoryByPath({ + const head = await AppService.getAppLatestVersion({ workspace: opWorkspace!, path: npath }) // The deploy's own answer is the next draft's base; `version` is what is deployed // now, and the two differ when another deploy landed beside this one. The route // owns this prop and re-pushes `parentVersion ?? head` as soon as `onDeploy` returns. - version = appHistory[0]?.version + version = head?.version closeSaveDrawer() sendUserToast('App deployed successfully') @@ -574,8 +574,8 @@ path: npath, version: deployed.version, head: version, - headBy: appHistory[0]?.created_by, - headAt: appHistory[0]?.created_at + headBy: head?.created_by, + headAt: head?.created_at }) }