From 72f32ea6e44ae4ddb7142fa66c0daa2463a02bb5 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 15 May 2026 12:52:50 -0400 Subject: [PATCH] fix: preserve workspaceId in bulkUpdateChatflows + add optional name sync - bulkUpdateChatflows was spreading the admin template entity without overriding workspaceId, causing all bulk-updated chatflows to land in the template owner's workspace (Default Workspace) instead of each user's Personal Workspace - Add workspaceId: targetChatflow.workspaceId to the override object - Add optional options.updateName flag so admins can choose to propagate the template name to user copies (off by default, preserving existing behaviour) - Wire options through controller and API client - Replace the raw Update button in Admin Chatflows UI with a confirmation dialog that exposes the updateName checkbox - Add migration 1770000000002 to repair chatflows already corrupted by prior bulk updates (idempotent, all orgs, same logic as prod SQL fix) --- .../ui/src/Admin/Chatflows/index.tsx | 87 +++++++++++++++---- .../server/src/controllers/chatflows/index.ts | 4 +- ...00000002-FixBulkUpdateChatflowWorkspace.ts | 48 ++++++++++ .../src/database/migrations/postgres/index.ts | 5 +- .../server/src/services/chatflows/index.ts | 10 ++- packages/ui/src/api/chatflows.js | 2 +- 6 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 packages/server/src/database/migrations/postgres/aai/1770000000002-FixBulkUpdateChatflowWorkspace.ts diff --git a/packages-answers/ui/src/Admin/Chatflows/index.tsx b/packages-answers/ui/src/Admin/Chatflows/index.tsx index 962958b67dd..2a2ec4bb80c 100644 --- a/packages-answers/ui/src/Admin/Chatflows/index.tsx +++ b/packages-answers/ui/src/Admin/Chatflows/index.tsx @@ -88,6 +88,10 @@ const AdminChatflows = () => { const [templateStatusFilter, setTemplateStatusFilter] = useState([]) const [selectedForUpdate, setSelectedForUpdate] = useState([]) + // Bulk update confirmation dialog state + const [bulkUpdateDialogOpen, setBulkUpdateDialogOpen] = useState(false) + const [bulkUpdateIncludeName, setBulkUpdateIncludeName] = useState(false) + // Versioning state const [versionModalOpen, setVersionModalOpen] = useState(false) const [selectedChatflowForVersions, setSelectedChatflowForVersions] = useState('') @@ -1027,22 +1031,9 @@ const AdminChatflows = () => { variant='contained' size='small' disabled={selectedForUpdate.length === 0} - onClick={async () => { - try { - const response = await chatflowsApi.bulkUpdateChatflows(selectedForUpdate) - - // Show success message and refresh data - if (response.updated > 0) { - // Refresh the chatflows data - window.location.reload() // Simple refresh for now - } - - // Clear selections - setSelectedForUpdate([]) - } catch (error) { - console.error('Bulk update failed:', error) - // TODO: Show error message to user - } + onClick={() => { + setBulkUpdateIncludeName(false) + setBulkUpdateDialogOpen(true) }} sx={{ bgcolor: alpha(theme.palette.warning.main, 0.8), @@ -1061,6 +1052,66 @@ const AdminChatflows = () => { + {/* Bulk update confirmation dialog */} + setBulkUpdateDialogOpen(false)} maxWidth='sm' fullWidth> + Confirm Template Update + + + You are about to push the latest template to {selectedForUpdate.length} chatflow + {selectedForUpdate.length !== 1 ? 's' : ''}. This will overwrite their flow configuration with the + current template version. + + setBulkUpdateIncludeName(e.target.checked)} + color='warning' + /> + } + label={ + + + Also update chatflow name to match template + + + Overwrites each user's chatflow name with the template name + + + } + /> + + + + + + + {selectedForUpdate.length > 0 && ( @@ -1071,7 +1122,9 @@ const AdminChatflows = () => {
• API settings, starter prompts, and system configuration will be updated
- • Name, description, owner, and organization will remain unchanged + • Description, owner, workspace, and organization will remain unchanged +
+ • Chatflow name is preserved unless "Also update name" is checked
• User customizations in flow logic may be overwritten
diff --git a/packages/server/src/controllers/chatflows/index.ts b/packages/server/src/controllers/chatflows/index.ts index 654f403c88b..66e7609d78a 100644 --- a/packages/server/src/controllers/chatflows/index.ts +++ b/packages/server/src/controllers/chatflows/index.ts @@ -291,12 +291,12 @@ const getDefaultChatflowTemplate = async (req: Request, res: Response, next: Nex const bulkUpdateChatflows = async (req: Request, res: Response, next: NextFunction) => { try { - const { chatflowIds } = req.body + const { chatflowIds, options } = req.body if (!Array.isArray(chatflowIds) || chatflowIds.length === 0) { return res.status(400).json({ error: 'chatflowIds must be a non-empty array' }) } - const apiResponse = await chatflowsService.bulkUpdateChatflows(chatflowIds, req.user!) + const apiResponse = await chatflowsService.bulkUpdateChatflows(chatflowIds, req.user!, options) return res.json(apiResponse) } catch (error) { next(error) diff --git a/packages/server/src/database/migrations/postgres/aai/1770000000002-FixBulkUpdateChatflowWorkspace.ts b/packages/server/src/database/migrations/postgres/aai/1770000000002-FixBulkUpdateChatflowWorkspace.ts new file mode 100644 index 00000000000..76d0a796ef2 --- /dev/null +++ b/packages/server/src/database/migrations/postgres/aai/1770000000002-FixBulkUpdateChatflowWorkspace.ts @@ -0,0 +1,48 @@ +/* eslint-disable no-console */ +import { MigrationInterface, QueryRunner } from 'typeorm' + +/** + * Fix chatflows whose workspaceId was overwritten by bulkUpdateChatflows. + * + * Root cause: bulkUpdateChatflows spread the admin template entity (including its + * workspaceId) onto each user's chatflow copy without overriding workspaceId. + * This caused all chatflows updated via the enterprise "push template" feature to + * land in the admin's workspace (typically 'Default Workspace') instead of each + * user's own 'Personal Workspace'. + * + * Migration 1770000000000 fixed the initial seeding case. This migration fixes + * the same class of breakage for chatflows re-corrupted by subsequent bulk updates + * after that migration ran. + * + * Idempotent: only moves chatflows that are currently in a 'Default Workspace'. + */ +export class FixBulkUpdateChatflowWorkspace1770000000002 implements MigrationInterface { + name = 'FixBulkUpdateChatflowWorkspace1770000000002' + + public async up(queryRunner: QueryRunner): Promise { + console.log('[FixBulkUpdateChatflowWorkspace] Starting: re-assign template chatflows to Personal Workspaces') + + const result = await queryRunner.query(` + UPDATE chat_flow cf + SET "workspaceId" = pw.id, + "updatedDate" = NOW() + FROM workspace pw, workspace cur + WHERE cur.id = cf."workspaceId" + AND pw."organizationId" = cf."organizationId" + AND pw.name = 'Personal Workspace' + AND pw."createdBy" = cf."userId" + AND cf."deletedDate" IS NULL + AND cf."parentChatflowId" IS NOT NULL + AND cur.name = 'Default Workspace' + `) + + const count = Array.isArray(result) ? result[1] ?? 0 : 0 + console.log(`[FixBulkUpdateChatflowWorkspace] Moved ${count} chatflows back to Personal Workspaces`) + console.log('[FixBulkUpdateChatflowWorkspace] Done') + } + + public async down(): Promise { + // Not reversible — chatflows should stay in Personal Workspaces + console.log('[FixBulkUpdateChatflowWorkspace] Down migration is a no-op') + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index e832e5bc16c..5b505f543bd 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -97,6 +97,7 @@ import { AddGuardrailsMetadataToChatMessage1753200000002 } from './aai/175320000 import { UpdateFiddlerCredentialsVisibility1768413137117 } from './aai/1768413137117-UpdateFiddlerCredentialsVisibility' import { MoveDefaultChatflowsToPersonalWorkspace1770000000000 } from './aai/1770000000000-MoveDefaultChatflowsToPersonalWorkspace' import { NormalizeLegacyCredentialNames1770000000001 } from './aai/1770000000001-NormalizeLegacyCredentialNames' +import { FixBulkUpdateChatflowWorkspace1770000000002 } from './aai/1770000000002-FixBulkUpdateChatflowWorkspace' export const postgresMigrations = [ Init1693891895163, @@ -196,5 +197,7 @@ export const postgresMigrations = [ // AAI: AGENT-674 - Move default sidekick chatflows to Personal Workspaces MoveDefaultChatflowsToPersonalWorkspace1770000000000, // AAI: Normalize legacy credential names in chat_flow.flowData and credential rows (e.g. JiraApi -> jiraApi) - NormalizeLegacyCredentialNames1770000000001 + NormalizeLegacyCredentialNames1770000000001, + // AAI: Fix chatflows re-assigned to Default Workspace by bulkUpdateChatflows workspace leak + FixBulkUpdateChatflowWorkspace1770000000002 ] diff --git a/packages/server/src/services/chatflows/index.ts b/packages/server/src/services/chatflows/index.ts index da99dbdda9f..712a394c13b 100644 --- a/packages/server/src/services/chatflows/index.ts +++ b/packages/server/src/services/chatflows/index.ts @@ -608,7 +608,11 @@ const getDefaultChatflowTemplate = async (): Promise<{ id: string; name: string } } -const bulkUpdateChatflows = async (chatflowIds: string[], user: IUser): Promise<{ updated: number; errors: string[] }> => { +const bulkUpdateChatflows = async ( + chatflowIds: string[], + user: IUser, + options?: { updateName?: boolean } +): Promise<{ updated: number; errors: string[] }> => { try { const appServer = getRunningExpressApp() const { id: _userId, organizationId } = user @@ -657,10 +661,12 @@ const bulkUpdateChatflows = async (chatflowIds: string[], user: IUser): Promise< const updatedChatflow = { ...templateChatflow, id: targetChatflow.id, - name: targetChatflow.name, // Preserve original name + // Only propagate template name when explicitly requested by admin + name: options?.updateName ? templateChatflow.name : targetChatflow.name, description: targetChatflow.description, // Preserve original description userId: targetChatflow.userId, // Preserve original owner organizationId: targetChatflow.organizationId, // Preserve original organization + workspaceId: targetChatflow.workspaceId, // Preserve original workspace (bug fix: was leaking template's workspaceId) parentChatflowId: targetChatflow.parentChatflowId, // Preserve parent relationship createdDate: targetChatflow.createdDate, // Preserve creation date currentVersion: (targetChatflow.currentVersion || 1) + 1, // Increment version diff --git a/packages/ui/src/api/chatflows.js b/packages/ui/src/api/chatflows.js index 497eb3f68fd..974fb6449b3 100644 --- a/packages/ui/src/api/chatflows.js +++ b/packages/ui/src/api/chatflows.js @@ -34,7 +34,7 @@ const getAdminChatflows = (filter, type = 'CHATFLOW') => { const getDefaultChatflowTemplate = () => client.get('/admin/chatflows/default-template') -const bulkUpdateChatflows = (chatflowIds) => client.put('/admin/chatflows/bulk-update', { chatflowIds }) +const bulkUpdateChatflows = (chatflowIds, options) => client.put('/admin/chatflows/bulk-update', { chatflowIds, options }) // Versioning API methods const getChatflowVersions = (id) => client.get(`/admin/chatflows/${id}/versions`)