add telemetry system with analytics and crash reporting (M08)
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/omixlab/mosis-portal/internal/database"
|
||||
"github.com/omixlab/mosis-portal/internal/review"
|
||||
"github.com/omixlab/mosis-portal/internal/storage"
|
||||
"github.com/omixlab/mosis-portal/internal/telemetry"
|
||||
)
|
||||
|
||||
// Handler handles web page requests
|
||||
@@ -16,6 +17,7 @@ type Handler struct {
|
||||
templates *Templates
|
||||
store *storage.Storage
|
||||
review *review.Service
|
||||
telemetry *telemetry.Service
|
||||
}
|
||||
|
||||
// NewHandler creates a new web handler
|
||||
@@ -36,6 +38,11 @@ func (h *Handler) SetStorage(store *storage.Storage) {
|
||||
h.store = store
|
||||
}
|
||||
|
||||
// SetTelemetry sets the telemetry service for the handler
|
||||
func (h *Handler) SetTelemetry(ts *telemetry.Service) {
|
||||
h.telemetry = ts
|
||||
}
|
||||
|
||||
// PageData is the base data structure for all pages
|
||||
type PageData struct {
|
||||
Title string
|
||||
@@ -436,3 +443,141 @@ func (h *Handler) AdminValidate(w http.ResponseWriter, r *http.Request) {
|
||||
// Render validation results partial
|
||||
h.renderPartial(w, "validation_results", result)
|
||||
}
|
||||
|
||||
// AppAnalytics renders the app analytics page
|
||||
func (h *Handler) AppAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||
developer := getDeveloperFromContext(r)
|
||||
if developer == nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
appID := chi.URLParam(r, "appID")
|
||||
app, err := h.db.GetApp(r.Context(), appID)
|
||||
if err != nil {
|
||||
http.Error(w, "App not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
if app.DeveloperID != developer.ID {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Get days parameter
|
||||
days := 30
|
||||
if d := r.URL.Query().Get("days"); d != "" {
|
||||
if parsed, err := strconv.Atoi(d); err == nil && parsed > 0 && parsed <= 365 {
|
||||
days = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// Get analytics data
|
||||
var overview *telemetry.AnalyticsOverview
|
||||
var eventStats []telemetry.DailyStats
|
||||
var crashes []*telemetry.CrashGroup
|
||||
|
||||
if h.telemetry != nil {
|
||||
overview, _ = h.telemetry.GetAnalyticsOverview(r.Context(), appID, days)
|
||||
eventStats, _ = h.telemetry.GetDailyStats(r.Context(), appID, "", days)
|
||||
crashes, _, _ = h.telemetry.GetCrashGroups(r.Context(), appID, "open", 5, 0)
|
||||
}
|
||||
|
||||
if overview == nil {
|
||||
overview = &telemetry.AnalyticsOverview{}
|
||||
}
|
||||
|
||||
data := struct {
|
||||
PageData
|
||||
App *database.App
|
||||
Days int
|
||||
Overview *telemetry.AnalyticsOverview
|
||||
EventStats []telemetry.DailyStats
|
||||
Crashes []*telemetry.CrashGroup
|
||||
}{
|
||||
PageData: PageData{
|
||||
Title: app.Name + " - Analytics",
|
||||
ActiveNav: "apps",
|
||||
Developer: developer,
|
||||
},
|
||||
App: app,
|
||||
Days: days,
|
||||
Overview: overview,
|
||||
EventStats: eventStats,
|
||||
Crashes: crashes,
|
||||
}
|
||||
|
||||
h.render(w, "app_analytics", data)
|
||||
}
|
||||
|
||||
// AppCrashes renders the app crashes page
|
||||
func (h *Handler) AppCrashes(w http.ResponseWriter, r *http.Request) {
|
||||
developer := getDeveloperFromContext(r)
|
||||
if developer == nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
appID := chi.URLParam(r, "appID")
|
||||
app, err := h.db.GetApp(r.Context(), appID)
|
||||
if err != nil {
|
||||
http.Error(w, "App not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
if app.DeveloperID != developer.ID {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Get status filter
|
||||
status := r.URL.Query().Get("status")
|
||||
if status == "" {
|
||||
status = "open"
|
||||
}
|
||||
|
||||
// Pagination
|
||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
limit := 20
|
||||
offset := (page - 1) * limit
|
||||
|
||||
// Get crashes
|
||||
var crashes []*telemetry.CrashGroup
|
||||
var total int
|
||||
if h.telemetry != nil {
|
||||
crashes, total, _ = h.telemetry.GetCrashGroups(r.Context(), appID, status, limit, offset)
|
||||
}
|
||||
|
||||
data := struct {
|
||||
PageData
|
||||
App *database.App
|
||||
Status string
|
||||
Crashes []*telemetry.CrashGroup
|
||||
Pagination struct {
|
||||
Page int
|
||||
Limit int
|
||||
Total int
|
||||
TotalPages int
|
||||
}
|
||||
}{
|
||||
PageData: PageData{
|
||||
Title: app.Name + " - Crashes",
|
||||
ActiveNav: "apps",
|
||||
Developer: developer,
|
||||
},
|
||||
App: app,
|
||||
Status: status,
|
||||
Crashes: crashes,
|
||||
}
|
||||
data.Pagination.Page = page
|
||||
data.Pagination.Limit = limit
|
||||
data.Pagination.Total = total
|
||||
data.Pagination.TotalPages = (total + limit - 1) / limit
|
||||
|
||||
h.render(w, "app_crashes", data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user