From 80d9f4eb6d5745901d65c2ccba3530bdd93963c0 Mon Sep 17 00:00:00 2001 From: motatoes Date: Mon, 6 Oct 2025 16:45:06 -0700 Subject: [PATCH 1/3] segment --- backend/controllers/github_callback.go | 22 +++++++++--- backend/controllers/github_comment.go | 36 ++++++++++++++----- backend/controllers/github_pull_request.go | 41 ++++++++++++++++++---- backend/controllers/github_test.go | 2 +- backend/controllers/internal_users.go | 24 +++++++------ backend/controllers/orgs.go | 2 +- backend/controllers/projects.go | 11 ++++++ backend/go.sum | 2 ++ backend/middleware/jwt.go | 5 --- backend/migrations/20251006225238.sql | 2 ++ backend/migrations/atlas.sum | 3 +- backend/models/orgs.go | 1 + backend/models/scheduler_test.go | 2 +- backend/models/setup.go | 4 +-- backend/models/storage.go | 7 ++-- backend/models/storage_test.go | 2 +- backend/segment/segment.go | 32 ++++++++++++++--- backend/tasks/runs_test.go | 2 +- ee/backend/controllers/bitbucket.go | 3 -- ee/backend/controllers/gitlab.go | 5 --- 20 files changed, 149 insertions(+), 59 deletions(-) create mode 100644 backend/migrations/20251006225238.sql diff --git a/backend/controllers/github_callback.go b/backend/controllers/github_callback.go index fdebb9bcf..04c8edef9 100644 --- a/backend/controllers/github_callback.go +++ b/backend/controllers/github_callback.go @@ -2,15 +2,17 @@ package controllers import ( "fmt" + "log/slog" + "net/http" + "strconv" + "strings" + "github.com/diggerhq/digger/backend/models" + "github.com/diggerhq/digger/backend/segment" "github.com/diggerhq/digger/backend/utils" "github.com/diggerhq/digger/libs/ci/github" "github.com/gin-gonic/gin" "github.com/google/uuid" - "log/slog" - "net/http" - "strconv" - "strings" ) func (d DiggerController) GithubAppCallbackPage(c *gin.Context) { @@ -111,7 +113,7 @@ func (d DiggerController) GithubAppCallbackPage(c *gin.Context) { "externalId", externalId, ) - org, err := models.DB.CreateOrganisation(name, "digger", externalId) + org, err := models.DB.CreateOrganisation(name, "digger", externalId, nil) if err != nil { slog.Error("Error creating organization", "name", name, @@ -151,6 +153,16 @@ func (d DiggerController) GithubAppCallbackPage(c *gin.Context) { org := link.Organisation orgId := link.OrganisationId + var vcsUser string = "" + if installation.Account.Email != nil { + vcsUser = *installation.Account.Email + } else if installation.Account.Login != nil { + vcsUser = *installation.Account.Login + } else { + + } + segment.Track(*org, "", vcsUser, "github", "vcs_repo_installed", map[string]string{}) + // create a github installation link (org ID matched to installation ID) _, err = models.DB.CreateGithubInstallationLink(org, installationId64) if err != nil { diff --git a/backend/controllers/github_comment.go b/backend/controllers/github_comment.go index 9376e7ac1..a5c212c6a 100644 --- a/backend/controllers/github_comment.go +++ b/backend/controllers/github_comment.go @@ -3,6 +3,12 @@ package controllers import ( "encoding/json" "fmt" + "log/slog" + "os" + "runtime/debug" + "strconv" + "strings" + "github.com/diggerhq/digger/backend/ci_backends" config2 "github.com/diggerhq/digger/backend/config" locking2 "github.com/diggerhq/digger/backend/locking" @@ -18,11 +24,6 @@ import ( "github.com/diggerhq/digger/libs/scheduler" "github.com/google/go-github/v61/github" "github.com/samber/lo" - "log/slog" - "os" - "runtime/debug" - "strconv" - "strings" ) func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.IssueCommentEvent, ciBackendProvider ci_backends.CiBackendProvider, appId int64, postCommentHooks []IssueCommentHook) error { @@ -55,6 +56,12 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu isDraft := payload.Issue.GetDraft() userCommentId := *payload.GetComment().ID actor := *payload.Sender.Login + var vcsActorID string = "" + if payload.Sender != nil && payload.Sender.Email != nil { + vcsActorID = *payload.Sender.Email + } else if payload.Sender != nil && payload.Sender.Login != nil { + vcsActorID = *payload.Sender.Login + } commentBody := *payload.Comment.Body defaultBranch := *payload.Repo.DefaultBranch isPullRequest := payload.Issue.IsPullRequest() @@ -145,6 +152,15 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu "orgId", orgId, ) + org, err := models.DB.GetOrganisationById(orgId) + if err != nil || org == nil { + slog.Error("Error getting organisation", + "orgId", orgId, + "error", err) + commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to get organisation by ID")) + return fmt.Errorf("error getting organisation") + } + diggerYmlStr, ghService, config, projectsGraph, prSourceBranch, commitSha, changedFiles, err := getDiggerConfigForPR(gh, orgId, prLabelsStr, installationId, repoFullName, repoOwner, repoName, cloneURL, issueNumber) if err != nil { slog.Error("Error getting Digger config for PR", @@ -445,6 +461,9 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu return nil } + // If we reach here then we have created a comment that would have led to more events + segment.Track(*org, repoOwner, vcsActorID, "github", "issue_digger_comment", map[string]string{"comment": commentBody}) + err = utils.SetPRStatusForJobs(ghService, issueNumber, jobs) if err != nil { slog.Error("Error setting status for PR", @@ -583,12 +602,9 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu commentReporterManager.UpdateComment(fmt.Sprintf(":x: UpdateDiggerBatch error: %v", err)) return fmt.Errorf("error updating digger batch") } - slog.Debug("Successfully updated batch with source details", "batchId", batchId) } - segment.Track(strconv.Itoa(int(orgId)), "backend_trigger_job") - slog.Info("Getting CI backend", "issueNumber", issueNumber, "repoFullName", repoFullName, @@ -614,6 +630,10 @@ func handleIssueCommentEvent(gh utils.GithubClientProvider, payload *github.Issu return fmt.Errorf("error fetching ci backed %v", err) } + segment.Track(*org, repoOwner, vcsActorID, "github", "backend_trigger_job", map[string]string{ + "comment": commentBody, + }) + slog.Info("Triggering Digger jobs", "issueNumber", issueNumber, "batchId", batchId, diff --git a/backend/controllers/github_pull_request.go b/backend/controllers/github_pull_request.go index 198b5b665..65df2d3b3 100644 --- a/backend/controllers/github_pull_request.go +++ b/backend/controllers/github_pull_request.go @@ -3,6 +3,13 @@ package controllers import ( "encoding/json" "fmt" + "log/slog" + "os" + "runtime/debug" + "slices" + "strconv" + "time" + "github.com/diggerhq/digger/backend/ci_backends" config2 "github.com/diggerhq/digger/backend/config" locking2 "github.com/diggerhq/digger/backend/locking" @@ -16,12 +23,6 @@ import ( "github.com/diggerhq/digger/libs/scheduler" "github.com/google/go-github/v61/github" "github.com/samber/lo" - "log/slog" - "os" - "runtime/debug" - "slices" - "strconv" - "time" ) func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullRequestEvent, ciBackendProvider ci_backends.CiBackendProvider, appId int64) error { @@ -49,6 +50,12 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR branch := payload.PullRequest.Head.GetRef() action := *payload.Action labels := payload.PullRequest.Labels + var vcsActorId string = "" + if payload.Sender != nil && payload.Sender.Email != nil { + vcsActorId = *payload.Sender.Email + } else if payload.Sender != nil && payload.Sender.Login != nil { + vcsActorId = *payload.Sender.Login + } prLabelsStr := lo.Map(labels, func(label *github.Label, i int) string { return *label.Name }) @@ -141,6 +148,15 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR } } + org, err := models.DB.GetOrganisationById(organisationId) + if err != nil || org == nil { + slog.Error("Error getting organisation", + "orgId", organisationId, + "error", err) + commentReporterManager.UpdateComment(fmt.Sprintf(":x: Failed to get organisation by ID")) + return fmt.Errorf("error getting organisation") + } + diggerYmlStr, ghService, config, projectsGraph, _, _, changedFiles, err := getDiggerConfigForPR(gh, organisationId, prLabelsStr, installationId, repoFullName, repoOwner, repoName, cloneURL, prNumber) if err != nil { slog.Error("Error getting Digger config for PR", @@ -244,6 +260,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR "commands", jobsForImpactedProjects[0].Commands, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not determine digger command from job: %v", err)) return fmt.Errorf("unknown digger command in comment %v", err) } @@ -340,12 +357,16 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR return nil } + // a pull request has led to jobs which would be triggered (ignoring closed event by here) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request", map[string]string{}) + commentReporter, err := commentReporterManager.UpdateComment(":construction_worker: Digger starting... Config loaded successfully") if err != nil { slog.Error("Error initializing comment reporter", "prNumber", prNumber, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) return fmt.Errorf("error initializing comment reporter") } @@ -355,6 +376,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR "prNumber", prNumber, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) commentReporterManager.UpdateComment(fmt.Sprintf(":x: error setting status for PR: %v", err)) return fmt.Errorf("error setting status for PR: %v", err) } @@ -415,6 +437,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR "commentId", commentReporter.CommentId, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not handle commentId: %v", err)) } @@ -427,6 +450,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR "prNumber", prNumber, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) commentReporterManager.UpdateComment(fmt.Sprintf(":x: could not post ai comment summary comment id: %v", err)) return fmt.Errorf("could not post ai summary comment: %v", err) } @@ -470,6 +494,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR "prNumber", prNumber, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) commentReporterManager.UpdateComment(fmt.Sprintf(":x: ConvertJobsToDiggerJobs error: %v", err)) return fmt.Errorf("error converting jobs") } @@ -525,7 +550,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR slog.Debug("Successfully updated batch with source details", "batchId", batchId) } - segment.Track(strconv.Itoa(int(organisationId)), "backend_trigger_job") + segment.Track(*org, repoOwner, vcsActorId, "github", "backend_trigger_job", map[string]string{}) slog.Info("Getting CI backend", "prNumber", prNumber, @@ -548,6 +573,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR "repoFullName", repoFullName, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) commentReporterManager.UpdateComment(fmt.Sprintf(":x: GetCiBackend error: %v", err)) return fmt.Errorf("error fetching ci backed %v", err) } @@ -565,6 +591,7 @@ func handlePullRequestEvent(gh utils.GithubClientProvider, payload *github.PullR "batchId", batchId, "error", err, ) + segment.Track(*org, repoOwner, vcsActorId, "github", "pull_request_ERROR", map[string]string{"error": err.Error()}) commentReporterManager.UpdateComment(fmt.Sprintf(":x: TriggerDiggerJobs error: %v", err)) return fmt.Errorf("error triggering Digger Jobs") } diff --git a/backend/controllers/github_test.go b/backend/controllers/github_test.go index 315c6503c..5f7219d85 100644 --- a/backend/controllers/github_test.go +++ b/backend/controllers/github_test.go @@ -600,7 +600,7 @@ func setupSuite(tb testing.TB) (func(tb testing.TB), *models.Database) { orgTenantId := "11111111-1111-1111-1111-111111111111" externalSource := "test" orgName := "testOrg" - org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId) + org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId, nil) if err != nil { panic(err) } diff --git a/backend/controllers/internal_users.go b/backend/controllers/internal_users.go index 782cd49ec..866a478cb 100644 --- a/backend/controllers/internal_users.go +++ b/backend/controllers/internal_users.go @@ -5,14 +5,16 @@ import ( "net/http" "github.com/diggerhq/digger/backend/models" + "github.com/diggerhq/digger/backend/segment" "github.com/gin-gonic/gin" ) func (d DiggerController) UpsertOrgInternal(c *gin.Context) { type OrgCreateRequest struct { - Name string `json:"org_name"` - ExternalSource string `json:"external_source"` - ExternalId string `json:"external_id"` + Name string `json:"org_name"` + ExternalSource string `json:"external_source"` + ExternalId string `json:"external_id"` + AdminEmail *string `json:"admin_email,omitempty"` } var request OrgCreateRequest @@ -26,6 +28,7 @@ func (d DiggerController) UpsertOrgInternal(c *gin.Context) { name := request.Name externalSource := request.ExternalSource externalId := request.ExternalId + adminEmail := request.AdminEmail slog.Info("Creating or updating organization", "name", name, @@ -42,13 +45,12 @@ func (d DiggerController) UpsertOrgInternal(c *gin.Context) { if org == nil { slog.Info("Organization not found, creating new one", "externalId", externalId) - org, err = models.DB.CreateOrganisation(name, externalSource, externalId) - } - - if err != nil { - slog.Error("Error creating org", "name", name, "externalId", externalId, "error", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating org"}) - return + org, err = models.DB.CreateOrganisation(name, externalSource, externalId, adminEmail) + if err != nil { + slog.Error("Error creating org", "name", name, "externalId", externalId, "error", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Error creating org"}) + return + } } slog.Info("Successfully upserted organization", "orgId", org.ID, "externalId", externalId) @@ -102,6 +104,8 @@ func (d DiggerController) CreateUserInternal(c *gin.Context) { return } + segment.IdentifyClient(userEmail, "", userEmail, userEmail, org.Name, org.ExternalId, string(org.BillingPlan)) + slog.Info("Successfully created user", "userId", user.ID, "email", userEmail, "orgId", org.ID) c.JSON(http.StatusOK, gin.H{"status": "success", "user_id": user.ID}) } diff --git a/backend/controllers/orgs.go b/backend/controllers/orgs.go index 516079ccc..02913e1a3 100644 --- a/backend/controllers/orgs.go +++ b/backend/controllers/orgs.go @@ -184,7 +184,7 @@ func AssociateTenantIdToDiggerOrg(c *gin.Context) { } if org == nil { - newOrg, err := models.DB.CreateOrganisation(nameStr, "", tenantIdStr) + newOrg, err := models.DB.CreateOrganisation(nameStr, "", tenantIdStr, nil) if err != nil { slog.Error("Failed to create organisation", "tenantId", tenantIdStr, "name", nameStr, "error", err) c.AbortWithStatus(http.StatusInternalServerError) diff --git a/backend/controllers/projects.go b/backend/controllers/projects.go index 45cf7b317..87cdf0162 100644 --- a/backend/controllers/projects.go +++ b/backend/controllers/projects.go @@ -16,6 +16,7 @@ import ( "github.com/diggerhq/digger/backend/logging" "github.com/diggerhq/digger/backend/middleware" "github.com/diggerhq/digger/backend/models" + "github.com/diggerhq/digger/backend/segment" "github.com/diggerhq/digger/backend/services" "github.com/diggerhq/digger/backend/utils" "github.com/diggerhq/digger/libs/comment_utils/reporting" @@ -655,6 +656,13 @@ func (d DiggerController) SetJobStatusForProject(c *gin.Context) { batchId := *job.BatchID + org, err := models.DB.GetOrganisationById(orgId) + if err != nil || org == nil { + slog.Error("Error getting organisation", "jobId", jobId, "error", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Error fetching organisation"}) + return + } + slog.Info("Processing job status update", "jobId", jobId, "currentStatus", job.Status, @@ -732,6 +740,8 @@ func (d DiggerController) SetJobStatusForProject(c *gin.Context) { }(c.Request.Context()) case "succeeded": + batch := job.Batch + segment.Track(*org, "", "", "github", "ci_job_successful", map[string]string{"ci_job_type": string(batch.BatchType)}) job.Status = orchestrator_scheduler.DiggerJobSucceeded job.TerraformOutput = request.TerraformOutput if request.Footprint != nil { @@ -896,6 +906,7 @@ func (d DiggerController) SetJobStatusForProject(c *gin.Context) { }(c.Request.Context()) case "failed": + segment.Track(*org, "", "", "github", "ci_job_failed", nil) job.Status = orchestrator_scheduler.DiggerJobFailed job.TerraformOutput = request.TerraformOutput err := models.DB.UpdateDiggerJob(job) diff --git a/backend/go.sum b/backend/go.sum index 36787c08f..443473b5c 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -750,6 +750,8 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4= +github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/backend/middleware/jwt.go b/backend/middleware/jwt.go index 5815f9f44..0c50fb484 100644 --- a/backend/middleware/jwt.go +++ b/backend/middleware/jwt.go @@ -5,11 +5,9 @@ import ( "log/slog" "net/http" "os" - "strconv" "strings" "github.com/diggerhq/digger/backend/models" - "github.com/diggerhq/digger/backend/segment" "github.com/diggerhq/digger/backend/services" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" @@ -36,9 +34,6 @@ func SetContextParameters(c *gin.Context, auth services.Auth, claims jwt.MapClai c.Set(ORGANISATION_ID_KEY, org.ID) - segment.GetClient() - segment.IdentifyClient(strconv.Itoa(int(org.ID)), org.Name, org.Name, org.Name, org.Name, strconv.Itoa(int(org.ID)), "") - slog.Debug("Set organisation ID in context", "orgId", org.ID) tokenType := claims["type"].(string) diff --git a/backend/migrations/20251006225238.sql b/backend/migrations/20251006225238.sql new file mode 100644 index 000000000..21e5490fd --- /dev/null +++ b/backend/migrations/20251006225238.sql @@ -0,0 +1,2 @@ +-- Modify "organisations" table +ALTER TABLE "public"."organisations" ADD COLUMN "admin_email" text NULL; diff --git a/backend/migrations/atlas.sum b/backend/migrations/atlas.sum index 669f80702..2606cb641 100644 --- a/backend/migrations/atlas.sum +++ b/backend/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:S0K8iqPq5Fov5NYALJBJVB8Scnz9f20URgSGJyeOhgA= +h1:jWoBs487iG65lDQFsl/k45k6w5yExxjs+1elkqh5xoI= 20231227132525.sql h1:43xn7XC0GoJsCnXIMczGXWis9d504FAWi4F1gViTIcw= 20240115170600.sql h1:IW8fF/8vc40+eWqP/xDK+R4K9jHJ9QBSGO6rN9LtfSA= 20240116123649.sql h1:R1JlUIgxxF6Cyob9HdtMqiKmx/BfnsctTl5rvOqssQw= @@ -65,3 +65,4 @@ h1:S0K8iqPq5Fov5NYALJBJVB8Scnz9f20URgSGJyeOhgA= 20250812035039.sql h1:OLRqsoO26JjdOABsc+QauTWV0kUuZKkx6EOPUVgpBpw= 20250907140955.sql h1:LHINhHgrPwM/Sy1UeIS4Z3iUVp6kv3/UtiGZZ5/SE8k= 20250910102133.sql h1:jBW3PuoCWZPJA8ZaXDAyRuA9LnGDQGxvL+HtjCn33DI= +20251006225238.sql h1:L581xAn5IsYt9Srf1RnJLleLIQVlgLzp7FaAChAlCJw= diff --git a/backend/models/orgs.go b/backend/models/orgs.go index 0b98bb2c1..33878664e 100644 --- a/backend/models/orgs.go +++ b/backend/models/orgs.go @@ -27,6 +27,7 @@ type Organisation struct { BillingPlan BillingPlan `gorm:"default:'free'"` BillingStripeSubscriptionId string SlackConnectChannelName *string + AdminEmail *string } type Repo struct { diff --git a/backend/models/scheduler_test.go b/backend/models/scheduler_test.go index 9c79de535..3516e2e42 100644 --- a/backend/models/scheduler_test.go +++ b/backend/models/scheduler_test.go @@ -42,7 +42,7 @@ func setupSuiteScheduler(tb testing.TB) (func(tb testing.TB), *Database) { orgTenantId := "11111111-1111-1111-1111-111111111111" externalSource := "test" orgName := "testOrg" - org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId) + org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId, nil) if err != nil { panic(err) } diff --git a/backend/models/setup.go b/backend/models/setup.go index a544e7aff..c274e8e45 100644 --- a/backend/models/setup.go +++ b/backend/models/setup.go @@ -6,9 +6,9 @@ import ( "gorm.io/driver/postgres" _ "gorm.io/driver/postgres" "gorm.io/gorm" + "log/slog" "os" "time" - "log/slog" ) type Database struct { @@ -47,7 +47,7 @@ func ConnectDatabase() { orgNumberOne, err := DB.GetOrganisation(DEFAULT_ORG_NAME) if orgNumberOne == nil { slog.Info("No default organization found, creating default organisation", "name", DEFAULT_ORG_NAME) - _, err := DB.CreateOrganisation("digger", "", DEFAULT_ORG_NAME) + _, err := DB.CreateOrganisation("digger", "", DEFAULT_ORG_NAME, nil) if err != nil { slog.Error("Failed to create default organization", "error", err) } diff --git a/backend/models/storage.go b/backend/models/storage.go index a7ab2fdfe..c5e8b5a34 100644 --- a/backend/models/storage.go +++ b/backend/models/storage.go @@ -4,12 +4,13 @@ import ( "encoding/json" "errors" "fmt" - "github.com/diggerhq/digger/libs/digger_config/terragrunt/tac" "log/slog" "math" "net/http" "time" + "github.com/diggerhq/digger/libs/digger_config/terragrunt/tac" + "github.com/dchest/uniuri" "github.com/diggerhq/digger/backend/queries" configuration "github.com/diggerhq/digger/libs/digger_config" @@ -1418,8 +1419,8 @@ func (db *Database) CreateUser(email string, externalSource string, externalId s return user, nil } -func (db *Database) CreateOrganisation(name string, externalSource string, tenantId string) (*Organisation, error) { - org := &Organisation{Name: name, ExternalSource: externalSource, ExternalId: tenantId} +func (db *Database) CreateOrganisation(name string, externalSource string, tenantId string, adminEmail *string) (*Organisation, error) { + org := &Organisation{Name: name, ExternalSource: externalSource, ExternalId: tenantId, AdminEmail: adminEmail} result := db.GormDB.Save(org) if result.Error != nil { slog.Error("failed to create organisation", diff --git a/backend/models/storage_test.go b/backend/models/storage_test.go index 1f5e0ecf7..f4aca9e0e 100644 --- a/backend/models/storage_test.go +++ b/backend/models/storage_test.go @@ -47,7 +47,7 @@ func setupSuite(tb testing.TB) (func(tb testing.TB), *Database, *Organisation) { orgTenantId := "11111111-1111-1111-1111-111111111111" externalSource := "test" orgName := "testOrg" - org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId) + org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId, nil) if err != nil { panic(err) } diff --git a/backend/segment/segment.go b/backend/segment/segment.go index bf7b85e62..88aab1cbd 100644 --- a/backend/segment/segment.go +++ b/backend/segment/segment.go @@ -4,12 +4,13 @@ import ( "log/slog" "os" + "github.com/diggerhq/digger/backend/models" "github.com/segmentio/analytics-go/v3" ) var client analytics.Client = nil -func GetClient() analytics.Client { +func getClient() analytics.Client { segmentApiKey := os.Getenv("SEGMENT_API_KEY") if segmentApiKey == "" { slog.Debug("Not initializing segment because SEGMENT_API_KEY is missing") @@ -29,6 +30,7 @@ func CloseClient() { } func IdentifyClient(userId string, userFullName string, username string, email string, organisationName string, organisationId string, userPlan string) { + getClient() if client == nil { return } @@ -45,14 +47,34 @@ func IdentifyClient(userId string, userFullName string, username string, email s }) } -func Track(userId string, action string) { +func Track(orgnaisation models.Organisation, vcsOwner string, vcsUser string, vcsType string, action string, extraProps map[string]string) { + getClient() if client == nil { return } - slog.Debug("Tracking client action", "userId", userId, "action", action) + externalOrgId := orgnaisation.ExternalId + var adminEmail string + if orgnaisation.AdminEmail != nil { + adminEmail = *orgnaisation.AdminEmail + } else { + adminEmail = "UNKNOWN" + } + + props := analytics.NewProperties(). + Set("org_id", externalOrgId). + Set("vcs_user", vcsUser). + Set("vcs_owner", vcsOwner). + Set("vcs_type", vcsType) + + if extraProps != nil { + for k, v := range extraProps { + props.Set(k, v) + } + } + slog.Debug("Tracking client action", "userId", adminEmail, "action", action) client.Enqueue(analytics.Track{ Event: action, - UserId: userId, - Properties: analytics.NewProperties(), + UserId: adminEmail, + Properties: props, }) } diff --git a/backend/tasks/runs_test.go b/backend/tasks/runs_test.go index 2ec8a88d4..411608f1d 100644 --- a/backend/tasks/runs_test.go +++ b/backend/tasks/runs_test.go @@ -60,7 +60,7 @@ func setupSuite(tb testing.TB) (func(tb testing.TB), *models.Database) { orgTenantId := "11111111-1111-1111-1111-111111111111" externalSource := "test" orgName := "testOrg" - org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId) + org, err := database.CreateOrganisation(orgName, externalSource, orgTenantId, nil) if err != nil { panic(err) } diff --git a/ee/backend/controllers/bitbucket.go b/ee/backend/controllers/bitbucket.go index 73a14093e..1183b2569 100644 --- a/ee/backend/controllers/bitbucket.go +++ b/ee/backend/controllers/bitbucket.go @@ -18,7 +18,6 @@ import ( "github.com/diggerhq/digger/backend/controllers" "github.com/diggerhq/digger/backend/locking" "github.com/diggerhq/digger/backend/models" - "github.com/diggerhq/digger/backend/segment" "github.com/diggerhq/digger/backend/utils" ci_backends2 "github.com/diggerhq/digger/ee/backend/ci_backends" "github.com/diggerhq/digger/libs/ci/generic" @@ -343,8 +342,6 @@ func handleIssueCommentEventBB(bitbucketProvider utils.BitbucketProvider, payloa } } - segment.Track(strconv.Itoa(int(organisationId)), "backend_trigger_job") - // hardcoded bitbucket ci backend for this controller // TODO: making this configurable based on env variable and connection ciBackend := ci_backends2.BitbucketPipelineCI{ diff --git a/ee/backend/controllers/gitlab.go b/ee/backend/controllers/gitlab.go index ea854c2f4..5027ec036 100644 --- a/ee/backend/controllers/gitlab.go +++ b/ee/backend/controllers/gitlab.go @@ -15,7 +15,6 @@ import ( "github.com/diggerhq/digger/backend/controllers" "github.com/diggerhq/digger/backend/locking" "github.com/diggerhq/digger/backend/models" - "github.com/diggerhq/digger/backend/segment" "github.com/diggerhq/digger/backend/utils" "github.com/diggerhq/digger/libs/ci/generic" dg_github "github.com/diggerhq/digger/libs/ci/github" @@ -296,8 +295,6 @@ func handlePullRequestEvent(gitlabProvider utils.GitlabProvider, payload *gitlab return fmt.Errorf("error converting jobs") } - segment.Track(strconv.Itoa(int(organisationId)), "backend_trigger_job") - ciBackend, err := ciBackendProvider.GetCiBackend( ci_backends.CiBackendOptions{ RepoName: repoName, @@ -528,8 +525,6 @@ func handleIssueCommentEvent(gitlabProvider utils.GitlabProvider, payload *gitla } } - segment.Track(strconv.Itoa(int(organisationId)), "backend_trigger_job") - ciBackend, err := ciBackendProvider.GetCiBackend( ci_backends.CiBackendOptions{ RepoName: repoName, From a278ce65d5ecd574a9aca63db7f035b9ab6b0173 Mon Sep 17 00:00:00 2001 From: motatoes Date: Mon, 6 Oct 2025 16:48:47 -0700 Subject: [PATCH 2/3] improve coverage --- backend/controllers/github_callback.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/controllers/github_callback.go b/backend/controllers/github_callback.go index 04c8edef9..7b6d34ee3 100644 --- a/backend/controllers/github_callback.go +++ b/backend/controllers/github_callback.go @@ -153,15 +153,12 @@ func (d DiggerController) GithubAppCallbackPage(c *gin.Context) { org := link.Organisation orgId := link.OrganisationId - var vcsUser string = "" - if installation.Account.Email != nil { - vcsUser = *installation.Account.Email - } else if installation.Account.Login != nil { - vcsUser = *installation.Account.Login - } else { - + var vcsOwner string = "" + if installation.Account.Login != nil { + vcsOwner = *installation.Account.Login } - segment.Track(*org, "", vcsUser, "github", "vcs_repo_installed", map[string]string{}) + // we have multiple repos here, we don't really want to send an track event for each repo, so we just send the vcs owner + segment.Track(*org, vcsOwner, "", "github", "vcs_repo_installed", map[string]string{}) // create a github installation link (org ID matched to installation ID) _, err = models.DB.CreateGithubInstallationLink(org, installationId64) From 694960b1372131decffd0286130efd8e48fdb231 Mon Sep 17 00:00:00 2001 From: motatoes Date: Mon, 6 Oct 2025 16:53:44 -0700 Subject: [PATCH 3/3] email --- backend/controllers/internal_users.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/controllers/internal_users.go b/backend/controllers/internal_users.go index 866a478cb..c3d538a97 100644 --- a/backend/controllers/internal_users.go +++ b/backend/controllers/internal_users.go @@ -104,7 +104,7 @@ func (d DiggerController) CreateUserInternal(c *gin.Context) { return } - segment.IdentifyClient(userEmail, "", userEmail, userEmail, org.Name, org.ExternalId, string(org.BillingPlan)) + segment.IdentifyClient(userEmail, userEmail, userEmail, userEmail, org.Name, org.ExternalId, string(org.BillingPlan)) slog.Info("Successfully created user", "userId", user.ID, "email", userEmail, "orgId", org.ID) c.JSON(http.StatusOK, gin.H{"status": "success", "user_id": user.ID})