fix(agent): make tag metadata searchable and sortable in agent list (#18313)

This commit is contained in:
peewee92
2026-08-18 17:17:03 +08:00
committed by GitHub
parent b2baddfe77
commit 89f3611248
3 changed files with 80 additions and 2 deletions

View File

@@ -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:

View File

@@ -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)

View File

@@ -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])
}
}
}