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:
@@ -26,11 +26,6 @@ Your responses will be kept confidential and used solely for research purposes.`
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "survey",
|
||||
Long: long,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
if app != nil {
|
||||
cmd.SetContext(context.WithValue(cmd.Context(), appKey, app))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func SetApp(application *App) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
DevFlag bool
|
||||
InterfaceType string
|
||||
Task string
|
||||
)
|
||||
@@ -22,4 +23,5 @@ func init() {
|
||||
StartCmd.Flags().StringVarP(&InterfaceType, "interface", "i", "", "Specify interface.")
|
||||
StartCmd.RegisterFlagCompletionFunc("task", shellcomp.CompletionFunc(task.ListTasks()))
|
||||
StartCmd.RegisterFlagCompletionFunc("interface", shellcomp.CompletionFunc(task.ListInterfaces()))
|
||||
StartCmd.Flags().BoolVar(&DevFlag, "dev", false, "Enable development mode, which skips database connection, submission and intro")
|
||||
}
|
||||
|
||||
@@ -1,123 +1,39 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/LazyBachelor/LazyPM/internal/models"
|
||||
"github.com/LazyBachelor/LazyPM/internal/storage"
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
var mongoURI = os.Getenv("MONGODB_URI")
|
||||
|
||||
var SubmitCmd = &cobra.Command{
|
||||
Use: "submit",
|
||||
Short: "Submit your survey responses",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client, error := mongo.Connect(cmd.Context(), options.Client().ApplyURI(mongoURI))
|
||||
if error != nil {
|
||||
return fmt.Errorf("Failed to connect to MongoDB: %v", error)
|
||||
app := AppFromContext(cmd.Context())
|
||||
|
||||
if app == nil {
|
||||
return fmt.Errorf("application context not initialized")
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := client.Disconnect(cmd.Context()); err != nil {
|
||||
fmt.Printf("Failed to disconnect MongoDB client: %v", err)
|
||||
}
|
||||
}()
|
||||
if app.Config.DbUri == "" {
|
||||
cmd.Println("No database URI provided in environment, survey responses will not be submitted.")
|
||||
return nil
|
||||
}
|
||||
|
||||
userStatscollection := client.Database("Responses").Collection("stats")
|
||||
taskMetricsCollection := client.Database("Responses").Collection("task_metrics")
|
||||
|
||||
pmDir := "./.pm/"
|
||||
|
||||
entries, err := os.ReadDir(pmDir)
|
||||
db, err := storage.NewMongoStorageInteractive(cmd.Context(), app.Config.DbUri)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read .pm directory: %v", err)
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
return fmt.Errorf("No files found in .pm directory")
|
||||
defer db.Close()
|
||||
|
||||
if err := db.SubmitSurveyResponsesCmd(cmd.Context(), app.Config.AppDir); err != nil {
|
||||
return fmt.Errorf("failed to submit survey responses: %w", err)
|
||||
}
|
||||
|
||||
statFile := pmDir + "stats.json"
|
||||
if _, err := os.Stat(statFile); os.IsNotExist(err) {
|
||||
return fmt.Errorf("stats.json not found in .pm directory")
|
||||
}
|
||||
|
||||
stats, err := getStats(statFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read stats.json: %v", err)
|
||||
}
|
||||
|
||||
_, err = userStatscollection.InsertOne(cmd.Context(), stats)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert stats into database: %v", err)
|
||||
}
|
||||
|
||||
metricFiles := []string{}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if entry.Name() == "stats.json" {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasSuffix(entry.Name(), "-stats.json") {
|
||||
metricFiles = append(metricFiles, pmDir+entry.Name())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range metricFiles {
|
||||
metrics, err := getTaskMetrics(file)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to read metrics from %s: %v", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = taskMetricsCollection.InsertOne(cmd.Context(), metrics)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to insert metrics from %s: %v", file, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Printf("Successfully submitted survey responses and metrics to the database")
|
||||
|
||||
cmd.Println("Successfully submitted survey responses and metrics to the database")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func getStats(file string) (*models.Statistics, error) {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stats models.Statistics
|
||||
if err := json.Unmarshal(data, &stats); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
func getTaskMetrics(file string) (*models.TaskMetricsFile, error) {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var metrics models.TaskMetricsFile
|
||||
if err := json.Unmarshal(data, &metrics); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &metrics, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user