From 6fa808c87524e94adfc5cadc2aa288bb747ab1d4 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 24 Jan 2026 18:49:39 +0800 Subject: [PATCH] Enhance ListRobots API to Include Runtime Status - Added concurrent fetching of runtime status for each robot in the ListRobots function, improving response efficiency. - Updated the Response struct to include new fields for runtime status: Running, MaxRunning, LastRun, and NextRun, optimizing dashboard display. - Implemented unit tests to verify the inclusion of runtime status fields in the ListRobots response, ensuring accurate data representation for users. --- openapi/agent/robot/list.go | 28 +++++++++++++--- openapi/agent/robot/types.go | 6 ++++ openapi/tests/agent/robot_test.go | 53 +++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/openapi/agent/robot/list.go b/openapi/agent/robot/list.go index 1a7fc6f0..29d32c9d 100644 --- a/openapi/agent/robot/list.go +++ b/openapi/agent/robot/list.go @@ -3,6 +3,7 @@ package robot import ( "strconv" "strings" + "sync" "github.com/gin-gonic/gin" "github.com/yaoapp/kun/log" @@ -80,11 +81,30 @@ func ListRobots(c *gin.Context) { return } - // Convert to HTTP response format - robots := make([]*Response, 0, len(result.Data)) - for _, r := range result.Data { - robots = append(robots, newResponseFromRobot(r)) + // Convert to HTTP response format with runtime status + robots := make([]*Response, len(result.Data)) + var wg sync.WaitGroup + + for i, r := range result.Data { + wg.Add(1) + go func(idx int, robot *robottypes.Robot) { + defer wg.Done() + resp := newResponseFromRobot(robot) + + // Fetch runtime status for each robot + if status, err := robotapi.GetRobotStatus(ctx, robot.MemberID); err == nil && status != nil { + resp.Running = status.Running + resp.MaxRunning = status.MaxRunning + resp.LastRun = status.LastRun + resp.NextRun = status.NextRun + // Use runtime status instead of stored status + resp.RobotStatus = string(status.Status) + } + + robots[idx] = resp + }(i, r) } + wg.Wait() resp := &ListResponse{ Data: robots, diff --git a/openapi/agent/robot/types.go b/openapi/agent/robot/types.go index 3695b9d2..a5129196 100644 --- a/openapi/agent/robot/types.go +++ b/openapi/agent/robot/types.go @@ -126,6 +126,12 @@ type Response struct { // Timestamps CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` + + // Runtime Status (populated in list view for dashboard) + Running int `json:"running"` // Current running executions count + MaxRunning int `json:"max_running,omitempty"` // Maximum concurrent executions + LastRun *time.Time `json:"last_run,omitempty"` // Last execution time + NextRun *time.Time `json:"next_run,omitempty"` // Next scheduled run time } // StatusResponse - runtime status response diff --git a/openapi/tests/agent/robot_test.go b/openapi/tests/agent/robot_test.go index 92ad12aa..e73852a1 100644 --- a/openapi/tests/agent/robot_test.go +++ b/openapi/tests/agent/robot_test.go @@ -52,6 +52,15 @@ func TestListRobots(t *testing.T) { assert.Contains(t, response, "page") assert.Contains(t, response, "pagesize") assert.Contains(t, response, "total") + + // Verify runtime status fields are included in robot items + if data, ok := response["data"].([]interface{}); ok && len(data) > 0 { + robot := data[0].(map[string]interface{}) + // Runtime status fields should be present (added for dashboard optimization) + assert.Contains(t, robot, "running", "Robot should include running count") + assert.Contains(t, robot, "max_running", "Robot should include max_running") + // last_run and next_run are optional (omitempty) + } }) t.Run("ListRobotsWithPagination", func(t *testing.T) { @@ -136,6 +145,50 @@ func TestListRobots(t *testing.T) { } }) + t.Run("ListRobotsIncludesRuntimeStatus", func(t *testing.T) { + // Verify that list response includes runtime status fields for dashboard optimization + req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots", nil) + require.NoError(t, err) + + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + require.NoError(t, err) + + // Verify we have robots to test + data, ok := response["data"].([]interface{}) + require.True(t, ok, "Response should have data array") + + if len(data) > 0 { + robot := data[0].(map[string]interface{}) + + // Runtime status fields (running is always present, defaults to 0) + _, hasRunning := robot["running"] + assert.True(t, hasRunning, "Robot should include 'running' field for dashboard") + + // max_running should be present (with omitempty, only if > 0) + // The field is returned by GetRobotStatus, so it should be there + if maxRunning, ok := robot["max_running"]; ok { + assert.GreaterOrEqual(t, maxRunning.(float64), float64(0), "max_running should be >= 0") + } + + // robot_status should reflect runtime status + robotStatus, hasStatus := robot["robot_status"] + assert.True(t, hasStatus, "Robot should include 'robot_status' field") + if hasStatus { + validStatuses := []string{"idle", "working", "paused", "error", "maintenance"} + assert.Contains(t, validStatuses, robotStatus.(string), "robot_status should be a valid status") + } + } + }) + t.Run("ListRobotsUnauthorized", func(t *testing.T) { req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots", nil) require.NoError(t, err)