@@ -596,3 +596,93 @@ func Health(w http.ResponseWriter, r *http.Request) {
596596 w .WriteHeader (http .StatusOK )
597597 fmt .Fprintf (w , `{"status":"ok"}` )
598598}
599+
600+ // AnalyticsHandlers handles analytics endpoints
601+ type AnalyticsHandlers struct {
602+ store * store.Store
603+ }
604+
605+ // NewAnalyticsHandlers creates analytics handlers
606+ func NewAnalyticsHandlers (st * store.Store ) * AnalyticsHandlers {
607+ return & AnalyticsHandlers {store : st }
608+ }
609+
610+ // GetExecutionTrends handles GET /api/analytics/execution-trends
611+ func (h * AnalyticsHandlers ) GetExecutionTrends (w http.ResponseWriter , r * http.Request ) {
612+ daysStr := r .URL .Query ().Get ("days" )
613+ days := 30 // default
614+ if d , err := strconv .Atoi (daysStr ); err == nil && d > 0 && d <= 365 {
615+ days = d
616+ }
617+
618+ trends , err := h .store .GetExecutionTrends (days )
619+ if err != nil {
620+ WriteError (w , http .StatusInternalServerError , "Failed to get execution trends" , "INTERNAL_ERROR" )
621+ return
622+ }
623+
624+ WriteJSON (w , http .StatusOK , map [string ]interface {}{
625+ "trends" : trends ,
626+ "days" : days ,
627+ })
628+ }
629+
630+ // GetJobStats handles GET /api/analytics/job-stats
631+ func (h * AnalyticsHandlers ) GetJobStats (w http.ResponseWriter , r * http.Request ) {
632+ stats , err := h .store .GetJobStats ()
633+ if err != nil {
634+ WriteError (w , http .StatusInternalServerError , "Failed to get job stats" , "INTERNAL_ERROR" )
635+ return
636+ }
637+
638+ WriteJSON (w , http .StatusOK , map [string ]interface {}{
639+ "jobs" : stats ,
640+ "total" : len (stats ),
641+ })
642+ }
643+
644+ // GetJobDurationTrends handles GET /api/analytics/jobs/{id}/duration-trends
645+ func (h * AnalyticsHandlers ) GetJobDurationTrends (w http.ResponseWriter , r * http.Request ) {
646+ jobID := r .PathValue ("id" )
647+ if jobID == "" {
648+ WriteError (w , http .StatusBadRequest , "Job ID is required" , "INVALID_ID" )
649+ return
650+ }
651+
652+ daysStr := r .URL .Query ().Get ("days" )
653+ days := 30 // default
654+ if d , err := strconv .Atoi (daysStr ); err == nil && d > 0 && d <= 365 {
655+ days = d
656+ }
657+
658+ trends , err := h .store .GetJobDurationTrends (jobID , days )
659+ if err != nil {
660+ WriteError (w , http .StatusInternalServerError , "Failed to get duration trends" , "INTERNAL_ERROR" )
661+ return
662+ }
663+
664+ // Get job name
665+ job , _ := h .store .GetJob (jobID )
666+ jobName := ""
667+ if job != nil {
668+ jobName = job .Name
669+ }
670+
671+ WriteJSON (w , http .StatusOK , map [string ]interface {}{
672+ "job_id" : jobID ,
673+ "job_name" : jobName ,
674+ "trends" : trends ,
675+ "days" : days ,
676+ })
677+ }
678+
679+ // GetOverallStats handles GET /api/analytics/overview
680+ func (h * AnalyticsHandlers ) GetOverallStats (w http.ResponseWriter , r * http.Request ) {
681+ stats , err := h .store .GetOverallStats ()
682+ if err != nil {
683+ WriteError (w , http .StatusInternalServerError , "Failed to get overall stats" , "INTERNAL_ERROR" )
684+ return
685+ }
686+
687+ WriteJSON (w , http .StatusOK , stats )
688+ }
0 commit comments