yao/agent/robot/api/activities.go
Max 3ef536c1e7 Add Activity Type Filtering to ListActivities API
- Introduced a new `Type` field in the `ActivityQuery` struct to allow filtering activities by type (e.g., execution.started, execution.completed, execution.failed, execution.cancelled).
- Updated the `ListActivities` method in `ExecutionStore` to handle the new type filter, mapping it to corresponding execution statuses.
- Enhanced unit tests in `execution_test.go` to validate filtering by activity type, including tests for valid and invalid type scenarios.
- Modified OpenAPI definitions and related types to support the new type filter in the activities endpoint, improving API usability and flexibility.
2026-01-24 18:28:16 +08:00

112 lines
3 KiB
Go

package api
import (
"context"
"fmt"
"time"
"github.com/yaoapp/yao/agent/robot/store"
"github.com/yaoapp/yao/agent/robot/types"
)
// ==================== Activity Types ====================
// ActivityQuery - query parameters for listing activities
type ActivityQuery struct {
TeamID string `json:"team_id,omitempty"` // Filter by team ID
Limit int `json:"limit,omitempty"`
Since *time.Time `json:"since,omitempty"` // Only activities after this time
Type string `json:"type,omitempty"` // Filter by activity type: execution.started, execution.completed, execution.failed, execution.cancelled
}
// Activity - activity item for feed
type Activity struct {
Type store.ActivityType `json:"type"`
RobotID string `json:"robot_id"`
RobotName string `json:"robot_name,omitempty"` // Display name from robot
ExecutionID string `json:"execution_id"`
Message string `json:"message"`
Timestamp time.Time `json:"timestamp"`
}
// ActivityListResponse - response with activities
type ActivityListResponse struct {
Data []*Activity `json:"data"`
}
// ==================== Activity API Functions ====================
// ListActivities returns recent activities for a team
// Activities are derived from execution status changes
func ListActivities(ctx *types.Context, query *ActivityQuery) (*ActivityListResponse, error) {
if query == nil {
query = &ActivityQuery{}
}
query.applyDefaults()
// Build store options
opts := &store.ActivityListOptions{
Limit: query.Limit,
Since: query.Since,
}
if query.TeamID != "" {
opts.TeamID = query.TeamID
}
// Pass type filter if provided
if query.Type != "" {
opts.Type = store.ActivityType(query.Type)
}
// Query from store
storeActivities, err := getExecutionStore().ListActivities(context.Background(), opts)
if err != nil {
return nil, fmt.Errorf("failed to list activities: %w", err)
}
// Transform to Activity slice
// Also enrich with robot display names
activities := make([]*Activity, 0, len(storeActivities))
robotNames := make(map[string]string) // Cache robot names
for _, sa := range storeActivities {
activity := &Activity{
Type: sa.Type,
RobotID: sa.RobotID,
ExecutionID: sa.ExecutionID,
Message: sa.Message,
Timestamp: sa.Timestamp,
}
// Try to get robot name (with caching)
if name, ok := robotNames[sa.RobotID]; ok {
activity.RobotName = name
} else {
// Try to get robot display name
robotResp, err := GetRobotResponse(ctx, sa.RobotID)
if err == nil && robotResp != nil {
activity.RobotName = robotResp.DisplayName
robotNames[sa.RobotID] = robotResp.DisplayName
}
}
activities = append(activities, activity)
}
return &ActivityListResponse{
Data: activities,
}, nil
}
// ==================== Helper Functions ====================
// applyDefaults applies default values to ActivityQuery
func (q *ActivityQuery) applyDefaults() {
if q.Limit <= 0 {
q.Limit = 20
}
if q.Limit > 100 {
q.Limit = 100
}
}