Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions cmd/gomodel/docs/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions docs/features/session-keeping.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,11 @@ checkbox. Requests with no detected session remain round robin.
Audit entries record the session id (`session_id`), and the Audit Logs page
groups them by default ("Group by session" toggle). Each thread shows its
latest request with a count badge; the expander on the left unfolds the older
requests. `GET /admin/audit/sessions` serves the thread list;
`GET /admin/audit/log?session_id=…` returns one session's entries.
requests. The badge counts the complete session. Filters select which sessions
appear and which matching request represents each thread.
`GET /admin/audit/sessions` returns each thread's `request_count` and latest
matching audit-log entry. `GET /admin/audit/log?session_id=…` returns all
requests in an expanded session.

## Configuration

Expand Down
38 changes: 26 additions & 12 deletions docs/openapi.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/admin/dashboard/static/dist/index.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions internal/admin/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,8 @@ type auditConversationResponse struct {
}

type auditSessionResponse struct {
SessionID string `json:"session_id,omitempty"`
Count int `json:"count"`
FirstTimestamp time.Time `json:"first_timestamp"`
LastTimestamp time.Time `json:"last_timestamp"`
Latest auditLogEntryResponse `json:"latest"`
RequestCount int `json:"request_count"`
Latest auditLogEntryResponse `json:"latest"`
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

type auditSessionsListResponse struct {
Expand Down
11 changes: 4 additions & 7 deletions internal/admin/handler_audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ func parseAuditLogQueryParams(c *echo.Context) (auditlog.LogQueryParams, error)
//
// @Summary Get paginated audit sessions (threads)
// @Description Groups audit log entries by session id into threads and returns
// @Description one summary per thread — its latest entry, entry count, and time
// @Description span — ordered by latest activity. Entries without a session id
// @Description one summary per thread — its latest matching entry and complete
// @Description request count — ordered by latest activity. Entries without a session id
// @Description appear as single-entry threads. Filters apply to entries before
// @Description grouping.
// @Tags admin
Expand Down Expand Up @@ -264,11 +264,8 @@ func (h *Handler) AuditSessions(c *echo.Context) error {
// same slim payload contract as /admin/audit/log.
slimAuditListEntry(&enriched.Entries[i])
response.Sessions[i] = auditSessionResponse{
SessionID: session.SessionID,
Count: session.Count,
FirstTimestamp: session.FirstTimestamp,
LastTimestamp: session.LastTimestamp,
Latest: enriched.Entries[i],
RequestCount: session.RequestCount,
Comment thread
SantiagoDePolonia marked this conversation as resolved.
Latest: enriched.Entries[i],
}
}
return c.JSON(http.StatusOK, response)
Expand Down
27 changes: 10 additions & 17 deletions internal/admin/handler_audit_sessions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ func TestAuditSessions_Success(t *testing.T) {
sessionsResult: &auditlog.SessionListResult{
Sessions: []auditlog.SessionSummary{
{
SessionID: "sess-a",
Count: 3,
FirstTimestamp: now.Add(-time.Minute),
LastTimestamp: now,
SessionID: "sess-a",
RequestCount: 6,
Latest: auditlog.LogEntry{
ID: "log-3",
Timestamp: now,
Expand All @@ -51,9 +49,7 @@ func TestAuditSessions_Success(t *testing.T) {
},
},
{
Count: 1,
FirstTimestamp: now.Add(-time.Hour),
LastTimestamp: now.Add(-time.Hour),
RequestCount: 1,
Latest: auditlog.LogEntry{
ID: "log-1",
Timestamp: now.Add(-time.Hour),
Expand Down Expand Up @@ -83,9 +79,8 @@ func TestAuditSessions_Success(t *testing.T) {

var result struct {
Sessions []struct {
SessionID string `json:"session_id"`
Count int `json:"count"`
Latest *auditlog.LogEntry `json:"latest"`
RequestCount int `json:"request_count"`
Latest *auditlog.LogEntry `json:"latest"`
} `json:"sessions"`
Total int `json:"total"`
}
Expand All @@ -95,13 +90,13 @@ func TestAuditSessions_Success(t *testing.T) {
if result.Total != 2 || len(result.Sessions) != 2 {
t.Fatalf("total=%d sessions=%d, want 2/2", result.Total, len(result.Sessions))
}
if result.Sessions[0].SessionID != "sess-a" || result.Sessions[0].Count != 3 {
if result.Sessions[0].RequestCount != 6 {
t.Errorf("first session = %+v", result.Sessions[0])
}
if result.Sessions[0].Latest == nil || result.Sessions[0].Latest.ID != "log-3" {
t.Errorf("latest entry not embedded: %+v", result.Sessions[0].Latest)
}
if result.Sessions[1].SessionID != "" || result.Sessions[1].Count != 1 {
if result.Sessions[1].RequestCount != 1 {
t.Errorf("singleton thread = %+v", result.Sessions[1])
}
}
Expand All @@ -112,11 +107,9 @@ func TestAuditSessions_SlimsLatestEntries(t *testing.T) {
reader := &mockAuditReader{
sessionsResult: &auditlog.SessionListResult{
Sessions: []auditlog.SessionSummary{{
SessionID: "sess-a",
Count: 2,
FirstTimestamp: entry.Timestamp.Add(-time.Minute),
LastTimestamp: entry.Timestamp,
Latest: entry,
SessionID: "sess-a",
RequestCount: 2,
Latest: entry,
}},
Total: 1,
Limit: 25,
Expand Down
Loading