Skip to content

ability store (and version) flows in github repo #4767

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions packages/server/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export interface IChatFlow {
category?: string
type?: ChatflowType
workspaceId?: string
lastPublishedAt?: Date
lastPublishedCommit?: string
isDirty?: boolean
}

export interface IChatMessage {
Expand Down
14 changes: 14 additions & 0 deletions packages/server/src/controllers/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import chatflowsService from '../../services/chatflows'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { checkUsageLimit } from '../../utils/quotaUsage'
import { RateLimiterManager } from '../../utils/rateLimit'
import { FlowVersionService } from '../../enterprise/services/flow-version.service'

const checkIfChatflowIsValidForStreaming = async (req: Request, res: Response, next: NextFunction) => {
try {
Expand Down Expand Up @@ -99,6 +100,18 @@ const getChatflowById = async (req: Request, res: Response, next: NextFunction)
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.getChatflowById - id not provided!`)
}

// Check if commitId query parameter is provided
const commitId = req.query.commitId as string
if (commitId) {
const flowVersionService = new FlowVersionService()
const apiResponse = await flowVersionService.getChatflowByCommitId(req.params.id, commitId)
apiResponse.isDirty = false
apiResponse.lastPublishedCommit = commitId
return res.json(apiResponse)
}

// Fall back to regular chatflow service if no commitId provided
const apiResponse = await chatflowsService.getChatflowById(req.params.id)
return res.json(apiResponse)
} catch (error) {
Expand Down Expand Up @@ -200,6 +213,7 @@ const updateChatflow = async (req: Request, res: Response, next: NextFunction) =
}
const subscriptionId = req.user?.activeOrganizationSubscriptionId || ''
const body = req.body
body.isDirty = true
const updateChatFlow = new ChatFlow()
Object.assign(updateChatFlow, body)

Expand Down
9 changes: 9 additions & 0 deletions packages/server/src/database/entities/ChatFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,13 @@ export class ChatFlow implements IChatFlow {

@Column({ nullable: true, type: 'text' })
workspaceId?: string

@Column({ nullable: true })
lastPublishedAt?: Date

@Column({ nullable: true, type: 'text' })
lastPublishedCommit?: string

@Column({ nullable: true, type: 'boolean', default: true })
isDirty?: boolean
}
4 changes: 3 additions & 1 deletion packages/server/src/database/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { OrganizationUser } from '../../enterprise/database/entities/organizatio
import { Workspace } from '../../enterprise/database/entities/workspace.entity'
import { WorkspaceUser } from '../../enterprise/database/entities/workspace-user.entity'
import { LoginMethod } from '../../enterprise/database/entities/login-method.entity'
import { GitConfig } from '../../enterprise/database/entities/git-config.entity'

export const entities = {
ChatFlow,
Expand Down Expand Up @@ -55,5 +56,6 @@ export const entities = {
OrganizationUser,
Workspace,
WorkspaceUser,
LoginMethod
LoginMethod,
GitConfig
}
4 changes: 3 additions & 1 deletion packages/server/src/database/migrations/mariadb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrati
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/mariadb/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/mariadb/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/mariadb/1746862866554-ExecutionLinkWorkspaceId'
import { AddGitConfig1751035139965 } from '../../../enterprise/database/migrations/mariadb/1751035139965-AddGitConfig'

export const mariadbMigrations = [
Init1693840429259,
Expand Down Expand Up @@ -98,5 +99,6 @@ export const mariadbMigrations = [
FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionDataColumnType1747902489801
ModifyExecutionDataColumnType1747902489801,
AddGitConfig1751035139965
]
4 changes: 3 additions & 1 deletion packages/server/src/database/migrations/mysql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrati
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/mysql/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/mysql/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/mysql/1746862866554-ExecutionLinkWorkspaceId'
import { AddGitConfig1751035139965 } from '../../../enterprise/database/migrations/mysql/1751035139965-AddGitConfig'

export const mysqlMigrations = [
Init1693840429259,
Expand Down Expand Up @@ -100,5 +101,6 @@ export const mysqlMigrations = [
AddErrorToEvaluationRun1744964560174,
FixErrorsColumnInEvaluationRun1746437114935,
ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionDataColumnType1747902489801
ModifyExecutionDataColumnType1747902489801,
AddGitConfig1751035139965
]
4 changes: 3 additions & 1 deletion packages/server/src/database/migrations/postgres/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrati
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/postgres/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/postgres/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/postgres/1746862866554-ExecutionLinkWorkspaceId'
import { AddGitConfig1751035139965 } from '../../../enterprise/database/migrations/postgres/1751035139965-AddGitConfig'

export const postgresMigrations = [
Init1693891895163,
Expand Down Expand Up @@ -98,5 +99,6 @@ export const postgresMigrations = [
FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554,
ModifyExecutionSessionIdFieldType1748450230238
ModifyExecutionSessionIdFieldType1748450230238,
AddGitConfig1751035139965
]
4 changes: 3 additions & 1 deletion packages/server/src/database/migrations/sqlite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { AddSSOColumns1730519457880 } from '../../../enterprise/database/migrati
import { AddPersonalWorkspace1734074497540 } from '../../../enterprise/database/migrations/sqlite/1734074497540-AddPersonalWorkspace'
import { RefactorEnterpriseDatabase1737076223692 } from '../../../enterprise/database/migrations/sqlite/1737076223692-RefactorEnterpriseDatabase'
import { ExecutionLinkWorkspaceId1746862866554 } from '../../../enterprise/database/migrations/sqlite/1746862866554-ExecutionLinkWorkspaceId'
import { AddGitConfig1751035139965 } from '../../../enterprise/database/migrations/sqlite/1751035139965-AddGitConfig'

export const sqliteMigrations = [
Init1693835579790,
Expand Down Expand Up @@ -94,5 +95,6 @@ export const sqliteMigrations = [
AddExecutionEntity1738090872625,
FixOpenSourceAssistantTable1743758056188,
AddErrorToEvaluationRun1744964560174,
ExecutionLinkWorkspaceId1746862866554
ExecutionLinkWorkspaceId1746862866554,
AddGitConfig1751035139965
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Request, Response, NextFunction } from 'express'
import { StatusCodes } from 'http-status-codes'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { FlowVersionService } from '../services/flow-version.service'

export class FlowVersionController {
public async publishFlow(req: Request, res: Response, next: NextFunction) {
try {
if (!req.params || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'id not provided!')
}
const message = req.body?.message
if (!message) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'message not provided!')
}
const service = new FlowVersionService()
const result = await service.publishFlow(req.params.id, message)
if (result.success) {
return res.status(StatusCodes.OK).json(result)
} else {
return res.status(StatusCodes.BAD_REQUEST).json(result)
}
} catch (error) {
next(error)
}
}

public async getVersions(req: Request, res: Response, next: NextFunction) {
try {
if (!req.params || !req.params.id || req.params.id === "undefined") {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'id not provided!')
}
const service = new FlowVersionService()
const result = await service.getVersions(req.params.id)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}

public async makeDraft(req: Request, res: Response, next: NextFunction) {
try {
if (!req.params || !req.params.id || req.params.id === "undefined") {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'id not provided!')
}
if (!req.params || !req.params.commitId || req.params.commitId === "undefined") {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'commitId not provided!')
}
const service = new FlowVersionService()
const result = await service.makeDraft(req.params.id, req.params.commitId)
return res.status(StatusCodes.OK).json(result)
} catch (error) {
next(error)
}
}

public async check(req: Request, res: Response, next: NextFunction) {
try {
const service = new FlowVersionService()
const result = await service.check()
return res.status(StatusCodes.OK).json({
isActive: result
})
} catch (error) {
next(error)
}
}
}
145 changes: 145 additions & 0 deletions packages/server/src/enterprise/controllers/git-config.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { Request, Response, NextFunction } from 'express'
import { StatusCodes } from 'http-status-codes'
import { GitConfigService } from '../services/git-config.service'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { GitConfig } from '../database/entities/git-config.entity'

export class GitConfigController {
public async getAll(req: Request, res: Response, next: NextFunction) {
try {
const currentUser = req.user
if (!currentUser) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'User not found')
}
const organizationId = currentUser.activeOrganizationId
const service = new GitConfigService()
const configs = await service.getAllGitConfigs(organizationId)
return res.status(StatusCodes.OK).json(configs)
} catch (error) {
next(error)
}
}

public async getById(req: Request, res: Response, next: NextFunction) {
try {
const currentUser = req.user
if (!currentUser) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'User not found')
}
const organizationId = currentUser.activeOrganizationId

if (!req.params || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'id not provided!')
}
const service = new GitConfigService()
const config = await service.getGitConfigById(req.params.id, organizationId)
if (!config) return res.status(StatusCodes.NOT_FOUND).json({ message: 'Git config not found' })
return res.status(StatusCodes.OK).json(config)
} catch (error) {
next(error)
}
}

public async create(req: Request, res: Response, next: NextFunction) {
try {
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'body not provided!')
}
const currentUser = req.user
if (!currentUser) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'User not found')
}
const organizationId = currentUser.activeOrganizationId
const body: Partial<GitConfig> = {
...req.body,
organizationId,
createdBy: currentUser.id,
createdByUser: currentUser,
updatedBy: currentUser.id,
updatedByUser: currentUser,
isActive: true
}
const service = new GitConfigService()
const config = await service.createGitConfig(body)
return res.status(StatusCodes.CREATED).json(config)
} catch (error) {
next(error)
}
}

public async testConnection(req: Request, res: Response, next: NextFunction) {
try {
if (!req.body || !req.body.username || !req.body.secret || !req.body.repository) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'username, secret, and repository are required!')
}
const service = new GitConfigService()
const config = await service.testGitConfig(req.body)
return res.status(StatusCodes.OK).json(config)
} catch (error) {
next(error)
}
}

public async getBranches(req: Request, res: Response, next: NextFunction) {
try {
if (!req.params || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'id not provided!')
}
const currentUser = req.user
if (!currentUser) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'User not found')
}
const organizationId = currentUser.activeOrganizationId
const service = new GitConfigService()
const branches = await service.getBranches(req.params.id, organizationId)
return res.status(StatusCodes.OK).json(branches)
} catch (error) {
next(error)
}
}

public async update(req: Request, res: Response, next: NextFunction) {
try {
if (!req.params || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'id not provided!')
}
if (!req.body) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'body not provided!')
}
const currentUser = req.user
if (!currentUser) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'User not found')
}
const organizationId = currentUser.activeOrganizationId
const body = {
...req.body,
organizationId
}
const service = new GitConfigService()
const config = await service.updateGitConfig(req.params.id, body)
if (!config) return res.status(StatusCodes.NOT_FOUND).json({ message: 'Git config not found' })
return res.status(StatusCodes.OK).json(config)
} catch (error) {
next(error)
}
}

public async delete(req: Request, res: Response, next: NextFunction) {
try {
if (!req.params || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, 'id not provided!')
}
const currentUser = req.user
if (!currentUser) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, 'User not found')
}
const organizationId = currentUser.activeOrganizationId
const service = new GitConfigService()
const result = await service.deleteGitConfig(req.params.id, organizationId)
if (!result) return res.status(StatusCodes.NOT_FOUND).json({ message: 'Git config not found' })
return res.status(StatusCodes.NO_CONTENT).send()
} catch (error) {
next(error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ export class LoginActivity implements ILoginActivity {
@UpdateDateColumn()
attemptedDateTime: Date
}

Loading