Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 70 additions & 17 deletions packages-answers/ui/src/Admin/Chatflows/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@
const [templateStatusFilter, setTemplateStatusFilter] = useState<string[]>([])
const [selectedForUpdate, setSelectedForUpdate] = useState<string[]>([])

// 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<string>('')
Expand Down Expand Up @@ -334,7 +338,7 @@

useEffect(() => {
refreshChatflows()
}, [flowType, agentflowVersion])

Check warning on line 341 in packages-answers/ui/src/Admin/Chatflows/index.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a missing dependency: 'refreshChatflows'. Either include it or remove the dependency array

if (error) {
return (
Expand Down Expand Up @@ -1027,22 +1031,9 @@
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),
Expand All @@ -1061,6 +1052,66 @@
</Box>
</Box>

{/* Bulk update confirmation dialog */}
<Dialog open={bulkUpdateDialogOpen} onClose={() => setBulkUpdateDialogOpen(false)} maxWidth='sm' fullWidth>
<DialogTitle sx={{ fontWeight: 600 }}>Confirm Template Update</DialogTitle>
<DialogContent>
<Typography variant='body2' sx={{ mb: 2, color: theme.palette.text.secondary }}>
You are about to push the latest template to <strong>{selectedForUpdate.length}</strong> chatflow
{selectedForUpdate.length !== 1 ? 's' : ''}. This will overwrite their flow configuration with the
current template version.
</Typography>
<FormControlLabel
control={
<Checkbox
checked={bulkUpdateIncludeName}
onChange={(e) => setBulkUpdateIncludeName(e.target.checked)}
color='warning'
/>
}
label={
<Box>
<Typography variant='body2' sx={{ fontWeight: 500 }}>
Also update chatflow name to match template
</Typography>
<Typography variant='caption' sx={{ color: theme.palette.text.secondary }}>
Overwrites each user&apos;s chatflow name with the template name
</Typography>
</Box>
}
/>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2, gap: 1 }}>
<Button variant='outlined' onClick={() => setBulkUpdateDialogOpen(false)}>
Cancel
</Button>
<Button
variant='contained'
onClick={async () => {
setBulkUpdateDialogOpen(false)
try {
const response = await chatflowsApi.bulkUpdateChatflows(selectedForUpdate, {
updateName: bulkUpdateIncludeName
})
if (response.updated > 0) {
window.location.reload()
}
setSelectedForUpdate([])
} catch (error) {
console.error('Bulk update failed:', error)
}
}}
sx={{
bgcolor: alpha(theme.palette.warning.main, 0.8),
color: theme.palette.common.white,
'&:hover': { bgcolor: alpha(theme.palette.warning.main, 0.9) }
}}
>
Update {selectedForUpdate.length} Chatflow{selectedForUpdate.length !== 1 ? 's' : ''}
</Button>
</DialogActions>
</Dialog>

{selectedForUpdate.length > 0 && (
<Box sx={{ p: 2, bgcolor: 'rgba(0, 0, 0, 0.2)', borderRadius: '4px' }}>
<Typography variant='body2' sx={{ color: theme.palette.text.secondary, fontWeight: 600, mb: 1 }}>
Expand All @@ -1071,7 +1122,9 @@
<br />
• API settings, starter prompts, and system configuration will be updated
<br />
• Name, description, owner, and organization will remain unchanged
• Description, owner, workspace, and organization will remain unchanged
<br />
• Chatflow name is preserved unless &quot;Also update name&quot; is checked
<br />• User customizations in flow logic may be overwritten
</Typography>
</Box>
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/controllers/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<void> {
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<void> {
// Not reversible — chatflows should stay in Personal Workspaces
console.log('[FixBulkUpdateChatflowWorkspace] Down migration is a no-op')
}
}
5 changes: 4 additions & 1 deletion packages/server/src/database/migrations/postgres/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
]
10 changes: 8 additions & 2 deletions packages/server/src/services/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/api/chatflows.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
Loading