feat: get available evaluation workflow.

This commit is contained in:
FFXN
2026-03-30 18:18:57 +08:00
parent 3865483d95
commit a20c6bd4bc
3 changed files with 163 additions and 3 deletions

View File

@@ -158,6 +158,7 @@ class WorkflowListQuery(BaseModel):
limit: int = Field(default=10, ge=1, le=100)
user_id: str | None = None
named_only: bool = False
keyword: str | None = Field(default=None, max_length=255)
class WorkflowUpdatePayload(BaseModel):

View File

@@ -7,14 +7,15 @@ from typing import TYPE_CHECKING, ParamSpec, TypeVar, Union
from urllib.parse import quote
from flask import Response, request
from flask_restx import Resource, fields
from flask_restx import Resource, fields, marshal
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.orm import Session
from werkzeug.exceptions import BadRequest, NotFound
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
from controllers.common.schema import register_schema_models
from controllers.console import console_ns
from controllers.console.app.workflow import WorkflowListQuery
from controllers.console.wraps import (
account_initialization_required,
edit_permission_required,
@@ -23,6 +24,7 @@ from controllers.console.wraps import (
from core.evaluation.entities.evaluation_entity import EvaluationCategory, EvaluationConfigData, EvaluationRunRequest
from extensions.ext_database import db
from extensions.ext_storage import storage
from fields.member_fields import simple_account_fields
from graphon.file import helpers as file_helpers
from libs.helper import TimestampField
from libs.login import current_account_with_tenant, login_required
@@ -36,6 +38,7 @@ from services.errors.evaluation import (
EvaluationNotFoundError,
)
from services.evaluation_service import EvaluationService
from services.workflow_service import WorkflowService
if TYPE_CHECKING:
from models.evaluation import EvaluationRun, EvaluationRunItem
@@ -125,6 +128,33 @@ evaluation_detail_fields = {
evaluation_detail_model = console_ns.model("EvaluationDetail", evaluation_detail_fields)
available_evaluation_workflow_list_fields = {
"id": fields.String,
"app_id": fields.String,
"app_name": fields.String,
"type": fields.String,
"version": fields.String,
"marked_name": fields.String,
"marked_comment": fields.String,
"hash": fields.String,
"created_by": fields.Nested(simple_account_fields),
"created_at": TimestampField,
"updated_by": fields.Nested(simple_account_fields, allow_null=True),
"updated_at": TimestampField,
}
available_evaluation_workflow_pagination_fields = {
"items": fields.List(fields.Nested(available_evaluation_workflow_list_fields)),
"page": fields.Integer,
"limit": fields.Integer,
"has_more": fields.Boolean,
}
available_evaluation_workflow_pagination_model = console_ns.model(
"AvailableEvaluationWorkflowPagination",
available_evaluation_workflow_pagination_fields,
)
def get_evaluation_target(view_func: Callable[P, R]):
"""
@@ -601,6 +631,81 @@ class EvaluationVersionApi(Resource):
}
@console_ns.route("/workspaces/current/available-evaluation-workflows")
class AvailableEvaluationWorkflowsApi(Resource):
@console_ns.expect(console_ns.models[WorkflowListQuery.__name__])
@console_ns.doc("list_available_evaluation_workflows")
@console_ns.doc(description="List published evaluation workflows in the current workspace (all apps)")
@console_ns.response(
200,
"Available evaluation workflows retrieved",
available_evaluation_workflow_pagination_model,
)
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
def get(self):
"""List published evaluation-type workflows for the current tenant (cross-app)."""
current_user, current_tenant_id = current_account_with_tenant()
args = WorkflowListQuery.model_validate(request.args.to_dict(flat=True)) # type: ignore
page = args.page
limit = args.limit
user_id = args.user_id
named_only = args.named_only
keyword = args.keyword
if user_id and user_id != current_user.id:
raise Forbidden()
workflow_service = WorkflowService()
with Session(db.engine) as session:
workflows, has_more = workflow_service.list_published_evaluation_workflows(
session=session,
tenant_id=current_tenant_id,
page=page,
limit=limit,
user_id=user_id,
named_only=named_only,
keyword=keyword,
)
app_ids = {w.app_id for w in workflows}
if app_ids:
apps = session.scalars(select(App).where(App.id.in_(app_ids))).all()
app_names = {a.id: a.name for a in apps}
else:
app_names = {}
items = []
for wf in workflows:
items.append(
{
"id": wf.id,
"app_id": wf.app_id,
"app_name": app_names.get(wf.app_id, ""),
"type": wf.type.value,
"version": wf.version,
"marked_name": wf.marked_name,
"marked_comment": wf.marked_comment,
"hash": wf.unique_hash,
"created_by": wf.created_by_account,
"created_at": wf.created_at,
"updated_by": wf.updated_by_account,
"updated_at": wf.updated_at,
}
)
return (
marshal(
{"items": items, "page": page, "limit": limit, "has_more": has_more},
available_evaluation_workflow_pagination_fields,
),
200,
)
# ---- Serialization Helpers ----

View File

@@ -5,7 +5,7 @@ import uuid
from collections.abc import Callable, Generator, Mapping, Sequence
from typing import Any, cast
from sqlalchemy import exists, select
from sqlalchemy import and_, exists, or_, select
from sqlalchemy.orm import Session, sessionmaker
from configs import dify_config
@@ -58,6 +58,7 @@ from graphon.variables import VariableBase
from graphon.variables.input_entities import VariableEntityType
from graphon.variables.variables import Variable
from libs.datetime_utils import naive_utc_now
from libs.helper import escape_like_pattern
from models import Account
from models.human_input import HumanInputFormRecipient, RecipientType
from models.model import App, AppMode
@@ -240,6 +241,59 @@ class WorkflowService:
return workflows, has_more
def list_published_evaluation_workflows(
self,
*,
session: Session,
tenant_id: str,
page: int,
limit: int,
user_id: str | None,
named_only: bool = False,
keyword: str | None = None,
) -> tuple[Sequence[Workflow], bool]:
"""
List published evaluation-type workflows for a tenant (cross-app), excluding draft rows.
When ``keyword`` is non-empty, match workflows whose marked name or parent app name contains
the substring (case-insensitive, LIKE wildcards escaped).
"""
stmt = select(Workflow).where(
Workflow.tenant_id == tenant_id,
Workflow.type == WorkflowType.EVALUATION,
Workflow.version != Workflow.VERSION_DRAFT,
)
if user_id:
stmt = stmt.where(Workflow.created_by == user_id)
if named_only:
stmt = stmt.where(Workflow.marked_name != "")
keyword_stripped = keyword.strip() if keyword else ""
if keyword_stripped:
escaped = escape_like_pattern(keyword_stripped)
pattern = f"%{escaped}%"
stmt = stmt.join(
App,
and_(Workflow.app_id == App.id, App.tenant_id == tenant_id),
).where(
or_(
Workflow.marked_name.ilike(pattern, escape="\\"),
App.name.ilike(pattern, escape="\\"),
)
)
stmt = stmt.order_by(Workflow.created_at.desc()).limit(limit + 1).offset((page - 1) * limit)
workflows = session.scalars(stmt).all()
has_more = len(workflows) > limit
if has_more:
workflows = workflows[:-1]
return workflows, has_more
def sync_draft_workflow(
self,
*,