yao/openapi/agent/robot/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

107 lines
2.8 KiB
Go

package robot
import (
"time"
"github.com/gin-gonic/gin"
"github.com/yaoapp/kun/log"
robotapi "github.com/yaoapp/yao/agent/robot/api"
robottypes "github.com/yaoapp/yao/agent/robot/types"
"github.com/yaoapp/yao/openapi/oauth/authorized"
"github.com/yaoapp/yao/openapi/response"
)
// ==================== Activities Handler ====================
// Activities are derived from execution status changes across all robots in a team
// ListActivities lists recent activities for the user's team
// GET /v1/agent/robots/activities
func ListActivities(c *gin.Context) {
// Get authorized information
authInfo := authorized.GetInfo(c)
// Get team_id from auth - activities are team-scoped
teamID := ""
if authInfo != nil {
teamID = authInfo.TeamID
// If no team_id, fall back to user_id for personal users
if teamID == "" {
teamID = authInfo.UserID
}
}
if teamID == "" {
errorResp := &response.ErrorResponse{
Code: response.ErrAccessDenied.Code,
ErrorDescription: "Unable to determine team scope",
}
response.RespondWithError(c, response.StatusForbidden, errorResp)
return
}
// Parse query parameters
var filter ActivityFilter
if err := c.ShouldBindQuery(&filter); err != nil {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "Invalid query parameters: " + err.Error(),
}
response.RespondWithError(c, response.StatusBadRequest, errorResp)
return
}
// Apply defaults
if filter.Limit <= 0 {
filter.Limit = 20
}
if filter.Limit > 100 {
filter.Limit = 100
}
// Create robot context
ctx := &robottypes.Context{}
// Build API query
query := &robotapi.ActivityQuery{
TeamID: teamID,
Limit: filter.Limit,
Type: filter.Type, // Pass type filter
}
// Parse 'since' if provided
if filter.Since != "" {
since, err := time.Parse(time.RFC3339, filter.Since)
if err != nil {
errorResp := &response.ErrorResponse{
Code: response.ErrInvalidRequest.Code,
ErrorDescription: "Invalid 'since' parameter: must be RFC3339 format",
}
response.RespondWithError(c, response.StatusBadRequest, errorResp)
return
}
query.Since = &since
}
// Call API layer
result, err := robotapi.ListActivities(ctx, query)
if err != nil {
log.Error("Failed to list activities for team %s: %v", teamID, err)
errorResp := &response.ErrorResponse{
Code: response.ErrServerError.Code,
ErrorDescription: "Failed to list activities: " + err.Error(),
}
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
return
}
// Convert to response
data := make([]*ActivityResponse, 0, len(result.Data))
for _, item := range result.Data {
data = append(data, NewActivityResponse(item))
}
resp := &ActivityListResponse{
Data: data,
}
response.RespondWithSuccess(c, response.StatusOK, resp)
}