perf(monitor): aggregate realtime workspace
This commit is contained in:
@@ -55,6 +55,7 @@ func (h *Handler) routes() {
|
||||
h.mux.HandleFunc("GET /api/ops/source-readiness", h.handleSourceReadiness)
|
||||
h.mux.HandleFunc("GET /api/v2/monitor/summary", h.handleMonitorSummary)
|
||||
h.mux.HandleFunc("GET /api/v2/monitor/map", h.handleMonitorMap)
|
||||
h.mux.HandleFunc("GET /api/v2/monitor/workspace", h.handleMonitorWorkspace)
|
||||
h.mux.HandleFunc("GET /api/v2/vehicles/{vin}/profile", h.handleVehicleProfile)
|
||||
h.mux.HandleFunc("PUT /api/v2/vehicles/{vin}/profile", h.handleSaveVehicleProfile)
|
||||
h.mux.HandleFunc("GET /api/v2/vehicles/{vin}/telemetry/latest", h.handleLatestTelemetry)
|
||||
@@ -307,6 +308,11 @@ func (h *Handler) handleMonitorMap(w http.ResponseWriter, r *http.Request) {
|
||||
h.write(w, r, data, err)
|
||||
}
|
||||
|
||||
func (h *Handler) handleMonitorWorkspace(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := h.service.MonitorWorkspace(r.Context(), r.URL.Query())
|
||||
h.write(w, r, data, err)
|
||||
}
|
||||
|
||||
func (h *Handler) handleDashboardSummary(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := h.service.DashboardSummary(r.Context())
|
||||
h.write(w, r, data, err)
|
||||
|
||||
@@ -834,6 +834,25 @@ func TestHandlerVehicleRealtime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerMonitorWorkspaceReturnsCoherentPayload(t *testing.T) {
|
||||
handler := NewHandler(NewService(NewMockStore()))
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v2/monitor/workspace?zoom=13&railLimit=2", nil)
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var body struct {
|
||||
Data MonitorWorkspaceResponse `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("response JSON should decode: %v body=%s", err, rec.Body.String())
|
||||
}
|
||||
if body.Data.Summary.TotalVehicles == 0 || body.Data.Map.Total == 0 || len(body.Data.Vehicles.Items) != 2 {
|
||||
t.Fatalf("workspace response is incomplete: %+v body=%s", body.Data, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerVehicleRealtimeFiltersServiceStatus(t *testing.T) {
|
||||
handler := NewHandler(NewService(NewMockStore()))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -32,6 +32,12 @@ type MonitorMapResponse struct {
|
||||
AsOf string `json:"asOf"`
|
||||
}
|
||||
|
||||
type MonitorWorkspaceResponse struct {
|
||||
Summary MonitorSummary `json:"summary"`
|
||||
Vehicles Page[VehicleRealtimeRow] `json:"vehicles"`
|
||||
Map MonitorMapResponse `json:"map"`
|
||||
}
|
||||
|
||||
type MonitorMapPoint struct {
|
||||
VIN string `json:"vin"`
|
||||
Plate string `json:"plate"`
|
||||
|
||||
@@ -488,6 +488,10 @@ func (s *Service) MonitorSummary(ctx context.Context, query url.Values) (Monitor
|
||||
if err != nil {
|
||||
return MonitorSummary{}, err
|
||||
}
|
||||
return s.buildMonitorSummary(ctx, query, vehicles)
|
||||
}
|
||||
|
||||
func (s *Service) buildMonitorSummary(ctx context.Context, query url.Values, vehicles Page[VehicleRealtimeRow]) (MonitorSummary, error) {
|
||||
dashboard, err := s.store.DashboardSummary(ctx)
|
||||
if err != nil {
|
||||
return MonitorSummary{}, err
|
||||
@@ -533,6 +537,34 @@ func (s *Service) MonitorSummary(ctx context.Context, query url.Values) (Monitor
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) MonitorWorkspace(ctx context.Context, query url.Values) (MonitorWorkspaceResponse, error) {
|
||||
vehicles, err := s.store.VehicleRealtime(ctx, normalizeMonitorQuery(query))
|
||||
if err != nil {
|
||||
return MonitorWorkspaceResponse{}, err
|
||||
}
|
||||
summary, err := s.buildMonitorSummary(ctx, query, vehicles)
|
||||
if err != nil {
|
||||
return MonitorWorkspaceResponse{}, err
|
||||
}
|
||||
monitorMap, err := buildMonitorMapResponse(vehicles, query)
|
||||
if err != nil {
|
||||
return MonitorWorkspaceResponse{}, err
|
||||
}
|
||||
railLimit := parsePositive(query.Get("railLimit"), 200)
|
||||
if railLimit > 200 {
|
||||
railLimit = 200
|
||||
}
|
||||
if railLimit > len(vehicles.Items) {
|
||||
railLimit = len(vehicles.Items)
|
||||
}
|
||||
railItems := append([]VehicleRealtimeRow(nil), vehicles.Items[:railLimit]...)
|
||||
return MonitorWorkspaceResponse{
|
||||
Summary: summary,
|
||||
Vehicles: Page[VehicleRealtimeRow]{Items: railItems, Total: vehicles.Total, Limit: railLimit, Offset: 0},
|
||||
Map: monitorMap,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) MonitorMap(ctx context.Context, query url.Values) (MonitorMapResponse, error) {
|
||||
vehicles, err := s.store.VehicleRealtime(ctx, normalizeMonitorQuery(query))
|
||||
if err != nil {
|
||||
|
||||
@@ -291,6 +291,26 @@ func TestMonitorMapTenThousandVehiclesNeverReturnsTenThousandPoints(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorWorkspaceSharesOneRealtimeSnapshot(t *testing.T) {
|
||||
store := newCountingStore()
|
||||
service := NewService(store)
|
||||
workspace, err := service.MonitorWorkspace(context.Background(), url.Values{
|
||||
"zoom": {"13"}, "railLimit": {"2"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("MonitorWorkspace returned error: %v", err)
|
||||
}
|
||||
if store.vehicleRealtimeCalls != 1 {
|
||||
t.Fatalf("workspace should read one realtime snapshot, calls=%d", store.vehicleRealtimeCalls)
|
||||
}
|
||||
if workspace.Summary.TotalVehicles == 0 || workspace.Map.Total == 0 {
|
||||
t.Fatalf("workspace should derive summary and map from the shared snapshot: %+v", workspace)
|
||||
}
|
||||
if len(workspace.Vehicles.Items) != 2 || workspace.Vehicles.Limit != 2 || workspace.Vehicles.Total < 2 {
|
||||
t.Fatalf("workspace rail page should honor its bounded limit: %+v", workspace.Vehicles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorMapUsesMeaningfulClustersAndReleasesPointsAtDetailZoom(t *testing.T) {
|
||||
vehicles := Page[VehicleRealtimeRow]{Items: []VehicleRealtimeRow{
|
||||
{VIN: "NEAR-1", Plate: "粤A00001", Online: true, Longitude: 113.260, Latitude: 23.130},
|
||||
|
||||
Reference in New Issue
Block a user