项目文件夹

文件
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

107 行
3.9 KiB
Go

package server
import (
"context"
"fmt"
"net/http"
"time"
"go.kenn.io/agentsview/internal/activity"
"go.kenn.io/agentsview/internal/db"
)
func (s *Server) registerActivityRoutes() {
group := newRouteGroup(s.api, "/api/v1/activity", "Activity")
get(s, group, "/report", "Get activity report", s.humaActivityReport)
}
type activityReportInput struct {
Preset string `query:"preset" enum:"day,week,month,custom" doc:"Range preset"`
Date string `query:"date" format:"date" doc:"Calendar day (YYYY-MM-DD) for presets"`
From string `query:"from" doc:"Range start (RFC3339) for custom ranges"`
To string `query:"to" doc:"Range end (RFC3339) for custom ranges"`
Timezone string `query:"timezone" doc:"IANA timezone name"`
Bucket string `query:"bucket" enum:"5m,15m,1h,1d,1w" doc:"Timeline bucket size override"`
Project string `query:"project" doc:"Filter by project"`
GitBranch string `query:"git_branch" doc:"Filter by git branch; opaque (project, branch) tokens from the /branches endpoint"`
Agent string `query:"agent" doc:"Filter by agent"`
Machine string `query:"machine" doc:"Filter by machine"`
// Automation classes the report: "all" (default) keeps both, "interactive"
// drops automated sessions, "automated" drops interactive ones. Empty is
// treated as "all"; any other value is rejected.
Automation string `query:"automation" default:"all" doc:"Automation class: all, interactive, or automated"`
}
func (s *Server) humaActivityReport(
ctx context.Context, in *activityReportInput,
) (*jsonOutput[activity.Report], error) {
tz := in.Timezone
if tz == "" {
tz = "UTC"
}
loc, err := time.LoadLocation(tz)
if err != nil {
return nil, apiError(http.StatusBadRequest, "invalid timezone: "+tz)
}
input := activity.QueryInput{
Preset: in.Preset, Date: in.Date, From: in.From, To: in.To,
Timezone: tz, BucketOverride: in.Bucket,
}
// Presets need an anchor date; default to today in the requested
// timezone, matching the prior day-only handler's behavior.
if input.Date == "" && input.From == "" {
input.Date = time.Now().In(loc).Format("2006-01-02")
}
q, err := activity.ResolveQuery(input, time.Now())
if err != nil {
return nil, apiError(http.StatusBadRequest, err.Error())
}
excludeAutomated, excludeInteractive, err := activityAutomationFilter(in.Automation)
if err != nil {
return nil, apiError(http.StatusBadRequest, err.Error())
}
// The activity report intentionally includes one-shot sessions, unlike
// analytics which excludes them by default. The automation class is the
// caller's choice (default "all" keeps both automated and interactive).
f := db.AnalyticsFilter{
Timezone: tz, Project: in.Project, GitBranch: in.GitBranch,
Agent: in.Agent, Machine: in.Machine,
ExcludeOneShot: false,
ExcludeAutomated: excludeAutomated,
ExcludeInteractive: excludeInteractive,
}
r, err := s.db.GetActivityReport(ctx, f, q)
if err != nil {
if handled := handleHumaContextError(err); handled != nil {
return nil, handled
}
if handled := handleHumaReadOnly(err); handled != nil {
return nil, handled
}
return nil, internalError("activity report error", err)
}
return &jsonOutput[activity.Report]{Body: r}, nil
}
// activityAutomationFilter maps the activity report's automation query value to
// the AnalyticsFilter class exclusions. Empty and "all" keep both classes;
// "interactive" drops automated sessions; "automated" drops interactive ones.
// Any other value is an error so a typo surfaces as 400 rather than silently
// returning the unfiltered report.
func activityAutomationFilter(
automation string,
) (excludeAutomated, excludeInteractive bool, err error) {
switch automation {
case "", "all":
return false, false, nil
case "interactive":
return true, false, nil
case "automated":
return false, true, nil
default:
return false, false, fmt.Errorf(
"invalid automation %q (want all, interactive, or automated)",
automation)
}
}