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.
This commit is contained in:
Max 2026-01-24 18:49:39 +08:00
parent 3ef536c1e7
commit 6fa808c875
3 changed files with 83 additions and 4 deletions

View file

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

View file

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

View file

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