Metrics and logs - MongoDB Submission (#43)

Adds MongoDB-backed submission for survey statistics/metrics and introduces MongoDB ObjectID-based participant/task identifiers to support upserts and linking metrics to a participant.

It introduces breaking changes to the exsisting json stat and metric files. Delete the old ones

Changes:

Add internal/storage/MongoStorage and wire pm survey submit / pm start to submit local .pm/*.json files to MongoDB.
Extend stats/metrics models to include MongoDB _id / participant_id fields and add participant ID propagation into task metrics.
Update app initialization to generate ObjectIDs for new participants and expose GetParticipantID() via StatsService.
This commit is contained in:
Robin Olsen
2026-03-10 06:09:31 -07:00
committed by GitHub
parent 549415135c
commit 72a2e8acf5
17 changed files with 426 additions and 197 deletions

View File

@@ -5,10 +5,13 @@ import (
"errors"
"fmt"
"math/rand"
"time"
"github.com/LazyBachelor/LazyPM/cmd/pm/tasks"
"github.com/LazyBachelor/LazyPM/internal/commands/survey"
"github.com/LazyBachelor/LazyPM/internal/storage"
"github.com/LazyBachelor/LazyPM/pkg/task"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
)
@@ -28,6 +31,79 @@ func runStartCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("application services are not available")
}
if !cmd.Flags().Changed("dev") {
var mongoStorage *storage.MongoStorage
var continueWithoutSubmitting bool
for {
if app.Config.DbUri == "" {
cmd.Println("No database URI provided in environment, survey responses will not be submitted.")
break
}
db, err := storage.NewMongoStorageInteractive(cmd.Context(), app.Config.DbUri)
if err == nil {
mongoStorage = db
break
}
cmd.Println("Failed to connect to database, survey responses will not be submitted.")
if err := huh.NewConfirm().
Title("Do you want to continue without submitting your responses?").
Description("You can fix your database connection and submit your responses later with the submit command.").
Value(&continueWithoutSubmitting).
WithTheme(huh.ThemeBase16()).
RunAccessible(cmd.OutOrStdout(), cmd.InOrStdin()); err != nil {
return fmt.Errorf("failed to read user input: %w", err)
}
if continueWithoutSubmitting {
break
}
}
if mongoStorage != nil {
cmd.Println("Connected to Database Successfully. Starting survey...")
time.Sleep(2 * time.Second)
defer mongoStorage.Close()
ctx := cmd.Context()
go func() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := mongoStorage.SubmitSurveyResponsesCmd(ctx, app.Config.AppDir); err != nil {
cmd.Printf("Failed to submit survey responses: %v\n", err)
}
}
}
}()
if err := mongoStorage.SubmitSurveyResponsesCmd(ctx, app.Config.AppDir); err != nil {
cmd.Printf("Failed to submit survey responses: %v\n", err)
}
defer func() {
if err := mongoStorage.SubmitSurveyResponsesCmd(context.Background(), app.Config.AppDir); err != nil {
cmd.Printf("Failed to submit survey responses on shutdown: %v\n", err)
}
}()
} else {
cmd.Println("Starting survey without database connection. Your responses will not be submitted...")
time.Sleep(2 * time.Second)
}
}
interfaces := initInterfaces()
surveyTasks := initTasks(app)
@@ -49,11 +125,11 @@ func runStartCmd(cmd *cobra.Command, args []string) error {
survey.Task: surveyTasks[survey.Task],
}
}
if err := newIntroModel().Run(); err != nil {
return returnIfUserQuit(err, "failed to run intro")
if !cmd.Flags().Changed("dev") {
if err := newIntroModel().Run(); err != nil {
return returnIfUserQuit(err, "failed to run intro")
}
}
if err := taskLoop(cmd.Context(), app, surveyTasks, interfaces); err != nil {
return returnIfUserQuit(err, "task loop failed")
}