mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 16:15:51 +00:00
fix: preserve db draft rename deploys
This commit is contained in:
@@ -987,6 +987,50 @@ describe('global AI tools', () => {
|
||||
expect(ScriptService.getScriptByPath).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('deploys saved DB script rename drafts to the draft target path', async () => {
|
||||
vi.mocked(ScriptService.existsScriptByPath).mockResolvedValueOnce(true)
|
||||
vi.mocked(ScriptService.getScriptByPathWithDraft).mockResolvedValueOnce({
|
||||
path: 'f/scripts/old-name',
|
||||
hash: 'old-script-hash',
|
||||
summary: 'deployed summary',
|
||||
description: 'deployed description',
|
||||
content: 'deployed content',
|
||||
schema: {},
|
||||
is_template: false,
|
||||
language: 'bun',
|
||||
kind: 'script',
|
||||
draft: {
|
||||
path: 'f/scripts/new-name',
|
||||
summary: 'renamed draft summary',
|
||||
description: 'renamed draft description',
|
||||
content: 'renamed draft content',
|
||||
language: 'bun',
|
||||
kind: 'script'
|
||||
}
|
||||
} as any)
|
||||
|
||||
const raw = await callGlobalTool('deploy_workspace_item', {
|
||||
type: 'script',
|
||||
path: 'f/scripts/old-name'
|
||||
})
|
||||
|
||||
expect(ScriptService.createScript).toHaveBeenCalledWith({
|
||||
workspace: WORKSPACE,
|
||||
requestBody: expect.objectContaining({
|
||||
path: 'f/scripts/new-name',
|
||||
parent_hash: 'old-script-hash',
|
||||
summary: 'renamed draft summary',
|
||||
description: 'renamed draft description',
|
||||
content: 'renamed draft content'
|
||||
})
|
||||
})
|
||||
expect(JSON.parse(raw)).toMatchObject({
|
||||
success: true,
|
||||
type: 'script',
|
||||
path: 'f/scripts/new-name'
|
||||
})
|
||||
})
|
||||
|
||||
it('deploys DB flow drafts with draft metadata when no local draft exists', async () => {
|
||||
vi.mocked(FlowService.existsFlowByPath).mockResolvedValueOnce(true)
|
||||
vi.mocked(FlowService.getFlowByPathWithDraft).mockResolvedValueOnce({
|
||||
@@ -1051,6 +1095,52 @@ describe('global AI tools', () => {
|
||||
expect(FlowService.getFlowByPath).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('deploys saved DB flow rename drafts through the original route path', async () => {
|
||||
vi.mocked(FlowService.existsFlowByPath).mockResolvedValueOnce(true)
|
||||
vi.mocked(FlowService.getFlowByPathWithDraft).mockResolvedValueOnce({
|
||||
path: 'f/flows/old-name',
|
||||
summary: 'deployed summary',
|
||||
description: 'deployed description',
|
||||
value: {
|
||||
modules: [{ id: 'deployed_step', value: { type: 'identity' } }]
|
||||
},
|
||||
schema: { type: 'object', properties: { deployed: { type: 'boolean' } } },
|
||||
draft: {
|
||||
path: 'f/flows/new-name',
|
||||
summary: 'renamed draft summary',
|
||||
description: 'renamed draft description',
|
||||
value: {
|
||||
modules: [{ id: 'draft_step', value: { type: 'identity' } }]
|
||||
},
|
||||
schema: { type: 'object', properties: { draft: { type: 'string' } } }
|
||||
}
|
||||
} as any)
|
||||
|
||||
const raw = await callGlobalTool('deploy_workspace_item', {
|
||||
type: 'flow',
|
||||
path: 'f/flows/old-name'
|
||||
})
|
||||
|
||||
expect(FlowService.updateFlow).toHaveBeenCalledWith({
|
||||
workspace: WORKSPACE,
|
||||
path: 'f/flows/old-name',
|
||||
requestBody: expect.objectContaining({
|
||||
path: 'f/flows/new-name',
|
||||
summary: 'renamed draft summary',
|
||||
description: 'renamed draft description',
|
||||
schema: { type: 'object', properties: { draft: { type: 'string' } } }
|
||||
})
|
||||
})
|
||||
expect(vi.mocked(FlowService.updateFlow).mock.calls[0]?.[0].requestBody.value.modules).toEqual([
|
||||
{ id: 'draft_step', value: { type: 'identity' } }
|
||||
])
|
||||
expect(JSON.parse(raw)).toMatchObject({
|
||||
success: true,
|
||||
type: 'flow',
|
||||
path: 'f/flows/new-name'
|
||||
})
|
||||
})
|
||||
|
||||
it('applies path_prefix to local drafts before enforcing the result limit', async () => {
|
||||
await callGlobalTool('write_script', {
|
||||
path: 'f/other/outside',
|
||||
@@ -1913,6 +2003,50 @@ describe('global AI tools', () => {
|
||||
expect(UserDraft.get('raw_app', 'f/apps/report', { workspace: WORKSPACE })).toBeUndefined()
|
||||
})
|
||||
|
||||
it('deploys saved DB app rename drafts through the original route path', async () => {
|
||||
appDrafts.set('f/apps/old-report', {
|
||||
path: 'f/apps/new-report',
|
||||
summary: 'Renamed report',
|
||||
value: {
|
||||
files: { '/index.tsx': 'console.log("renamed")' },
|
||||
runnables: {},
|
||||
data: { tables: ['renamed'] }
|
||||
},
|
||||
policy: { execution_mode: 'anonymous' }
|
||||
})
|
||||
|
||||
const raw = await callGlobalTool('deploy_workspace_item', {
|
||||
type: 'app',
|
||||
path: 'f/apps/old-report'
|
||||
})
|
||||
|
||||
expect(AppService.updateAppRaw).toHaveBeenCalledWith({
|
||||
workspace: WORKSPACE,
|
||||
path: 'f/apps/old-report',
|
||||
formData: {
|
||||
app: {
|
||||
path: 'f/apps/new-report',
|
||||
value: {
|
||||
files: { '/index.tsx': 'console.log("renamed")' },
|
||||
runnables: {},
|
||||
data: { tables: ['renamed'] }
|
||||
},
|
||||
summary: 'Renamed report',
|
||||
policy: expect.objectContaining({ execution_mode: 'anonymous' }),
|
||||
deployment_message: undefined
|
||||
},
|
||||
js: 'bundled js',
|
||||
css: 'bundled css'
|
||||
}
|
||||
})
|
||||
expect(AppService.createAppRaw).not.toHaveBeenCalled()
|
||||
expect(JSON.parse(raw)).toMatchObject({
|
||||
success: true,
|
||||
type: 'app',
|
||||
path: 'f/apps/new-report'
|
||||
})
|
||||
})
|
||||
|
||||
it('notifies the session preview (as raw_app) after deploying a raw app', async () => {
|
||||
const onDeployed = vi.fn()
|
||||
setDeployedInSessionHandler(onDeployed)
|
||||
|
||||
@@ -3072,9 +3072,11 @@ async function deployDraft(
|
||||
if (draft.value === undefined) {
|
||||
throw new Error(`Draft ${type} "${path}" has no value to deploy.`)
|
||||
}
|
||||
const deployPath =
|
||||
type === 'script' || type === 'flow' || type === 'app' ? (draft.path ?? path) : path
|
||||
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: `Deploying ${type} "${path}"...`
|
||||
content: `Deploying ${type} "${deployPath}"...`
|
||||
})
|
||||
|
||||
let actions: ToolDisplayAction[] | undefined
|
||||
@@ -3083,7 +3085,7 @@ async function deployDraft(
|
||||
case 'script': {
|
||||
await deployScriptDraft({
|
||||
workspace,
|
||||
path,
|
||||
path: deployPath,
|
||||
draft,
|
||||
existing: deployableDraft.existingScript,
|
||||
deploymentMessage,
|
||||
@@ -3095,6 +3097,7 @@ async function deployDraft(
|
||||
await deployFlowDraft({
|
||||
workspace,
|
||||
path,
|
||||
targetPath: deployPath,
|
||||
draft,
|
||||
existing: deployableDraft.existingFlow,
|
||||
deploymentMessage,
|
||||
@@ -3153,6 +3156,7 @@ async function deployDraft(
|
||||
await deployAppDraft({
|
||||
workspace,
|
||||
path,
|
||||
targetPath: deployPath,
|
||||
draft,
|
||||
appExists: deployableDraft.appExists,
|
||||
deploymentMessage,
|
||||
@@ -3175,19 +3179,19 @@ async function deployDraft(
|
||||
app: 'raw_app'
|
||||
}
|
||||
const kind = previewKindByType[type]
|
||||
if (kind) deployedInSessionHandler?.({ sessionId, kind, path })
|
||||
if (kind) deployedInSessionHandler?.({ sessionId, kind, path: deployPath })
|
||||
|
||||
toolCallbacks.setToolStatus(toolId, {
|
||||
content: `Deployed ${type} "${path}"`,
|
||||
content: `Deployed ${type} "${deployPath}"`,
|
||||
result: 'Deployed',
|
||||
actions
|
||||
})
|
||||
return JSON.stringify(
|
||||
{
|
||||
success: true,
|
||||
message: `Deployed draft ${type} "${path}" to the workspace. Draft removed from the draft system.`,
|
||||
message: `Deployed draft ${type} "${deployPath}" to the workspace. Draft removed from the draft system.`,
|
||||
type,
|
||||
path,
|
||||
path: deployPath,
|
||||
triggerKind
|
||||
},
|
||||
null,
|
||||
|
||||
@@ -188,7 +188,9 @@ export async function loadDbDraftForDeploy(
|
||||
if (!(await ScriptService.existsScriptByPath({ workspace, path }))) return undefined
|
||||
const script = await ScriptService.getScriptByPathWithDraft({ workspace, path })
|
||||
if (!script.draft && !script.draft_only) return undefined
|
||||
const draftScript = script.draft ? { ...script, ...script.draft, path: script.path } : script
|
||||
const draftScript = script.draft
|
||||
? { ...script, ...script.draft, path: script.draft.path ?? script.path }
|
||||
: script
|
||||
return {
|
||||
item: scriptToItem(draftScript, true, true),
|
||||
existingScript: script,
|
||||
@@ -199,7 +201,9 @@ export async function loadDbDraftForDeploy(
|
||||
if (!(await FlowService.existsFlowByPath({ workspace, path }))) return undefined
|
||||
const flow = await FlowService.getFlowByPathWithDraft({ workspace, path })
|
||||
if (!flow.draft && !flow.draft_only) return undefined
|
||||
const draftFlow = flow.draft ? { ...flow, ...flow.draft, path: flow.path } : flow
|
||||
const draftFlow = flow.draft
|
||||
? { ...flow, ...flow.draft, path: flow.draft.path ?? flow.path }
|
||||
: flow
|
||||
return {
|
||||
item: flowToItem(draftFlow, true, true),
|
||||
existingFlow: flow,
|
||||
@@ -210,11 +214,12 @@ export async function loadDbDraftForDeploy(
|
||||
if (!(await AppService.existsApp({ workspace, path }))) return undefined
|
||||
const app = await AppService.getAppByPathWithDraft({ workspace, path })
|
||||
if (!app.draft && !app.draft_only) return undefined
|
||||
const draftPath = (app.draft as { path?: string } | undefined)?.path ?? app.path
|
||||
const value = appSourceToDraftValue(app.draft ?? app, app)
|
||||
return {
|
||||
item: {
|
||||
type: 'app',
|
||||
path: app.path,
|
||||
path: draftPath,
|
||||
summary: value.summary,
|
||||
value,
|
||||
isDraft: true
|
||||
@@ -310,15 +315,24 @@ export async function deployScriptDraft(args: {
|
||||
export async function deployFlowDraft(args: {
|
||||
workspace: string
|
||||
path: string
|
||||
targetPath?: string
|
||||
draft: WorkspaceItem
|
||||
existing?: FlowDeployMetadata
|
||||
deploymentMessage?: string
|
||||
draftMetadata?: FlowDeployMetadata
|
||||
}): Promise<void> {
|
||||
const { workspace, path, draft, existing, deploymentMessage, draftMetadata } = args
|
||||
const {
|
||||
workspace,
|
||||
path,
|
||||
targetPath = path,
|
||||
draft,
|
||||
existing,
|
||||
deploymentMessage,
|
||||
draftMetadata
|
||||
} = args
|
||||
const flowDraft = draft.value as FlowDraftValue
|
||||
const requestBody = buildFlowDeployRequestBody(
|
||||
path,
|
||||
targetPath,
|
||||
draft.summary,
|
||||
flowDraft,
|
||||
existing,
|
||||
@@ -335,12 +349,13 @@ export async function deployFlowDraft(args: {
|
||||
export async function deployAppDraft(args: {
|
||||
workspace: string
|
||||
path: string
|
||||
targetPath?: string
|
||||
draft: WorkspaceItem
|
||||
appExists?: boolean
|
||||
deploymentMessage?: string
|
||||
onStatus?: DeployStatusHandler
|
||||
}): Promise<void> {
|
||||
const { workspace, path, draft, appExists, deploymentMessage, onStatus } = args
|
||||
const { workspace, path, targetPath = path, draft, appExists, deploymentMessage, onStatus } = args
|
||||
const appDraft = draft.value as AppDraftValue
|
||||
const appValue: AppDraftValue = {
|
||||
...appDraft,
|
||||
@@ -351,10 +366,10 @@ export async function deployAppDraft(args: {
|
||||
await recomputeAppPolicy(appValue)
|
||||
const policy = appValue.policy
|
||||
if (!policy) {
|
||||
throw new Error(`Draft app "${path}" has no policy to deploy.`)
|
||||
throw new Error(`Draft app "${targetPath}" has no policy to deploy.`)
|
||||
}
|
||||
|
||||
onStatus?.(`Bundling app "${path}"...`)
|
||||
onStatus?.(`Bundling app "${targetPath}"...`)
|
||||
const bundle = await bundleRawAppDraft({
|
||||
workspace,
|
||||
files: appValue.files,
|
||||
@@ -365,12 +380,12 @@ export async function deployAppDraft(args: {
|
||||
.filter(Boolean)
|
||||
const latest = lines[lines.length - 1]
|
||||
if (latest) {
|
||||
onStatus?.(`Bundling app "${path}"... ${latest}`)
|
||||
onStatus?.(`Bundling app "${targetPath}"... ${latest}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onStatus?.(`Deploying app "${path}"...`)
|
||||
onStatus?.(`Deploying app "${targetPath}"...`)
|
||||
const rawAppValue = {
|
||||
files: appValue.files,
|
||||
runnables: appValue.runnables,
|
||||
@@ -386,7 +401,7 @@ export async function deployAppDraft(args: {
|
||||
path,
|
||||
formData: {
|
||||
app: {
|
||||
path,
|
||||
path: targetPath,
|
||||
value: rawAppValue,
|
||||
summary,
|
||||
policy,
|
||||
@@ -401,7 +416,7 @@ export async function deployAppDraft(args: {
|
||||
workspace,
|
||||
formData: {
|
||||
app: {
|
||||
path,
|
||||
path: targetPath,
|
||||
value: rawAppValue,
|
||||
summary,
|
||||
policy,
|
||||
|
||||
Reference in New Issue
Block a user