feat: add schema check for workflow_list on release
This commit is contained in:
@ -2121,6 +2121,11 @@ func (w *ApplicationService) ListWorkflow(ctx context.Context, req *workflow.Get
|
||||
},
|
||||
}
|
||||
|
||||
ww.CheckResult, err = GetWorkflowDomainSVC().WorkflowSchemaCheck(ctx, w, req.Checker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if qType == vo.FromDraft {
|
||||
ww.UpdateTime = w.DraftMeta.Timestamp.Unix()
|
||||
} else if qType == vo.FromLatestVersion || qType == vo.FromSpecificVersion {
|
||||
|
||||
@ -38,6 +38,7 @@ type Service interface {
|
||||
Publish(ctx context.Context, policy *vo.PublishPolicy) (err error)
|
||||
UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpdate) (err error)
|
||||
CopyWorkflow(ctx context.Context, workflowID int64, policy vo.CopyWorkflowPolicy) (*entity.Workflow, error)
|
||||
WorkflowSchemaCheck(ctx context.Context, wf *entity.Workflow, checks []workflow.CheckType) ([]*workflow.CheckResult, error)
|
||||
|
||||
QueryNodeProperties(ctx context.Context, id int64) (map[string]*vo.NodeProperty, error) // only draft
|
||||
ValidateTree(ctx context.Context, id int64, validateConfig vo.ValidateTreeConfig) ([]*workflow.ValidateTreeInfo, error)
|
||||
|
||||
@ -1593,6 +1593,55 @@ func (i *impl) GetWorkflowDependenceResource(ctx context.Context, workflowID int
|
||||
|
||||
}
|
||||
|
||||
// checkBotAgentNodes checks for rules related to BotAgent.
|
||||
// It returns an error with the reason if the check fails.
|
||||
func (i *impl) checkBotAgentNodes(nodes []*vo.Node) error {
|
||||
for _, node := range nodes {
|
||||
if node.Type == vo.BlockTypeCreateConversation || node.Type == vo.BlockTypeConversationDelete || node.Type == vo.BlockTypeConversationUpdate || node.Type == vo.BlockTypeConversationList {
|
||||
return errors.New("不支持在对话流内添加会话相关节点")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *impl) WorkflowSchemaCheck(ctx context.Context, wf *entity.Workflow, checks []cloudworkflow.CheckType) ([]*cloudworkflow.CheckResult, error) {
|
||||
if len(checks) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nodeList, err := GetAllNodesRecursively(ctx, wf, i.repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checkResults := make([]*cloudworkflow.CheckResult, 0, len(checks))
|
||||
for _, checkType := range checks {
|
||||
var checkErr error
|
||||
switch checkType {
|
||||
case cloudworkflow.CheckType_BotAgent:
|
||||
checkErr = i.checkBotAgentNodes(nodeList)
|
||||
// TODO: Add other cases here for new check types
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if checkErr != nil {
|
||||
checkResults = append(checkResults, &cloudworkflow.CheckResult{
|
||||
IsPass: false,
|
||||
Reason: checkErr.Error(),
|
||||
Type: checkType,
|
||||
})
|
||||
} else {
|
||||
checkResults = append(checkResults, &cloudworkflow.CheckResult{
|
||||
IsPass: true,
|
||||
Type: checkType,
|
||||
Reason: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
return checkResults, nil
|
||||
}
|
||||
|
||||
func (i *impl) MGet(ctx context.Context, policy *vo.MGetPolicy) ([]*entity.Workflow, int64, error) {
|
||||
if policy.MetaOnly {
|
||||
metas, total, err := i.repo.MGetMetas(ctx, &policy.MetaQuery)
|
||||
|
||||
@ -23,12 +23,14 @@ import (
|
||||
"strings"
|
||||
|
||||
cloudworkflow "github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/crossdomain/variable"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/adaptor"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/validate"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/sonic"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
@ -285,6 +287,70 @@ func replaceRelatedWorkflowOrPluginInWorkflowNodes(nodes []*vo.Node, relatedWork
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAllNodesRecursively(ctx context.Context, wfEntity *entity.Workflow, repo workflow.Repository) ([]*vo.Node, error) {
|
||||
visited := make(map[string]struct{})
|
||||
allNodes := make([]*vo.Node, 0)
|
||||
err := getAllNodesRecursiveHelper(ctx, wfEntity, repo, visited, &allNodes)
|
||||
return allNodes, err
|
||||
}
|
||||
|
||||
func getAllNodesRecursiveHelper(ctx context.Context, wfEntity *entity.Workflow, repo workflow.Repository, visited map[string]struct{}, allNodes *[]*vo.Node) error {
|
||||
visitedKey := fmt.Sprintf("%d:%s", wfEntity.ID, wfEntity.GetVersion())
|
||||
if _, ok := visited[visitedKey]; ok {
|
||||
return nil
|
||||
}
|
||||
visited[visitedKey] = struct{}{}
|
||||
|
||||
var canvas vo.Canvas
|
||||
if err := sonic.UnmarshalString(wfEntity.Canvas, &canvas); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal canvas for workflow %d: %w", wfEntity.ID, err)
|
||||
}
|
||||
|
||||
return collectNodes(ctx, canvas.Nodes, repo, visited, allNodes)
|
||||
}
|
||||
|
||||
func collectNodes(ctx context.Context, nodes []*vo.Node, repo workflow.Repository, visited map[string]struct{}, allNodes *[]*vo.Node) error {
|
||||
for _, node := range nodes {
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
*allNodes = append(*allNodes, node)
|
||||
|
||||
if node.Type == vo.BlockTypeBotSubWorkflow && node.Data != nil && node.Data.Inputs != nil {
|
||||
workflowIDStr := node.Data.Inputs.WorkflowID
|
||||
if workflowIDStr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
workflowID, err := strconv.ParseInt(workflowIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid workflow ID in sub-workflow node %s: %w", node.ID, err)
|
||||
}
|
||||
|
||||
subWfEntity, err := repo.GetEntity(ctx, &vo.GetPolicy{
|
||||
ID: workflowID,
|
||||
QType: ternary.IFElse(len(node.Data.Inputs.WorkflowVersion) == 0, vo.FromDraft, vo.FromSpecificVersion),
|
||||
Version: node.Data.Inputs.WorkflowVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get sub-workflow entity %d: %w", workflowID, err)
|
||||
}
|
||||
|
||||
if err := getAllNodesRecursiveHelper(ctx, subWfEntity, repo, visited, allNodes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(node.Blocks) > 0 {
|
||||
if err := collectNodes(ctx, node.Blocks, repo, visited, allNodes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// entityNodeTypeToBlockType converts an entity.NodeType to the corresponding vo.BlockType.
|
||||
func entityNodeTypeToBlockType(nodeType entity.NodeType) (vo.BlockType, error) {
|
||||
switch nodeType {
|
||||
|
||||
Reference in New Issue
Block a user