e74cb454c8
Turn the rule_run_actions ledger into operator intelligence. A new ActivityService rolls the ledger up by time window (24h / 7d / all-time) into syncs, operations and removals with success/failure counts and the number of distinct objects affected, plus all-time totals per action type and a most-active-rules ranking. A companion filtered, paginated feed answers the operational questions directly: what action ran, what happened, to which object, triggered by whom, and when. Backend: - ActivityService.Summary() and ListActions(filter) over the joined rule_run_actions / rule_runs / rules tables, categorising each action type as Sync (AddToGroup/AddGroupToGroup), Removal (RemoveFromGroupIfNoLongerMatched) or Operation (everything else). - GET /api/v1/activity/summary and GET /api/v1/activity (category, actionType, status, ruleId, search, pagination). - Table-driven test seeding a run with mixed action types/statuses and asserting window roll-ups, distinct-object counts, top rules, and the category/status feed filters. Frontend: - New Activity page: window summary cards, by-action-type and most-active-rules tables, and a drill-in feed with category/result/ object filters and a detail dialog that pretty-prints the action's details JSON and error. - Wired into the vertical sidebar and horizontal navbar under Automation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
54 lines
1.7 KiB
Go
54 lines
1.7 KiB
Go
// Package api - activity intelligence handlers
|
|
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/Grace-Solutions/OrchestrAD/internal/logging"
|
|
"github.com/Grace-Solutions/OrchestrAD/internal/services"
|
|
)
|
|
|
|
// ActivityHandler serves aggregated action intelligence and the drill-in feed.
|
|
type ActivityHandler struct {
|
|
service *services.ActivityService
|
|
logger *logging.Logger
|
|
}
|
|
|
|
// NewActivityHandler creates a new ActivityHandler.
|
|
func NewActivityHandler(service *services.ActivityService, logger *logging.Logger) *ActivityHandler {
|
|
return &ActivityHandler{service: service, logger: logger}
|
|
}
|
|
|
|
// Summary handles GET /api/v1/activity/summary
|
|
func (h *ActivityHandler) Summary(w http.ResponseWriter, r *http.Request) {
|
|
summary, err := h.service.Summary()
|
|
if err != nil {
|
|
h.logger.Error("ActivityHandler", "Summary failed: %v", err)
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to compute activity summary")
|
|
return
|
|
}
|
|
WriteJSON(w, http.StatusOK, summary)
|
|
}
|
|
|
|
// List handles GET /api/v1/activity — the filtered, paginated action feed.
|
|
func (h *ActivityHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
p := ParsePagination(r)
|
|
q := r.URL.Query()
|
|
filter := services.ActivityFilter{
|
|
Category: q.Get("category"),
|
|
ActionType: q.Get("actionType"),
|
|
Status: q.Get("status"),
|
|
RuleID: q.Get("ruleId"),
|
|
Search: q.Get("search"),
|
|
Offset: (p.Page - 1) * p.PageSize,
|
|
Limit: p.PageSize,
|
|
}
|
|
records, total, err := h.service.ListActions(filter)
|
|
if err != nil {
|
|
h.logger.Error("ActivityHandler", "List failed: %v", err)
|
|
WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to list activity")
|
|
return
|
|
}
|
|
WriteList(w, records, p.Page, p.PageSize, total)
|
|
}
|