mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
fix(frontend): follow the operating workspace in step input forms (#10834)
* fix(frontend): follow the operating workspace in step input forms Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(frontend): rethrow auth errors and wire remaining variable pickers Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(frontend): use runed watch for picker workspace reloads Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8a6dc27236
commit
6b73145e72
@@ -15,6 +15,7 @@
|
||||
import PropertyEditor from './schema/PropertyEditor.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { watch } from 'runed'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import Label from './Label.svelte'
|
||||
@@ -173,6 +174,11 @@
|
||||
let itemPicker: ItemPicker | undefined = $state(undefined)
|
||||
let variableEditor: VariableEditor | undefined = $state(undefined)
|
||||
|
||||
watch(
|
||||
() => ws,
|
||||
() => itemPicker?.reloadItems()
|
||||
)
|
||||
|
||||
let keys: string[] = $state(
|
||||
(Array.isArray(schema?.order)
|
||||
? [...schema.order]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import { VariableService, WorkspaceService, type InputTransform } from '$lib/gen'
|
||||
import { CancelError, VariableService, WorkspaceService, type InputTransform } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { allTrue, type DynamicInput as DynamicInputTypes } from '$lib/utils'
|
||||
import { untrack } from 'svelte'
|
||||
import { resource, watch } from 'runed'
|
||||
import { Button } from './common'
|
||||
import StepInputsGen from './copilot/StepInputsGen.svelte'
|
||||
import type { PickableProperties } from './flows/previousResults'
|
||||
@@ -81,21 +82,37 @@
|
||||
let itemPicker: ItemPicker | undefined = $state(undefined)
|
||||
let variableEditor: VariableEditor | undefined = $state(undefined)
|
||||
|
||||
let s3StorageConfigured = $state(true)
|
||||
|
||||
async function checkS3Storage() {
|
||||
try {
|
||||
if (ws) {
|
||||
const settings = await WorkspaceService.getPublicSettings({ workspace: ws })
|
||||
s3StorageConfigured = settings.large_file_storage?.s3_resource_path !== undefined
|
||||
const settings = resource(
|
||||
() => ws,
|
||||
async (ws, _previousWs, { onCleanup }) => {
|
||||
if (!ws) return undefined
|
||||
const req = WorkspaceService.getPublicSettings({ workspace: ws })
|
||||
// `resource` keeps whatever lands last: cancel a superseded request so a slow
|
||||
// reply for a workspace we have left cannot overwrite the current one.
|
||||
onCleanup(() => req.cancel())
|
||||
try {
|
||||
return { ws, settings: await req }
|
||||
} catch (err) {
|
||||
if (!(err instanceof CancelError)) {
|
||||
console.error('Failed to fetch workspace settings:', err)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch workspace settings:', error)
|
||||
s3StorageConfigured = true
|
||||
}
|
||||
}
|
||||
)
|
||||
// Assume configured until this workspace's own answer lands: the warning must not
|
||||
// linger from the previous workspace, nor appear merely because the fetch failed.
|
||||
let s3StorageConfigured = $derived.by(() => {
|
||||
const loaded = settings.current
|
||||
return loaded && loaded.ws === ws
|
||||
? loaded.settings.large_file_storage?.s3_resource_path !== undefined
|
||||
: true
|
||||
})
|
||||
|
||||
checkS3Storage()
|
||||
watch(
|
||||
() => ws,
|
||||
() => itemPicker?.reloadItems()
|
||||
)
|
||||
|
||||
let keys: string[] = $state([])
|
||||
$effect(() => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { RotateCw } from 'lucide-svelte'
|
||||
import { Button, Drawer, Skeleton } from './common'
|
||||
import { Alert, Button, Drawer, Skeleton } from './common'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import NoItemFound from './home/NoItemFound.svelte'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
import SearchItems from './SearchItems.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
|
||||
type Item = Record<string, any>
|
||||
|
||||
@@ -39,22 +40,62 @@
|
||||
}: Props = $props()
|
||||
|
||||
let loading = $state(false)
|
||||
let loadError: string | undefined = $state(undefined)
|
||||
let items: Item[] | undefined = $state([])
|
||||
let filteredItems: Item[] | undefined = $state([])
|
||||
let filter = $state('')
|
||||
|
||||
export function openDrawer() {
|
||||
loading = true
|
||||
loadItems()
|
||||
// Only the newest load may write `items`: a slower earlier request can resolve last.
|
||||
// Skeletons replace a list that is known-stale; the refresh button omits them so a
|
||||
// known-good list does not flicker.
|
||||
let loadSeq = 0
|
||||
function load(showSkeleton = false): Promise<void> {
|
||||
const seq = ++loadSeq
|
||||
if (showSkeleton) {
|
||||
loading = true
|
||||
}
|
||||
return loadItems()
|
||||
.then((v) => {
|
||||
items = v
|
||||
if (seq === loadSeq) {
|
||||
items = v
|
||||
loadError = undefined
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (seq !== loadSeq) return
|
||||
// Drop the list rather than keep offering entries the failed load may have
|
||||
// superseded. `loadError` then has to carry the reason, or an empty list reads
|
||||
// as an empty workspace. An empty body must not win over the message, or the
|
||||
// error state is skipped for a falsy `loadError` — hence `||`, not `??`.
|
||||
items = []
|
||||
loadError = err.body || err.message || String(err)
|
||||
// 401/403 are handled globally by onunhandledrejection (logout, privilege
|
||||
// toast). No caller awaits load(), so rethrowing still reaches it.
|
||||
if (err?.status === 401 || err?.status === 403) {
|
||||
throw err
|
||||
}
|
||||
sendUserToast(`Failed to load ${itemName.toLowerCase()}s: ${loadError}`, true)
|
||||
})
|
||||
.finally(() => {
|
||||
loading = false
|
||||
if (seq === loadSeq) {
|
||||
loading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function openDrawer() {
|
||||
load(true)
|
||||
drawer?.openDrawer?.()
|
||||
}
|
||||
|
||||
/** Re-runs `loadItems` against what it closes over now. No-op while closed —
|
||||
* opening reloads anyway. */
|
||||
export function reloadItems() {
|
||||
if (drawer?.isOpen()) {
|
||||
load(true)
|
||||
}
|
||||
}
|
||||
|
||||
let drawer: Drawer | undefined = $state()
|
||||
|
||||
let refreshing = $state(false)
|
||||
@@ -94,14 +135,9 @@
|
||||
<Button
|
||||
on:click={() => {
|
||||
refreshing = true
|
||||
loadItems()
|
||||
.then((v) => {
|
||||
items = v
|
||||
})
|
||||
.finally(() => {
|
||||
loading = false
|
||||
refreshing = false
|
||||
})
|
||||
load().finally(() => {
|
||||
refreshing = false
|
||||
})
|
||||
}}
|
||||
iconOnly
|
||||
startIcon={{ icon: RotateCw, classes: loading || refreshing ? 'animate-spin' : '' }}
|
||||
@@ -111,6 +147,10 @@
|
||||
{#each new Array(3) as _}
|
||||
<Skeleton layout={[[5], 0.2]} />
|
||||
{/each}
|
||||
{:else if loadError}
|
||||
<Alert type="error" size="xs" title="Failed to load {itemName.toLowerCase()}s">
|
||||
{loadError}
|
||||
</Alert>
|
||||
{:else if !items?.length}
|
||||
<div class="text-center text-sm text-primary mt-2">
|
||||
{@html noItemMessage}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { Plus } from 'lucide-svelte'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { watch } from 'runed'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import {
|
||||
dragHandleZone,
|
||||
@@ -155,6 +156,11 @@
|
||||
let itemPicker: ItemPicker | undefined = $state(undefined)
|
||||
let variableEditor: VariableEditor | undefined = $state(undefined)
|
||||
|
||||
watch(
|
||||
() => ws,
|
||||
() => itemPicker?.reloadItems()
|
||||
)
|
||||
|
||||
let resourceTypes: string[] | undefined = $state(undefined)
|
||||
|
||||
async function loadResourceTypes() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Alert } from '$lib/components/common'
|
||||
import { getContext, setContext } from 'svelte'
|
||||
import { watch } from 'runed'
|
||||
import type { PropPickerWrapperContext } from '../propPicker/PropPickerWrapper.svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
@@ -229,6 +230,11 @@
|
||||
let variablePicker: ItemPicker | undefined = $state(undefined)
|
||||
let pickForKey: string | undefined = $state(undefined)
|
||||
|
||||
watch(
|
||||
() => opWs,
|
||||
() => variablePicker?.reloadItems()
|
||||
)
|
||||
|
||||
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
|
||||
inputMatches: writable(undefined),
|
||||
connectProp: () => {},
|
||||
|
||||
Reference in New Issue
Block a user