From 89f36112483b433767b7ec929e7a7d595a05abcd Mon Sep 17 00:00:00 2001 From: peewee92 <857586121@qq.com> Date: Tue, 18 Aug 2026 17:17:03 +0800 Subject: [PATCH] fix(agent): make tag metadata searchable and sortable in agent list (#18313) --- api/db/services/canvas_service.py | 2 +- internal/dao/user_canvas.go | 3 +- internal/dao/user_canvas_test.go | 77 +++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/api/db/services/canvas_service.py b/api/db/services/canvas_service.py index 6550638086..d5a3529e5b 100644 --- a/api/db/services/canvas_service.py +++ b/api/db/services/canvas_service.py @@ -156,7 +156,7 @@ class UserCanvasService(CommonService): .join(User, on=(cls.model.user_id == User.id)) .where( owner_filter, - (fn.LOWER(cls.model.title).contains(keywords.lower())), + (fn.LOWER(cls.model.title).contains(keywords.lower()) | fn.LOWER(cls.model.tags).contains(keywords.lower())), ) ) else: diff --git a/internal/dao/user_canvas.go b/internal/dao/user_canvas.go index 26ca185d41..c7cd68584c 100644 --- a/internal/dao/user_canvas.go +++ b/internal/dao/user_canvas.go @@ -42,6 +42,7 @@ var userCanvasOrderableColumns = map[string]struct{}{ "permission": {}, "canvas_type": {}, "canvas_category": {}, + "tags": {}, "create_time": {}, "create_date": {}, "update_time": {}, @@ -382,7 +383,7 @@ func (dao *UserCanvasDAO) ListByTenantIDs(ctx context.Context, db *gorm.DB, owne if keywords != "" { like := "%" + keywords + "%" - base = base.Where("user_canvas.title LIKE ?", like) + base = base.Where("user_canvas.title LIKE ? OR user_canvas.tags LIKE ?", like, like) } base = applyUserCanvasTagFilter(ctx, db, base, tags) diff --git a/internal/dao/user_canvas_test.go b/internal/dao/user_canvas_test.go index ffd30baf85..b6704a9010 100644 --- a/internal/dao/user_canvas_test.go +++ b/internal/dao/user_canvas_test.go @@ -264,3 +264,80 @@ func TestUserCanvasDAOOwnerAndCategoryFilters(t *testing.T) { t.Fatalf("dataflow_canvas count = %d, want 1", catByID["dataflow_canvas"]) } } + +// TestUserCanvasDAOKeywordSearchIncludesTags verifies that the keyword +// search matches agents by tag in addition to title (issue #14774: +// "Tag metadata should be searchable across the Agent list"). +func TestUserCanvasDAOKeywordSearchIncludesTags(t *testing.T) { + db := setupUserCanvasTestDB(t) + if err := db.AutoMigrate(&entity.User{}); err != nil { + t.Fatalf("failed to migrate user: %v", err) + } + pushDB(t, db) + ctx := t.Context() + d := NewUserCanvasDAO() + + if err := db.Create(&entity.User{ID: "u1", Nickname: "Owner", Email: "o@example.com"}).Error; err != nil { + t.Fatalf("create user: %v", err) + } + canvases := []entity.UserCanvas{ + {ID: "c1", UserID: "u1", Permission: "me", CanvasCategory: "agent_canvas", Title: stringPtr("Budget Report"), Tags: "finance,budget"}, + {ID: "c2", UserID: "u1", Permission: "me", CanvasCategory: "agent_canvas", Title: stringPtr("Sales Bot"), Tags: "customer-support"}, + } + for i := range canvases { + if err := db.Create(&canvases[i]).Error; err != nil { + t.Fatalf("create canvas: %v", err) + } + } + + results, _, err := d.ListByTenantIDs(ctx, db, []string{"u1"}, "u1", 1, 10, "create_time", false, "finance", "", "", nil) + if err != nil { + t.Fatalf("ListByTenantIDs: %v", err) + } + if len(results) != 1 { + t.Fatalf("keyword search 'finance' returned %d rows, want 1 (matched by tag not title)", len(results)) + } + if results[0].ID != "c1" { + t.Errorf("matched canvas id = %s, want c1", results[0].ID) + } +} + +// TestUserCanvasDAOOrderByTags verifies that agents can be sorted by +// their tags column (issue #14774: "Agents can be sorted by tag"). +func TestUserCanvasDAOOrderByTags(t *testing.T) { + db := setupUserCanvasTestDB(t) + if err := db.AutoMigrate(&entity.User{}); err != nil { + t.Fatalf("failed to migrate user: %v", err) + } + pushDB(t, db) + ctx := t.Context() + d := NewUserCanvasDAO() + + if err := db.Create(&entity.User{ID: "u1", Nickname: "Owner", Email: "o@example.com"}).Error; err != nil { + t.Fatalf("create user: %v", err) + } + canvases := []entity.UserCanvas{ + {ID: "c-z", UserID: "u1", Permission: "me", CanvasCategory: "agent_canvas", Title: stringPtr("Zeta"), Tags: "zebra"}, + {ID: "c-a", UserID: "u1", Permission: "me", CanvasCategory: "agent_canvas", Title: stringPtr("Alpha"), Tags: "alpha"}, + {ID: "c-m", UserID: "u1", Permission: "me", CanvasCategory: "agent_canvas", Title: stringPtr("Mid"), Tags: "middle"}, + } + for i := range canvases { + if err := db.Create(&canvases[i]).Error; err != nil { + t.Fatalf("create canvas: %v", err) + } + } + + results, _, err := d.ListByTenantIDs(ctx, db, []string{"u1"}, "u1", 1, 10, "tags", false, "", "", "", nil) + if err != nil { + t.Fatalf("ListByTenantIDs: %v", err) + } + if len(results) != 3 { + t.Fatalf("returned %d rows, want 3", len(results)) + } + want := []string{"c-a", "c-m", "c-z"} + for i, r := range results { + if r.ID != want[i] { + t.Errorf("row[%d] id = %s, want %s (ascending tag sort)", i, r.ID, want[i]) + } + } +}