mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-14 00:02:55 +00:00
fix: check pinned versions and app kind before keeping an item page on switch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
147dd09330
commit
0caf054295
@@ -247,8 +247,7 @@
|
||||
{
|
||||
displayName: editInForkLabel($workspaceStore, $userWorkspaces),
|
||||
icon: Pen,
|
||||
// No `href`: the handler resolves the destination asynchronously, and a melt
|
||||
// menu item's anchor navigates before a delegated onclick can preventDefault it.
|
||||
// No `href`: the handler resolves the destination asynchronously.
|
||||
action: (e) => onEditInForkClick(e, app.raw_app ? 'raw_app' : 'app', path),
|
||||
hide:
|
||||
$userStore?.operator ||
|
||||
|
||||
@@ -272,8 +272,7 @@
|
||||
{
|
||||
displayName: editInForkLabel($workspaceStore, $userWorkspaces),
|
||||
icon: Pen,
|
||||
// No `href`: the handler resolves the destination asynchronously, and a melt
|
||||
// menu item's anchor navigates before a delegated onclick can preventDefault it.
|
||||
// No `href`: the handler resolves the destination asynchronously.
|
||||
action: (e) => onEditInForkClick(e, 'flow', path),
|
||||
hide:
|
||||
$userStore?.operator ||
|
||||
|
||||
@@ -358,8 +358,7 @@
|
||||
{
|
||||
displayName: editInForkLabel($workspaceStore, $userWorkspaces),
|
||||
icon: Pen,
|
||||
// No `href`: the handler resolves the destination asynchronously, and a melt
|
||||
// menu item's anchor navigates before a delegated onclick can preventDefault it.
|
||||
// No `href`: the handler resolves the destination asynchronously.
|
||||
action: (e) => onEditInForkClick(e, 'script', script.path),
|
||||
hide:
|
||||
$userStore?.operator ||
|
||||
|
||||
@@ -14,35 +14,61 @@ import { workspaceStore } from '$lib/stores'
|
||||
import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte'
|
||||
import { workspaceAIClients } from '$lib/components/copilot/lib'
|
||||
|
||||
type ItemExists = (workspace: string, param: string) => Promise<boolean>
|
||||
// `search` is the page's query string: a `hash` or `version` in it pins one version of the
|
||||
// item, which must exist in the target workspace, not just its path.
|
||||
type ItemExists = (workspace: string, param: string, search: URLSearchParams) => Promise<boolean>
|
||||
|
||||
const appExists: ItemExists = (workspace, path) => AppService.existsApp({ workspace, path })
|
||||
const flowExists: ItemExists = (workspace, path) =>
|
||||
FlowService.existsFlowByPath({ workspace, path })
|
||||
const scriptExists: ItemExists = (workspace, path) =>
|
||||
ScriptService.existsScriptByPath({ workspace, path })
|
||||
// A failed lookup (404 included) counts as missing.
|
||||
async function succeeds(lookup: () => Promise<unknown>): Promise<boolean> {
|
||||
try {
|
||||
await lookup()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const scriptHashExists = (workspace: string, hash: string) =>
|
||||
succeeds(() => ScriptService.getScriptByHash({ workspace, hash }))
|
||||
|
||||
// The two app editors can't open each other's kind, so the target's app must match it.
|
||||
const appExists =
|
||||
(raw: boolean): ItemExists =>
|
||||
async (workspace, path) => {
|
||||
try {
|
||||
return (await AppService.getAppLiteByPath({ workspace, path })).raw_app === raw
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const flowExists: ItemExists = async (workspace, path, search) => {
|
||||
const version = Number(search.get('version') ?? NaN)
|
||||
if (!Number.isFinite(version)) return FlowService.existsFlowByPath({ workspace, path })
|
||||
try {
|
||||
return (await FlowService.getFlowVersion({ workspace, version })).path === path
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const scriptExists: ItemExists = (workspace, path, search) => {
|
||||
const hash = search.get('hash')
|
||||
return hash
|
||||
? scriptHashExists(workspace, hash)
|
||||
: ScriptService.existsScriptByPath({ workspace, path })
|
||||
}
|
||||
|
||||
// The script page's param is a hash, a path, or a hub path.
|
||||
const scriptHashOrPathExists: ItemExists = async (workspace, hashOrPath) => {
|
||||
if (hashOrPath.startsWith('hub/')) return true
|
||||
if (await scriptExists(workspace, hashOrPath)) return true
|
||||
try {
|
||||
await ScriptService.getScriptByHash({ workspace, hash: hashOrPath })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
if (await ScriptService.existsScriptByPath({ workspace, path: hashOrPath })) return true
|
||||
return scriptHashExists(workspace, hashOrPath)
|
||||
}
|
||||
|
||||
// A one-row lookup, where getJob would download the job's args and result.
|
||||
const jobExists: ItemExists = async (workspace, id) => {
|
||||
try {
|
||||
await JobService.getRootJobId({ workspace, id })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
const jobExists: ItemExists = (workspace, id) =>
|
||||
succeeds(() => JobService.getRootJobId({ workspace, id }))
|
||||
|
||||
type ItemPage = {
|
||||
param: string
|
||||
@@ -62,14 +88,18 @@ const ITEM_PAGES: Record<string, ItemPage> = {
|
||||
'/(root)/(logged)/scripts/get/[...hash]': { param: 'hash', exists: scriptHashOrPathExists },
|
||||
'/(root)/(logged)/flows/edit/[...path]': { param: 'path', exists: flowExists, draftKind: 'flow' },
|
||||
'/(root)/(logged)/flows/get/[...path]': { param: 'path', exists: flowExists },
|
||||
'/(root)/(logged)/apps/edit/[...path]': { param: 'path', exists: appExists, draftKind: 'app' },
|
||||
'/(root)/(logged)/apps/get/[...path]': { param: 'path', exists: appExists },
|
||||
'/(root)/(logged)/apps/edit/[...path]': {
|
||||
param: 'path',
|
||||
exists: appExists(false),
|
||||
draftKind: 'app'
|
||||
},
|
||||
'/(root)/(logged)/apps/get/[...path]': { param: 'path', exists: appExists(false) },
|
||||
'/(root)/(logged)/apps_raw/edit/[...path]': {
|
||||
param: 'path',
|
||||
exists: appExists,
|
||||
exists: appExists(true),
|
||||
draftKind: 'raw_app'
|
||||
},
|
||||
'/(root)/(logged)/apps_raw/get/[...path]': { param: 'path', exists: appExists },
|
||||
'/(root)/(logged)/apps_raw/get/[...path]': { param: 'path', exists: appExists(true) },
|
||||
'/(root)/(logged)/run/[...run]': { param: 'run', exists: jobExists }
|
||||
}
|
||||
|
||||
@@ -83,7 +113,7 @@ async function itemPageMissingIn(workspace: string): Promise<boolean> {
|
||||
const current = currentItemPage()
|
||||
if (!current) return false
|
||||
try {
|
||||
return !(await current.itemPage.exists(workspace, current.param))
|
||||
return !(await current.itemPage.exists(workspace, current.param, page.url.searchParams))
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
@@ -121,8 +151,10 @@ export async function switchWorkspaceAndPage(
|
||||
href?: string
|
||||
}
|
||||
): Promise<void> {
|
||||
const missing = !opts?.landOnHome && (await itemPageMissingIn(id))
|
||||
// Read after the lookup: the user can edit while it is in flight.
|
||||
const unsavedEdits = editorHasUnsavedEdits()
|
||||
if (opts?.landOnHome || unsavedEdits || (await itemPageMissingIn(id))) {
|
||||
if (opts?.landOnHome || unsavedEdits || missing) {
|
||||
// Leave before switching: an item page still mounted when the store changes
|
||||
// refetches its item in `id` and toasts the 404.
|
||||
// The param carries the switch through the editor's unsaved-changes prompt: that
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
} from '$lib/gen'
|
||||
import { capitalize, classNames, getModifierKey, sendUserToast } from '$lib/utils'
|
||||
import { useLocalStorageValue } from '$lib/svelte5Utils.svelte'
|
||||
import { switchWorkspace } from '$lib/storeUtils'
|
||||
import { isSessionPreviewFrame } from '$lib/components/sessions/sessionMode.svelte'
|
||||
import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte'
|
||||
import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte'
|
||||
@@ -299,8 +300,8 @@
|
||||
|
||||
function onQueryChange() {
|
||||
let queryWorkspace = page.url.searchParams.get('workspace')
|
||||
if (queryWorkspace) {
|
||||
$workspaceStore = queryWorkspace
|
||||
if (queryWorkspace && queryWorkspace !== $workspaceStore) {
|
||||
switchWorkspace(queryWorkspace)
|
||||
}
|
||||
|
||||
// When this window is an iframe (e.g. the sessions preview), keep the menu
|
||||
|
||||
Reference in New Issue
Block a user