mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 00:01:55 +00:00
feat: add 'Reset to deployed' action on draft-loaded toast
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { clearJsonSchemaResourceCache } from './schema/jsonSchemaResource.svelte'
|
||||
import ResourceForm from './ResourceForm.svelte'
|
||||
import { invalidateWorkspacePaths } from './PathNameAutocomplete.svelte'
|
||||
@@ -254,7 +255,29 @@
|
||||
getUserExt(ws)
|
||||
]).then(([r, user]) => {
|
||||
if (r.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: ws,
|
||||
itemKind: 'resource',
|
||||
path: initialPath ?? '',
|
||||
onResetToDeployed: async () => {
|
||||
const fresh = await ResourceService.getResource({
|
||||
workspace: ws,
|
||||
path: initialPath ?? ''
|
||||
})
|
||||
const deployed: ResourceState = {
|
||||
path: fresh.path,
|
||||
description: fresh.description ?? '',
|
||||
args: (fresh.value ?? {}) as any,
|
||||
labels: fresh.labels ?? undefined,
|
||||
wsSpecific: fresh.ws_specific ?? false
|
||||
}
|
||||
fetchedResources[ws] = fresh
|
||||
fetchedRev[ws] = fresh.edited_at
|
||||
initialStates[ws] = structuredClone(deployed)
|
||||
const handle = states[ws]
|
||||
if (handle) handle.setDraftAndMeta(undefined, { remoteRev: fresh.edited_at })
|
||||
}
|
||||
})
|
||||
}
|
||||
fetchedResources[ws] = r
|
||||
fetchedRev[ws] = r.edited_at
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { canWrite } from '$lib/utils'
|
||||
import { Save } from 'lucide-svelte'
|
||||
import VariableForm from './VariableForm.svelte'
|
||||
@@ -168,7 +169,32 @@
|
||||
getUserExt(ws)
|
||||
]).then(([v, user]) => {
|
||||
if (v.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: ws,
|
||||
itemKind: 'variable',
|
||||
path: p,
|
||||
onResetToDeployed: async () => {
|
||||
const fresh = await VariableService.getVariable({
|
||||
workspace: ws,
|
||||
path: p,
|
||||
decryptSecret: false
|
||||
})
|
||||
const deployed: VariableState = {
|
||||
path: fresh.path,
|
||||
variable: {
|
||||
value: fresh.value ?? '',
|
||||
is_secret: fresh.is_secret,
|
||||
description: fresh.description ?? ''
|
||||
},
|
||||
labels: fresh.labels ?? undefined,
|
||||
wsSpecific: fresh.ws_specific ?? false
|
||||
}
|
||||
fetchedRev[ws] = fresh.edited_at
|
||||
initialStates[ws] = structuredClone(deployed)
|
||||
const handle = states[ws]
|
||||
if (handle) handle.setDraftAndMeta(undefined, { remoteRev: fresh.edited_at })
|
||||
}
|
||||
})
|
||||
}
|
||||
fetchedRev[ws] = v.edited_at
|
||||
const s: VariableState = {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -196,7 +197,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_azure',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadTriggerConfig(s)
|
||||
} catch (error) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
} from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -217,7 +218,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_email',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadTriggerConfig(s)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -200,7 +201,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_gcp',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadTriggerConfig(s)
|
||||
} catch (error) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
generateRandomString,
|
||||
sendUserToast
|
||||
} from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2, Pipette, Plus } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -350,7 +351,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_http',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadTriggerConfig(s)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { KafkaTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2, RotateCcw } from 'lucide-svelte'
|
||||
@@ -256,7 +257,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_kafka',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadTriggerConfig(s)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -256,7 +257,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_mqtt',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadTriggerConfig(s)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { NatsTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -238,7 +239,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_nats',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadTriggerConfig(s)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
} from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptyString, emptyStringTrimmed, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -382,7 +383,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_postgres',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const publication_data = await PostgresTriggerService.getPostgresPublication({
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
} from '$lib/gen'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptyString, formatCron, sendUserToast, cronV1toV2 } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { base } from '$lib/base'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { List, Loader2, Save, AlertTriangle } from 'lucide-svelte'
|
||||
@@ -292,7 +293,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (resp.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_schedule',
|
||||
path: schedule_path,
|
||||
onResetToDeployed: async () => {
|
||||
await openNew(nis_flow, initial_script_path, defaultValues, schedule_path)
|
||||
}
|
||||
})
|
||||
}
|
||||
s = resp
|
||||
initNewPath = true
|
||||
@@ -466,7 +474,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_schedule',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadSchedule()
|
||||
}
|
||||
})
|
||||
}
|
||||
await loadScheduleCfg(s)
|
||||
} catch (err) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -233,7 +234,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_sqs',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadTriggerConfig(s)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
} from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptySchema, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2, X, Plus } from 'lucide-svelte'
|
||||
@@ -313,7 +314,14 @@
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_websocket',
|
||||
path: initialPath,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
}
|
||||
})
|
||||
}
|
||||
loadTriggerConfig(s)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* site (route-specific state).
|
||||
*/
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte'
|
||||
import type { UserDraftItemKind } from '$lib/gen'
|
||||
|
||||
export type RestoreFromLocalActions = {
|
||||
/** Drop the local autosave, apply the backend DB draft. Offered when `hasBackendDraft`. */
|
||||
@@ -29,3 +31,45 @@ export function notifyRestoredFromLocal(
|
||||
if (actions.length === 0) return
|
||||
sendUserToast('Restored from local storage', false, actions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shown when an editor mounts on a per-user draft fetched from the server
|
||||
* (the `get_draft=true` overlay returned `is_draft: true`). Offers a
|
||||
* "Reset to deployed" action that:
|
||||
* 1. POSTs `value: null` to delete the user's draft for this entity, and
|
||||
* 2. invokes `onResetToDeployed` so the editor can re-fetch without the
|
||||
* overlay and apply the pure deployed version.
|
||||
*
|
||||
* Failures in either step surface a follow-up toast and leave the editor
|
||||
* state untouched.
|
||||
*/
|
||||
export function notifyDraftLoaded(opts: {
|
||||
workspace: string
|
||||
itemKind: UserDraftItemKind
|
||||
path: string
|
||||
onResetToDeployed: () => void | Promise<void>
|
||||
}): void {
|
||||
sendUserToast('Loaded your saved draft', false, [
|
||||
{
|
||||
label: 'Reset to deployed',
|
||||
callback: async () => {
|
||||
try {
|
||||
await UserDraftDbSyncer.save({
|
||||
workspace: opts.workspace,
|
||||
itemKind: opts.itemKind,
|
||||
path: opts.path,
|
||||
value: null
|
||||
})
|
||||
} catch (e: any) {
|
||||
sendUserToast(`Could not delete draft: ${e?.body ?? e}`, true)
|
||||
return
|
||||
}
|
||||
try {
|
||||
await opts.onResetToDeployed()
|
||||
} catch (e: any) {
|
||||
sendUserToast(`Could not reset to deployed: ${e?.body ?? e}`, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { untrack } from 'svelte'
|
||||
import { page } from '$app/state'
|
||||
import { UserDraft, checkStaleness, type UserDraftMeta } from '$lib/userDraft.svelte'
|
||||
import { notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
import { notifyDraftLoaded, notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
|
||||
let app = $state(undefined as (AppWithLastVersion & { value: any }) | undefined)
|
||||
let savedApp:
|
||||
@@ -119,7 +119,16 @@
|
||||
})
|
||||
if (tok !== loadAppToken) return
|
||||
if (backendApp.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'app',
|
||||
path: page.params.path ?? '',
|
||||
onResetToDeployed: async () => {
|
||||
UserDraft.remove('app', path)
|
||||
await loadApp()
|
||||
redraw++
|
||||
}
|
||||
})
|
||||
}
|
||||
const backendApp_ = structuredClone(stateSnapshot(backendApp))
|
||||
savedApp = {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
localDraftDiffers,
|
||||
type UserDraftMeta
|
||||
} from '$lib/userDraft.svelte'
|
||||
import { notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
import { notifyDraftLoaded, notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
import LocalDraftStaleModal from '$lib/components/common/confirmationModal/LocalDraftStaleModal.svelte'
|
||||
import OtherUsersDraftsModal from '$lib/components/common/confirmationModal/OtherUsersDraftsModal.svelte'
|
||||
|
||||
@@ -199,7 +199,15 @@
|
||||
})) as any
|
||||
if (tok !== loadAppToken) return
|
||||
if (backendApp.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'raw_app',
|
||||
path: page.params.path ?? '',
|
||||
onResetToDeployed: async () => {
|
||||
draftHandle.setDraftAndMeta(undefined, {})
|
||||
await loadApp()
|
||||
}
|
||||
})
|
||||
}
|
||||
// When the backend falls back to `fetch_draft_only` (no deployed
|
||||
// row at this path, only a per-user draft), the response's inner
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
type UserDraftMeta,
|
||||
type UserDraftHandle
|
||||
} from '$lib/userDraft.svelte'
|
||||
import { notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
import { notifyDraftLoaded, notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
|
||||
let version: undefined | number = $state(undefined)
|
||||
|
||||
@@ -218,7 +218,15 @@
|
||||
})
|
||||
if (tok !== loadFlowToken) return
|
||||
if (backendFlow.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'flow',
|
||||
path: page.params.path ?? '',
|
||||
onResetToDeployed: async () => {
|
||||
flowHandle.setDraftAndMeta(undefined, {})
|
||||
await loadFlow()
|
||||
}
|
||||
})
|
||||
}
|
||||
savedFlow = structuredClone($state.snapshot(backendFlow)) as Flow
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
type UserDraftMeta,
|
||||
type UserDraftHandle
|
||||
} from '$lib/userDraft.svelte'
|
||||
import { notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
import { notifyDraftLoaded, notifyRestoredFromLocal } from '$lib/userDraftToast'
|
||||
|
||||
type EditableScript = NewScript & { draft_triggers?: Trigger[] }
|
||||
|
||||
@@ -183,7 +183,19 @@
|
||||
})
|
||||
if (tok !== loadScriptToken) return
|
||||
if (backendScript.is_draft) {
|
||||
sendUserToast('Loaded your saved draft')
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'script',
|
||||
path: page.params.path ?? '',
|
||||
onResetToDeployed: async () => {
|
||||
// Drop the in-memory draft so loadScript's no-localDraft
|
||||
// branch fires; the next fetch with `getDraft: true` will
|
||||
// return pure deployed because we just deleted the draft
|
||||
// row server-side.
|
||||
scriptHandle.setDraftAndMeta(undefined, {})
|
||||
await loadScript()
|
||||
}
|
||||
})
|
||||
}
|
||||
savedScript = structuredClone($state.snapshot(backendScript))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user