mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix(drafts): guard empty storage path out of the draft DB seam
This commit is contained in:
@@ -568,7 +568,7 @@ describe('global AI tools', () => {
|
||||
it('lists and edits the live script editor draft through its effective path', async () => {
|
||||
seedDbDraft(
|
||||
'script',
|
||||
'',
|
||||
'u/admin/draft_amazed',
|
||||
{
|
||||
path: 'u/admin/amazed_script',
|
||||
summary: 'Live script',
|
||||
@@ -584,7 +584,7 @@ describe('global AI tools', () => {
|
||||
UserDraft.setLiveEditorDraft({
|
||||
workspace: WORKSPACE,
|
||||
itemKind: 'script',
|
||||
storagePath: '',
|
||||
storagePath: 'u/admin/draft_amazed',
|
||||
effectivePath: 'u/admin/amazed_script'
|
||||
})
|
||||
|
||||
@@ -604,7 +604,7 @@ describe('global AI tools', () => {
|
||||
new_string: 'return a * b'
|
||||
})
|
||||
|
||||
expect(dbDraftValue('script', '')).toMatchObject({
|
||||
expect(dbDraftValue('script', 'u/admin/draft_amazed')).toMatchObject({
|
||||
path: 'u/admin/amazed_script',
|
||||
content: 'export async function main(a: number, b: number) {\n\treturn a * b\n}'
|
||||
})
|
||||
@@ -616,7 +616,7 @@ describe('global AI tools', () => {
|
||||
it('lists and writes the live flow editor draft through its effective path', async () => {
|
||||
seedDbDraft(
|
||||
'flow',
|
||||
'',
|
||||
'u/admin/draft_live_flow',
|
||||
{
|
||||
path: '',
|
||||
summary: 'Live flow',
|
||||
@@ -632,7 +632,7 @@ describe('global AI tools', () => {
|
||||
UserDraft.setLiveEditorDraft({
|
||||
workspace: WORKSPACE,
|
||||
itemKind: 'flow',
|
||||
storagePath: '',
|
||||
storagePath: 'u/admin/draft_live_flow',
|
||||
effectivePath: 'u/admin/live_flow'
|
||||
})
|
||||
|
||||
@@ -652,7 +652,7 @@ describe('global AI tools', () => {
|
||||
modules: JSON.stringify([{ id: 'step', value: { type: 'identity' } }])
|
||||
})
|
||||
|
||||
expect(dbDraftValue('flow', '')).toMatchObject({
|
||||
expect(dbDraftValue('flow', 'u/admin/draft_live_flow')).toMatchObject({
|
||||
path: 'u/admin/live_flow',
|
||||
summary: 'Updated live flow',
|
||||
value: { modules: [{ id: 'step', value: { type: 'identity' } }] }
|
||||
@@ -663,7 +663,7 @@ describe('global AI tools', () => {
|
||||
it('writes the live raw app editor draft through its effective path', async () => {
|
||||
seedDbDraft(
|
||||
'raw_app',
|
||||
'',
|
||||
'u/admin/draft_live_app',
|
||||
{
|
||||
summary: 'Live app',
|
||||
files: { '/src/App.tsx': 'export default function App() { return null }' },
|
||||
@@ -675,7 +675,7 @@ describe('global AI tools', () => {
|
||||
UserDraft.setLiveEditorDraft({
|
||||
workspace: WORKSPACE,
|
||||
itemKind: 'raw_app',
|
||||
storagePath: '',
|
||||
storagePath: 'u/admin/draft_live_app',
|
||||
effectivePath: 'u/admin/live_app'
|
||||
})
|
||||
|
||||
@@ -685,7 +685,7 @@ describe('global AI tools', () => {
|
||||
content: 'export default function New() { return null }'
|
||||
})
|
||||
|
||||
expect(dbDraftValue('raw_app', '')).toMatchObject({
|
||||
expect(dbDraftValue('raw_app', 'u/admin/draft_live_app')).toMatchObject({
|
||||
files: {
|
||||
'/src/App.tsx': 'export default function App() { return null }',
|
||||
'/src/New.tsx': 'export default function New() { return null }'
|
||||
@@ -1503,7 +1503,7 @@ describe('global AI tools', () => {
|
||||
it('test_run_flow uses the live flow editor test hook when the active editor matches the path', async () => {
|
||||
seedDbDraft(
|
||||
'flow',
|
||||
'',
|
||||
'u/admin/draft_live_flow_hook',
|
||||
{
|
||||
path: 'u/admin/live_flow',
|
||||
summary: 'Live flow',
|
||||
@@ -1519,7 +1519,7 @@ describe('global AI tools', () => {
|
||||
UserDraft.setLiveEditorDraft({
|
||||
workspace: WORKSPACE,
|
||||
itemKind: 'flow',
|
||||
storagePath: '',
|
||||
storagePath: 'u/admin/draft_live_flow_hook',
|
||||
effectivePath: 'u/admin/live_flow'
|
||||
})
|
||||
const testActiveFlow = vi.fn(async () => 'job-live-flow')
|
||||
@@ -1545,7 +1545,7 @@ describe('global AI tools', () => {
|
||||
it('test_run_flow falls back to preview when the live flow editor test hook returns undefined', async () => {
|
||||
seedDbDraft(
|
||||
'flow',
|
||||
'',
|
||||
'u/admin/draft_live_flow_fallback',
|
||||
{
|
||||
path: 'u/admin/live_flow_fallback',
|
||||
summary: 'Live flow fallback',
|
||||
@@ -1561,7 +1561,7 @@ describe('global AI tools', () => {
|
||||
UserDraft.setLiveEditorDraft({
|
||||
workspace: WORKSPACE,
|
||||
itemKind: 'flow',
|
||||
storagePath: '',
|
||||
storagePath: 'u/admin/draft_live_flow_fallback',
|
||||
effectivePath: 'u/admin/live_flow_fallback'
|
||||
})
|
||||
const testActiveFlow = vi.fn(async () => undefined)
|
||||
@@ -1952,7 +1952,7 @@ describe('prepareGlobalUserMessage', () => {
|
||||
UserDraft.setLiveEditorDraft({
|
||||
workspace: WORKSPACE,
|
||||
itemKind: 'script',
|
||||
storagePath: '',
|
||||
storagePath: 'f/scripts/draft_live_greeting',
|
||||
effectivePath: 'f/scripts/live_greeting'
|
||||
})
|
||||
|
||||
|
||||
@@ -284,7 +284,14 @@ function resolveDraftStoragePath(
|
||||
path: string
|
||||
): string {
|
||||
const liveDraft = UserDraft.getLiveEditorDraft(itemKind, { workspace })
|
||||
if (!liveDraft) return path
|
||||
// Fall back to the caller's path when there's no live editor, or when
|
||||
// the live editor hasn't committed a storage path yet. An empty
|
||||
// storage path must never propagate to the DB seam: it can't be a
|
||||
// `draft` row key and the draft routes (`/save_draft/{kind}/{*path}`)
|
||||
// 404 on an empty wildcard tail. In practice new drafts always live at
|
||||
// a real `u/{user}/draft_{uuid}` path (see `/scripts/add` et al.), so
|
||||
// this is defensive — but it keeps `''` out of `DraftService` outright.
|
||||
if (!liveDraft || !liveDraft.storagePath) return path
|
||||
if (path === liveDraft.storagePath || path === liveDraft.effectivePath)
|
||||
return liveDraft.storagePath
|
||||
return path
|
||||
@@ -465,10 +472,10 @@ function liveEditorDraftType(kind: (typeof LIVE_EDITOR_DRAFT_KINDS)[number]): Wo
|
||||
* `isLiveDraft`.
|
||||
*
|
||||
* This is in-memory state that the DB draft list can't represent: a brand-new
|
||||
* unsaved draft has no DB row (it lives at an empty storage path), and an
|
||||
* in-progress rename's effective path differs from where the draft is stored.
|
||||
* Persisted draft listing comes from the backend (`includeDraftOnly` +
|
||||
* `isDraft`); this only fills that in-memory gap.
|
||||
* draft that hasn't been saved to the server yet has no DB row to list, and an
|
||||
* in-progress rename's effective path differs from the `u/{user}/draft_{uuid}`
|
||||
* path where the draft is stored. Persisted draft listing comes from the
|
||||
* backend (`includeDraftOnly` + `isDraft`); this only fills that in-memory gap.
|
||||
*
|
||||
* Existence and path are taken straight from the live registry
|
||||
* (`getLiveEditorDraft`) — not gated on the in-tab value cell, which is only
|
||||
|
||||
Reference in New Issue
Block a user