chore: auto-commit (10 archivos)
- chat.log - db.go - frontend/src/App.tsx - frontend/src/api.ts - frontend/src/components/CardForm.tsx - frontend/src/components/Dashboard.tsx - frontend/src/components/KanbanCard.tsx - frontend/src/types.ts - handlers.go - metrics.go Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+54
-6
@@ -39,6 +39,8 @@ type Totals struct {
|
||||
Cards int `json:"cards"`
|
||||
CardsCompleted int `json:"cards_completed_in_range"`
|
||||
CardsCreated int `json:"cards_created_in_range"`
|
||||
CardsActive int `json:"cards_active"`
|
||||
CardsDone int `json:"cards_done"`
|
||||
Columns int `json:"columns"`
|
||||
Users int `json:"users"`
|
||||
ActiveLocks int `json:"active_locks"`
|
||||
@@ -82,6 +84,8 @@ type AssigneeStat struct {
|
||||
type RequesterStat struct {
|
||||
Requester string `json:"requester"`
|
||||
Total int `json:"total"`
|
||||
Active int `json:"active"`
|
||||
Completed int `json:"completed_in_range"`
|
||||
}
|
||||
|
||||
type MovementStat struct {
|
||||
@@ -137,12 +141,25 @@ func parseDateOrDefault(s string, dflt time.Time) time.Time {
|
||||
return dflt
|
||||
}
|
||||
|
||||
func parseEndDateOrDefault(s string, dflt time.Time) time.Time {
|
||||
if s == "" {
|
||||
return dflt
|
||||
}
|
||||
if t, err := time.Parse("2006-01-02", s); err == nil {
|
||||
return t.Add(24*time.Hour - time.Nanosecond)
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
|
||||
return t
|
||||
}
|
||||
return dflt
|
||||
}
|
||||
|
||||
// GET /api/metrics?from=YYYY-MM-DD&to=YYYY-MM-DD&assignee_id=...&requester=...
|
||||
func handleMetrics(db *DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now().UTC()
|
||||
from := parseDateOrDefault(r.URL.Query().Get("from"), now.AddDate(0, 0, -30))
|
||||
to := parseDateOrDefault(r.URL.Query().Get("to"), now)
|
||||
to := parseEndDateOrDefault(r.URL.Query().Get("to"), now)
|
||||
assignee := r.URL.Query().Get("assignee_id")
|
||||
requester := r.URL.Query().Get("requester")
|
||||
|
||||
@@ -160,7 +177,15 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
|
||||
toStr := to.Format(time.RFC3339Nano)
|
||||
|
||||
m := &Metrics{
|
||||
Range: DateRange{From: from.Format("2006-01-02"), To: to.Format("2006-01-02")},
|
||||
Range: DateRange{From: from.Format("2006-01-02"), To: to.Format("2006-01-02")},
|
||||
ByColumn: []ColumnCount{},
|
||||
ThroughputDaily: []DailyCount{},
|
||||
CreatedDaily: []DailyCount{},
|
||||
CycleTimeColumn: []ColumnDuration{},
|
||||
TopAssignees: []AssigneeStat{},
|
||||
TopRequesters: []RequesterStat{},
|
||||
MovementsByUser: []MovementStat{},
|
||||
CumulativeFlow: []CumulativePoint{},
|
||||
}
|
||||
|
||||
cardWhere := "WHERE deleted_at IS NULL"
|
||||
@@ -190,6 +215,18 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
|
||||
).Scan(&m.Totals.CardsCreated); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.conn.QueryRow(
|
||||
`SELECT COUNT(*) FROM cards `+cardWhere+` AND (completed_at IS NULL OR completed_at='')`,
|
||||
args...,
|
||||
).Scan(&m.Totals.CardsActive); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.conn.QueryRow(
|
||||
`SELECT COUNT(*) FROM cards `+cardWhere+` AND completed_at IS NOT NULL AND completed_at!=''`,
|
||||
args...,
|
||||
).Scan(&m.Totals.CardsDone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = completedArgs
|
||||
|
||||
if err := db.conn.QueryRow(`SELECT COUNT(*) FROM columns`).Scan(&m.Totals.Columns); err != nil {
|
||||
@@ -276,12 +313,18 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
|
||||
}
|
||||
colRows.Close()
|
||||
|
||||
now := time.Now().UTC()
|
||||
cap := to
|
||||
if now.Before(cap) {
|
||||
cap = now
|
||||
}
|
||||
capStr := cap.Format(time.RFC3339Nano)
|
||||
for _, ci := range cols {
|
||||
histArgs := []any{ci.id, fromStr, toStr}
|
||||
histQ := `SELECT (julianday(COALESCE(h.exited_at, ?)) - julianday(h.entered_at)) * 86400000
|
||||
FROM card_column_history h JOIN cards c ON c.id=h.card_id
|
||||
WHERE h.column_id=? AND h.entered_at>=? AND h.entered_at<=?`
|
||||
histArgs = append([]any{toStr}, histArgs...)
|
||||
histArgs = append([]any{capStr}, histArgs...)
|
||||
if assignee != "" {
|
||||
histQ += ` AND c.assignee_id=?`
|
||||
histArgs = append(histArgs, assignee)
|
||||
@@ -325,9 +368,14 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
|
||||
|
||||
// Top requesters.
|
||||
reqRows, err := db.conn.Query(
|
||||
`SELECT requester, COUNT(*) as n FROM cards WHERE deleted_at IS NULL AND requester != '' AND created_at>=? AND created_at<=?`+
|
||||
`SELECT requester,
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN completed_at IS NULL OR completed_at='' THEN 1 ELSE 0 END) as active,
|
||||
SUM(CASE WHEN completed_at IS NOT NULL AND completed_at>=? AND completed_at<=? THEN 1 ELSE 0 END) as completed
|
||||
FROM cards
|
||||
WHERE deleted_at IS NULL AND requester != ''`+
|
||||
condFromCard(assignee, "", "", "AND")+
|
||||
` GROUP BY requester ORDER BY n DESC LIMIT 10`,
|
||||
` GROUP BY requester ORDER BY total DESC LIMIT 10`,
|
||||
topReqArgs(fromStr, toStr, assignee)...,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -335,7 +383,7 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
|
||||
}
|
||||
for reqRows.Next() {
|
||||
var s RequesterStat
|
||||
if err := reqRows.Scan(&s.Requester, &s.Total); err != nil {
|
||||
if err := reqRows.Scan(&s.Requester, &s.Total, &s.Active, &s.Completed); err != nil {
|
||||
reqRows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user