Fix chatflow's type null or blank (#5065)

* fix(entities/ChatFlow.ts): make type column non-nullable with default value

* fix(postgres/ModifyChatflowType): set default type and make column non-nullable

* fix(sqlite/ModifyChatflowType): set default type and make column non-nullable

* fix(mysql/ModifyChatflowType): set default type and make column non-nullable

* chore(sqlite/ModifyChatflowType): standardize type column to VARCHAR(20)

* chore(postgres/ModifyChatflowType): standardize type column to VARCHAR(20)

* fix(mariadb/ModifyChatflowType): set default type and make column non-nullable

* chore: rename ChatflowType to EnumChatflowType and update references

* feat(chatflows): add chatflow type validation

* fix(chatflows): empty string bypassing type validation on update
This commit is contained in:
Ong Chung Yau
2025-08-15 19:25:54 +08:00
committed by GitHub
parent 44087bc706
commit 4ce0851858
10 changed files with 180 additions and 73 deletions
@@ -2,6 +2,13 @@
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm' import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { ChatflowType, IChatFlow } from '../../Interface' import { ChatflowType, IChatFlow } from '../../Interface'
export enum EnumChatflowType {
CHATFLOW = 'CHATFLOW',
AGENTFLOW = 'AGENTFLOW',
MULTIAGENT = 'MULTIAGENT',
ASSISTANT = 'ASSISTANT'
}
@Entity() @Entity()
export class ChatFlow implements IChatFlow { export class ChatFlow implements IChatFlow {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
@@ -40,7 +47,7 @@ export class ChatFlow implements IChatFlow {
@Column({ nullable: true, type: 'text' }) @Column({ nullable: true, type: 'text' })
category?: string category?: string
@Column({ nullable: true, type: 'text' }) @Column({ type: 'varchar', length: 20, default: EnumChatflowType.CHATFLOW })
type?: ChatflowType type?: ChatflowType
@Column({ type: 'timestamp' }) @Column({ type: 'timestamp' })
@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { EnumChatflowType } from '../../entities/ChatFlow'
export class ModifyChatflowType1755066758601 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
UPDATE \`chat_flow\` SET \`type\` = '${EnumChatflowType.CHATFLOW}' WHERE \`type\` IS NULL OR \`type\` = '';
`)
await queryRunner.query(`
ALTER TABLE \`chat_flow\` MODIFY COLUMN \`type\` VARCHAR(20) NOT NULL DEFAULT '${EnumChatflowType.CHATFLOW}';
`)
}
public async down(): Promise<void> {}
}
@@ -36,6 +36,7 @@ import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEnt
import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable' import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable'
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun' import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { ModifyExecutionDataColumnType1747902489801 } from './1747902489801-ModifyExecutionDataColumnType' import { ModifyExecutionDataColumnType1747902489801 } from './1747902489801-ModifyExecutionDataColumnType'
import { ModifyChatflowType1755066758601 } from './1755066758601-ModifyChatflowType'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/mariadb/1720230151482-AddAuthTables' import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/mariadb/1720230151482-AddAuthTables'
import { AddWorkspace1725437498242 } from '../../../enterprise/database/migrations/mariadb/1725437498242-AddWorkspace' import { AddWorkspace1725437498242 } from '../../../enterprise/database/migrations/mariadb/1725437498242-AddWorkspace'
@@ -98,5 +99,6 @@ export const mariadbMigrations = [
FixOpenSourceAssistantTable1743758056188, FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174, AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554, ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionDataColumnType1747902489801 ModifyExecutionDataColumnType1747902489801,
ModifyChatflowType1755066758601
] ]
@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { EnumChatflowType } from '../../entities/ChatFlow'
export class ModifyChatflowType1755066758601 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
UPDATE \`chat_flow\` SET \`type\` = '${EnumChatflowType.CHATFLOW}' WHERE \`type\` IS NULL OR \`type\` = '';
`)
await queryRunner.query(`
ALTER TABLE \`chat_flow\` MODIFY COLUMN \`type\` VARCHAR(20) NOT NULL DEFAULT '${EnumChatflowType.CHATFLOW}';
`)
}
public async down(): Promise<void> {}
}
@@ -37,6 +37,7 @@ import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpe
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun' import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { FixErrorsColumnInEvaluationRun1746437114935 } from './1746437114935-FixErrorsColumnInEvaluationRun' import { FixErrorsColumnInEvaluationRun1746437114935 } from './1746437114935-FixErrorsColumnInEvaluationRun'
import { ModifyExecutionDataColumnType1747902489801 } from './1747902489801-ModifyExecutionDataColumnType' import { ModifyExecutionDataColumnType1747902489801 } from './1747902489801-ModifyExecutionDataColumnType'
import { ModifyChatflowType1755066758601 } from './1755066758601-ModifyChatflowType'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/mysql/1720230151482-AddAuthTables' import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/mysql/1720230151482-AddAuthTables'
import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/mysql/1720230151484-AddWorkspace' import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/mysql/1720230151484-AddWorkspace'
@@ -100,5 +101,6 @@ export const mysqlMigrations = [
AddErrorToEvaluationRun1744964560174, AddErrorToEvaluationRun1744964560174,
FixErrorsColumnInEvaluationRun1746437114935, FixErrorsColumnInEvaluationRun1746437114935,
ExecutionLinkWorkspaceId1746862866554, ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionDataColumnType1747902489801 ModifyExecutionDataColumnType1747902489801,
ModifyChatflowType1755066758601
] ]
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { EnumChatflowType } from '../../entities/ChatFlow'
export class ModifyChatflowType1755066758601 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
UPDATE "chat_flow" SET "type" = '${EnumChatflowType.CHATFLOW}' WHERE "type" IS NULL OR "type" = '';
`)
await queryRunner.query(`
ALTER TABLE "chat_flow" ALTER COLUMN "type" SET DEFAULT '${EnumChatflowType.CHATFLOW}';
`)
await queryRunner.query(`
ALTER TABLE "chat_flow" ALTER COLUMN "type" TYPE VARCHAR(20);
`)
await queryRunner.query(`
ALTER TABLE "chat_flow" ALTER COLUMN "type" SET NOT NULL;
`)
}
public async down(): Promise<void> {}
}
@@ -36,6 +36,7 @@ import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEnt
import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable' import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable'
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun' import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { ModifyExecutionSessionIdFieldType1748450230238 } from './1748450230238-ModifyExecutionSessionIdFieldType' import { ModifyExecutionSessionIdFieldType1748450230238 } from './1748450230238-ModifyExecutionSessionIdFieldType'
import { ModifyChatflowType1755066758601 } from './1755066758601-ModifyChatflowType'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/postgres/1720230151482-AddAuthTables' import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/postgres/1720230151482-AddAuthTables'
import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/postgres/1720230151484-AddWorkspace' import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/postgres/1720230151484-AddWorkspace'
@@ -98,5 +99,6 @@ export const postgresMigrations = [
FixOpenSourceAssistantTable1743758056188, FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174, AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554, ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionSessionIdFieldType1748450230238 ModifyExecutionSessionIdFieldType1748450230238,
ModifyChatflowType1755066758601
] ]
@@ -0,0 +1,40 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { EnumChatflowType } from '../../entities/ChatFlow'
export class ModifyChatflowType1755066758601 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "temp_chat_flow" (
"id" varchar PRIMARY KEY NOT NULL,
"name" varchar NOT NULL,
"flowData" text NOT NULL,
"deployed" boolean,
"isPublic" boolean,
"apikeyid" varchar,
"chatbotConfig" text,
"createdDate" datetime NOT NULL DEFAULT (datetime('now')),
"updatedDate" datetime NOT NULL DEFAULT (datetime('now')),
"apiConfig" TEXT,
"analytic" TEXT,
"category" TEXT,
"speechToText" TEXT,
"type" VARCHAR(20) NOT NULL DEFAULT '${EnumChatflowType.CHATFLOW}',
"workspaceId" TEXT,
"followUpPrompts" TEXT,
FOREIGN KEY ("workspaceId") REFERENCES "workspace"("id")
);
`)
await queryRunner.query(`
INSERT INTO "temp_chat_flow" ("id", "name", "flowData", "deployed", "isPublic", "apikeyid", "chatbotConfig", "createdDate", "updatedDate", "apiConfig", "analytic", "category", "speechToText", "type", "workspaceId", "followUpPrompts")
SELECT "id", "name", "flowData", "deployed", "isPublic", "apikeyid", "chatbotConfig", "createdDate", "updatedDate", "apiConfig", "analytic", "category", "speechToText",
CASE WHEN "type" IS NULL OR "type" = '' THEN '${EnumChatflowType.CHATFLOW}' ELSE "type" END, "workspaceId", "followUpPrompts" FROM "chat_flow";
`)
await queryRunner.query(`DROP TABLE "chat_flow";`)
await queryRunner.query(`ALTER TABLE "temp_chat_flow" RENAME TO "chat_flow";`)
}
public async down(): Promise<void> {}
}
@@ -34,6 +34,7 @@ import { AddSeqNoToDatasetRow1733752119696 } from './1733752119696-AddSeqNoToDat
import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEntity' import { AddExecutionEntity1738090872625 } from './1738090872625-AddExecutionEntity'
import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable' import { FixOpenSourceAssistantTable1743758056188 } from './1743758056188-FixOpenSourceAssistantTable'
import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun' import { AddErrorToEvaluationRun1744964560174 } from './1744964560174-AddErrorToEvaluationRun'
import { ModifyChatflowType1755066758601 } from './1755066758601-ModifyChatflowType'
import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/sqlite/1720230151482-AddAuthTables' import { AddAuthTables1720230151482 } from '../../../enterprise/database/migrations/sqlite/1720230151482-AddAuthTables'
import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/sqlite/1720230151484-AddWorkspace' import { AddWorkspace1720230151484 } from '../../../enterprise/database/migrations/sqlite/1720230151484-AddWorkspace'
@@ -94,5 +95,6 @@ export const sqliteMigrations = [
AddExecutionEntity1738090872625, AddExecutionEntity1738090872625,
FixOpenSourceAssistantTable1743758056188, FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174, AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554 ExecutionLinkWorkspaceId1746862866554,
ModifyChatflowType1755066758601
] ]
+69 -68
View File
@@ -4,7 +4,7 @@ import { In } from 'typeorm'
import { ChatflowType, IReactFlowObject } from '../../Interface' import { ChatflowType, IReactFlowObject } from '../../Interface'
import { FLOWISE_COUNTER_STATUS, FLOWISE_METRIC_COUNTERS } from '../../Interface.Metrics' import { FLOWISE_COUNTER_STATUS, FLOWISE_METRIC_COUNTERS } from '../../Interface.Metrics'
import { UsageCacheManager } from '../../UsageCacheManager' import { UsageCacheManager } from '../../UsageCacheManager'
import { ChatFlow } from '../../database/entities/ChatFlow' import { ChatFlow, EnumChatflowType } from '../../database/entities/ChatFlow'
import { ChatMessage } from '../../database/entities/ChatMessage' import { ChatMessage } from '../../database/entities/ChatMessage'
import { ChatMessageFeedback } from '../../database/entities/ChatMessageFeedback' import { ChatMessageFeedback } from '../../database/entities/ChatMessageFeedback'
import { UpsertHistory } from '../../database/entities/UpsertHistory' import { UpsertHistory } from '../../database/entities/UpsertHistory'
@@ -20,6 +20,15 @@ import { utilGetUploadsConfig } from '../../utils/getUploadsConfig'
import logger from '../../utils/logger' import logger from '../../utils/logger'
import { updateStorageUsage } from '../../utils/quotaUsage' import { updateStorageUsage } from '../../utils/quotaUsage'
export const enum ChatflowErrorMessage {
INVALID_CHATFLOW_TYPE = 'Invalid Chatflow Type'
}
export function validateChatflowType(type: ChatflowType | undefined) {
if (!Object.values(EnumChatflowType).includes(type as EnumChatflowType))
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, ChatflowErrorMessage.INVALID_CHATFLOW_TYPE)
}
// Check if chatflow valid for streaming // Check if chatflow valid for streaming
const checkIfChatflowIsValidForStreaming = async (chatflowId: string): Promise<any> => { const checkIfChatflowIsValidForStreaming = async (chatflowId: string): Promise<any> => {
try { try {
@@ -254,57 +263,51 @@ const saveChatflow = async (
subscriptionId: string, subscriptionId: string,
usageCacheManager: UsageCacheManager usageCacheManager: UsageCacheManager
): Promise<any> => { ): Promise<any> => {
try { validateChatflowType(newChatFlow.type)
const appServer = getRunningExpressApp() const appServer = getRunningExpressApp()
let dbResponse: ChatFlow let dbResponse: ChatFlow
if (containsBase64File(newChatFlow)) { if (containsBase64File(newChatFlow)) {
// we need a 2-step process, as we need to save the chatflow first and then update the file paths // we need a 2-step process, as we need to save the chatflow first and then update the file paths
// this is because we need the chatflow id to create the file paths // this is because we need the chatflow id to create the file paths
// step 1 - save with empty flowData // step 1 - save with empty flowData
const incomingFlowData = newChatFlow.flowData const incomingFlowData = newChatFlow.flowData
newChatFlow.flowData = JSON.stringify({}) newChatFlow.flowData = JSON.stringify({})
const chatflow = appServer.AppDataSource.getRepository(ChatFlow).create(newChatFlow) const chatflow = appServer.AppDataSource.getRepository(ChatFlow).create(newChatFlow)
const step1Results = await appServer.AppDataSource.getRepository(ChatFlow).save(chatflow) const step1Results = await appServer.AppDataSource.getRepository(ChatFlow).save(chatflow)
// step 2 - convert base64 to file paths and update the chatflow // step 2 - convert base64 to file paths and update the chatflow
step1Results.flowData = await updateFlowDataWithFilePaths( step1Results.flowData = await updateFlowDataWithFilePaths(
step1Results.id, step1Results.id,
incomingFlowData, incomingFlowData,
orgId, orgId,
workspaceId, workspaceId,
subscriptionId, subscriptionId,
usageCacheManager usageCacheManager
)
await _checkAndUpdateDocumentStoreUsage(step1Results, newChatFlow.workspaceId)
dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(step1Results)
} else {
const chatflow = appServer.AppDataSource.getRepository(ChatFlow).create(newChatFlow)
dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(chatflow)
}
await appServer.telemetry.sendTelemetry(
'chatflow_created',
{
version: await getAppVersion(),
chatflowId: dbResponse.id,
flowGraph: getTelemetryFlowObj(JSON.parse(dbResponse.flowData)?.nodes, JSON.parse(dbResponse.flowData)?.edges)
},
orgId
)
appServer.metricsProvider?.incrementCounter(
dbResponse?.type === 'MULTIAGENT' ? FLOWISE_METRIC_COUNTERS.AGENTFLOW_CREATED : FLOWISE_METRIC_COUNTERS.CHATFLOW_CREATED,
{ status: FLOWISE_COUNTER_STATUS.SUCCESS }
)
return dbResponse
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: chatflowsService.saveChatflow - ${getErrorMessage(error)}`
) )
await _checkAndUpdateDocumentStoreUsage(step1Results, newChatFlow.workspaceId)
dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(step1Results)
} else {
const chatflow = appServer.AppDataSource.getRepository(ChatFlow).create(newChatFlow)
dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(chatflow)
} }
await appServer.telemetry.sendTelemetry(
'chatflow_created',
{
version: await getAppVersion(),
chatflowId: dbResponse.id,
flowGraph: getTelemetryFlowObj(JSON.parse(dbResponse.flowData)?.nodes, JSON.parse(dbResponse.flowData)?.edges)
},
orgId
)
appServer.metricsProvider?.incrementCounter(
dbResponse?.type === 'MULTIAGENT' ? FLOWISE_METRIC_COUNTERS.AGENTFLOW_CREATED : FLOWISE_METRIC_COUNTERS.CHATFLOW_CREATED,
{ status: FLOWISE_COUNTER_STATUS.SUCCESS }
)
return dbResponse
} }
const updateChatflow = async ( const updateChatflow = async (
@@ -314,29 +317,27 @@ const updateChatflow = async (
workspaceId: string, workspaceId: string,
subscriptionId: string subscriptionId: string
): Promise<any> => { ): Promise<any> => {
try { const appServer = getRunningExpressApp()
const appServer = getRunningExpressApp() if (updateChatFlow.flowData && containsBase64File(updateChatFlow)) {
if (updateChatFlow.flowData && containsBase64File(updateChatFlow)) { updateChatFlow.flowData = await updateFlowDataWithFilePaths(
updateChatFlow.flowData = await updateFlowDataWithFilePaths( chatflow.id,
chatflow.id, updateChatFlow.flowData,
updateChatFlow.flowData, orgId,
orgId, workspaceId,
workspaceId, subscriptionId,
subscriptionId, appServer.usageCacheManager
appServer.usageCacheManager
)
}
const newDbChatflow = appServer.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow)
await _checkAndUpdateDocumentStoreUsage(newDbChatflow, chatflow.workspaceId)
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(newDbChatflow)
return dbResponse
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
`Error: chatflowsService.updateChatflow - ${getErrorMessage(error)}`
) )
} }
if (updateChatFlow.type || updateChatFlow.type === '') {
validateChatflowType(updateChatFlow.type)
} else {
updateChatFlow.type = chatflow.type
}
const newDbChatflow = appServer.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow)
await _checkAndUpdateDocumentStoreUsage(newDbChatflow, chatflow.workspaceId)
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(newDbChatflow)
return dbResponse
} }
// Get specific chatflow chatbotConfig via id (PUBLIC endpoint, used to retrieve config for embedded chat) // Get specific chatflow chatbotConfig via id (PUBLIC endpoint, used to retrieve config for embedded chat)