From 3a2ebee9fbfe4d89a9ad6efd7873043a0cbc3b88 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 9 Apr 2025 13:40:49 +0000 Subject: [PATCH 1/8] feat(notifications): group workspace build failure report Instead of sending X many reports to a single template admin, we instead send only 1. --- ...group_build_failure_notifications.down.sql | 0 ...6_group_build_failure_notifications.up.sql | 22 ++ coderd/notifications/notifications_test.go | 102 ++++++--- coderd/notifications/reports/generator.go | 158 ++++++++------ .../reports/generator_internal_test.go | 202 +++++++++++------- ...ateWorkspaceBuildsFailedReport.html.golden | 103 ++++++--- ...ateWorkspaceBuildsFailedReport.json.golden | 120 +++++++---- 7 files changed, 473 insertions(+), 234 deletions(-) create mode 100644 coderd/database/migrations/000316_group_build_failure_notifications.down.sql create mode 100644 coderd/database/migrations/000316_group_build_failure_notifications.up.sql diff --git a/coderd/database/migrations/000316_group_build_failure_notifications.down.sql b/coderd/database/migrations/000316_group_build_failure_notifications.down.sql new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/coderd/database/migrations/000316_group_build_failure_notifications.up.sql b/coderd/database/migrations/000316_group_build_failure_notifications.up.sql new file mode 100644 index 0000000000000..a98f1e49b7062 --- /dev/null +++ b/coderd/database/migrations/000316_group_build_failure_notifications.up.sql @@ -0,0 +1,22 @@ +UPDATE notification_templates +SET + name = 'Report: Workspace Builds Failed', + title_template = 'Failed workspace builds report', + body_template = +E'The following templates have had build failures over the last {{.Data.report_frequency}}: +{{range $template := .Data.templates}} +- **{{$template.display_name}}** failed to build {{$template.failed_builds}}/{{$template.total_builds}} times +{{end}} + +**Report:** +{{range $template := .Data.templates}} +{{range $version := $template.versions}} +**{{$template.display_name}}**@**{{$version.template_version_name}}** failed {{$version.failed_count}} time{{if gt $version.failed_count 1.0}}s{{end}}: +{{range $build := $version.failed_builds}} +* [{{$build.workspace_owner_username}} / {{$build.workspace_name}} / #{{$build.build_number}}]({{base_url}}/@{{$build.workspace_owner_username}}/{{$build.workspace_name}}/builds/{{$build.build_number}}) +{{end}} +{{end}} +{{end}} + +We recommend reviewing these issues to ensure future builds are successful.' +WHERE id = '34a20db2-e9cc-4a93-b0e4-8569699d7a00'; diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 60858f1b641b1..f294b7a40a141 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -978,45 +978,93 @@ func TestNotificationTemplates_Golden(t *testing.T) { UserName: "Bobby", UserEmail: "bobby@coder.com", UserUsername: "bobby", - Labels: map[string]string{ - "template_name": "bobby-first-template", - "template_display_name": "Bobby First Template", - }, + Labels: map[string]string{}, // We need to use floats as `json.Unmarshal` unmarshal numbers in `map[string]any` to floats. Data: map[string]any{ - "failed_builds": 4.0, - "total_builds": 55.0, "report_frequency": "week", - "template_versions": []map[string]any{ + "templates": []map[string]any{ { - "template_version_name": "bobby-template-version-1", - "failed_count": 3.0, - "failed_builds": []map[string]any{ + "name": "bobby-first-template", + "display_name": "Bobby First Template", + "failed_builds": 4.0, + "total_builds": 55.0, + "versions": []map[string]any{ { - "workspace_owner_username": "mtojek", - "workspace_name": "workspace-1", - "build_number": 1234.0, + "template_version_name": "bobby-template-version-1", + "failed_count": 3.0, + "failed_builds": []map[string]any{ + { + "workspace_owner_username": "mtojek", + "workspace_name": "workspace-1", + "build_number": 1234.0, + }, + { + "workspace_owner_username": "johndoe", + "workspace_name": "my-workspace-3", + "build_number": 5678.0, + }, + { + "workspace_owner_username": "jack", + "workspace_name": "workwork", + "build_number": 774.0, + }, + }, }, { - "workspace_owner_username": "johndoe", - "workspace_name": "my-workspace-3", - "build_number": 5678.0, - }, - { - "workspace_owner_username": "jack", - "workspace_name": "workwork", - "build_number": 774.0, + "template_version_count": "bobby-template-version-2", + "failed_count": 1.0, + "failed_builds": []map[string]any{ + { + "workspace_owner_username": "ben", + "workspace_name": "cool-workspace", + "build_number": 8888.0, + }, + }, }, }, }, { - "template_version_name": "bobby-template-version-2", - "failed_count": 1.0, - "failed_builds": []map[string]any{ + "name": "bobby-second-template", + "display_name": "Bobby Second Template", + "failed_builds": 5.0, + "total_builds": 50.0, + "versions": []map[string]any{ + { + "template_version_name": "bobby-template-version-1", + "failed_count": 3.0, + "failed_builds": []map[string]any{ + { + "workspace_owner_username": "daniellemaywood", + "workspace_name": "workspace-9", + "build_number": 9234.0, + }, + { + "workspace_owner_username": "johndoe", + "workspace_name": "my-workspace-7", + "build_number": 8678.0, + }, + { + "workspace_owner_username": "jack", + "workspace_name": "workworkwork", + "build_number": 374.0, + }, + }, + }, { - "workspace_owner_username": "ben", - "workspace_name": "cool-workspace", - "build_number": 8888.0, + "template_version_count": "bobby-template-version-2", + "failed_count": 2.0, + "failed_builds": []map[string]any{ + { + "workspace_owner_username": "ben", + "workspace_name": "more-cool-workspace", + "build_number": 8878.0, + }, + { + "workspace_owner_username": "ben", + "workspace_name": "less-cool-workspace", + "build_number": 8848.0, + }, + }, }, }, }, diff --git a/coderd/notifications/reports/generator.go b/coderd/notifications/reports/generator.go index 2424498146c60..9f001687e00aa 100644 --- a/coderd/notifications/reports/generator.go +++ b/coderd/notifications/reports/generator.go @@ -18,6 +18,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/notifications" + "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk" ) @@ -102,6 +103,11 @@ const ( failedWorkspaceBuildsReportFrequencyLabel = "week" ) +type adminReport struct { + stats database.GetWorkspaceBuildStatsByTemplatesRow + failedBuilds []database.GetFailedWorkspaceBuildsByTemplateIDRow +} + func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db database.Store, enqueuer notifications.Enqueuer, clk quartz.Clock) error { now := clk.Now() since := now.Add(-failedWorkspaceBuildsReportFrequency) @@ -136,6 +142,8 @@ func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db dat return xerrors.Errorf("unable to fetch failed workspace builds: %w", err) } + reports := make(map[uuid.UUID][]adminReport) + for _, stats := range templateStatsRows { select { case <-ctx.Done(): @@ -165,33 +173,40 @@ func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db dat logger.Error(ctx, "unable to fetch failed workspace builds", slog.F("template_id", stats.TemplateID), slog.Error(err)) continue } - reportData := buildDataForReportFailedWorkspaceBuilds(stats, failedBuilds) - // Send reports to template admins - templateDisplayName := stats.TemplateDisplayName - if templateDisplayName == "" { - templateDisplayName = stats.TemplateName + for _, templateAdmin := range templateAdmins { + adminReports := reports[templateAdmin.ID] + adminReports = append(adminReports, adminReport{ + failedBuilds: failedBuilds, + stats: stats, + }) + + reports[templateAdmin.ID] = adminReports } + } - for _, templateAdmin := range templateAdmins { - select { - case <-ctx.Done(): - logger.Debug(ctx, "context is canceled, quitting", slog.Error(ctx.Err())) - break - default: - } + for templateAdmin, reports := range reports { + select { + case <-ctx.Done(): + logger.Debug(ctx, "context is canceled, quitting", slog.Error(ctx.Err())) + break + default: + } - if _, err := enqueuer.EnqueueWithData(ctx, templateAdmin.ID, notifications.TemplateWorkspaceBuildsFailedReport, - map[string]string{ - "template_name": stats.TemplateName, - "template_display_name": templateDisplayName, - }, - reportData, - "report_generator", - stats.TemplateID, stats.TemplateOrganizationID, - ); err != nil { - logger.Warn(ctx, "failed to send a report with failed workspace builds", slog.Error(err)) - } + reportData := buildDataForReportFailedWorkspaceBuilds(reports) + + targets := []uuid.UUID{} + for _, report := range reports { + targets = append(targets, report.stats.TemplateID, report.stats.TemplateOrganizationID) + } + + if _, err := enqueuer.EnqueueWithData(ctx, templateAdmin, notifications.TemplateWorkspaceBuildsFailedReport, + map[string]string{}, + reportData, + "report_generator", + slice.Unique(targets)..., + ); err != nil { + logger.Warn(ctx, "failed to send a report with failed workspace builds", slog.Error(err)) } } @@ -213,54 +228,69 @@ func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db dat const workspaceBuildsLimitPerTemplateVersion = 10 -func buildDataForReportFailedWorkspaceBuilds(stats database.GetWorkspaceBuildStatsByTemplatesRow, failedBuilds []database.GetFailedWorkspaceBuildsByTemplateIDRow) map[string]any { - // Build notification model for template versions and failed workspace builds. - // - // Failed builds are sorted by template version ascending, workspace build number descending. - // Review builds, group them by template versions, and assign to builds to template versions. - // The map requires `[]map[string]any{}` to be compatible with data passed to `NotificationEnqueuer`. - templateVersions := []map[string]any{} - for _, failedBuild := range failedBuilds { - c := len(templateVersions) - - if c == 0 || templateVersions[c-1]["template_version_name"] != failedBuild.TemplateVersionName { - templateVersions = append(templateVersions, map[string]any{ - "template_version_name": failedBuild.TemplateVersionName, - "failed_count": 1, - "failed_builds": []map[string]any{ - { - "workspace_owner_username": failedBuild.WorkspaceOwnerUsername, - "workspace_name": failedBuild.WorkspaceName, - "build_number": failedBuild.WorkspaceBuildNumber, +func buildDataForReportFailedWorkspaceBuilds(reports []adminReport) map[string]any { + templates := []map[string]any{} + + for _, report := range reports { + // Build notification model for template versions and failed workspace builds. + // + // Failed builds are sorted by template version ascending, workspace build number descending. + // Review builds, group them by template versions, and assign to builds to template versions. + // The map requires `[]map[string]any{}` to be compatible with data passed to `NotificationEnqueuer`. + templateVersions := []map[string]any{} + for _, failedBuild := range report.failedBuilds { + c := len(templateVersions) + + if c == 0 || templateVersions[c-1]["template_version_name"] != failedBuild.TemplateVersionName { + templateVersions = append(templateVersions, map[string]any{ + "template_version_name": failedBuild.TemplateVersionName, + "failed_count": 1, + "failed_builds": []map[string]any{ + { + "workspace_owner_username": failedBuild.WorkspaceOwnerUsername, + "workspace_name": failedBuild.WorkspaceName, + "build_number": failedBuild.WorkspaceBuildNumber, + }, }, - }, - }) - continue + }) + continue + } + + tv := templateVersions[c-1] + //nolint:errorlint,forcetypeassert // only this function prepares the notification model + tv["failed_count"] = tv["failed_count"].(int) + 1 + + //nolint:errorlint,forcetypeassert // only this function prepares the notification model + builds := tv["failed_builds"].([]map[string]any) + if len(builds) < workspaceBuildsLimitPerTemplateVersion { + // return N last builds to prevent long email reports + builds = append(builds, map[string]any{ + "workspace_owner_username": failedBuild.WorkspaceOwnerUsername, + "workspace_name": failedBuild.WorkspaceName, + "build_number": failedBuild.WorkspaceBuildNumber, + }) + tv["failed_builds"] = builds + } + templateVersions[c-1] = tv } - tv := templateVersions[c-1] - //nolint:errorlint,forcetypeassert // only this function prepares the notification model - tv["failed_count"] = tv["failed_count"].(int) + 1 - - //nolint:errorlint,forcetypeassert // only this function prepares the notification model - builds := tv["failed_builds"].([]map[string]any) - if len(builds) < workspaceBuildsLimitPerTemplateVersion { - // return N last builds to prevent long email reports - builds = append(builds, map[string]any{ - "workspace_owner_username": failedBuild.WorkspaceOwnerUsername, - "workspace_name": failedBuild.WorkspaceName, - "build_number": failedBuild.WorkspaceBuildNumber, - }) - tv["failed_builds"] = builds + templateDisplayName := report.stats.TemplateDisplayName + if templateDisplayName == "" { + templateDisplayName = report.stats.TemplateName } - templateVersions[c-1] = tv + + templates = append(templates, map[string]any{ + "failed_builds": report.stats.FailedBuilds, + "total_builds": report.stats.TotalBuilds, + "versions": templateVersions, + "name": report.stats.TemplateName, + "display_name": templateDisplayName, + }) } return map[string]any{ - "failed_builds": stats.FailedBuilds, - "total_builds": stats.TotalBuilds, - "report_frequency": failedWorkspaceBuildsReportFrequencyLabel, - "template_versions": templateVersions, + "report_frequency": failedWorkspaceBuildsReportFrequencyLabel, + "templates": templates, } } diff --git a/coderd/notifications/reports/generator_internal_test.go b/coderd/notifications/reports/generator_internal_test.go index b2cc5e82aadaf..e246007d3d124 100644 --- a/coderd/notifications/reports/generator_internal_test.go +++ b/coderd/notifications/reports/generator_internal_test.go @@ -3,6 +3,7 @@ package reports import ( "context" "database/sql" + "sort" "testing" "time" @@ -118,17 +119,13 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { t.Run("FailedBuilds_SecondRun_Report_ThirdRunTooEarly_NoReport_FourthRun_Report", func(t *testing.T) { t.Parallel() - verifyNotification := func(t *testing.T, recipient database.User, notif *notificationstest.FakeNotification, tmpl database.Template, failedBuilds, totalBuilds int64, templateVersions []map[string]interface{}) { + verifyNotification := func(t *testing.T, recipientID uuid.UUID, notif *notificationstest.FakeNotification, templates []map[string]any) { t.Helper() - require.Equal(t, recipient.ID, notif.UserID) + require.Equal(t, recipientID, notif.UserID) require.Equal(t, notifications.TemplateWorkspaceBuildsFailedReport, notif.TemplateID) - require.Equal(t, tmpl.Name, notif.Labels["template_name"]) - require.Equal(t, tmpl.DisplayName, notif.Labels["template_display_name"]) - require.Equal(t, failedBuilds, notif.Data["failed_builds"]) - require.Equal(t, totalBuilds, notif.Data["total_builds"]) require.Equal(t, "week", notif.Data["report_frequency"]) - require.Equal(t, templateVersions, notif.Data["template_versions"]) + require.Equal(t, templates, notif.Data["templates"]) } // Setup @@ -212,43 +209,65 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { require.NoError(t, err) sent := notifEnq.Sent() - require.Len(t, sent, 4) // 2 templates, 2 template admins - for i, templateAdmin := range []database.User{templateAdmin1, templateAdmin2} { - verifyNotification(t, templateAdmin, sent[i], t1, 3, 4, []map[string]interface{}{ - { - "failed_builds": []map[string]interface{}{ - {"build_number": int32(7), "workspace_name": w3.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(1), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - }, - "failed_count": 2, - "template_version_name": t1v1.Name, - }, - { - "failed_builds": []map[string]interface{}{ - {"build_number": int32(3), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - }, - "failed_count": 1, - "template_version_name": t1v2.Name, - }, - }) - } + require.Len(t, sent, 2) // 2 templates, 2 template admins - for i, templateAdmin := range []database.User{templateAdmin1, templateAdmin2} { - verifyNotification(t, templateAdmin, sent[i+2], t2, 3, 5, []map[string]interface{}{ + templateAdmins := []uuid.UUID{templateAdmin1.ID, templateAdmin2.ID} + + // Ensure consistent order for tests + sort.Slice(templateAdmins, func(i, j int) bool { + return templateAdmins[i].String() < templateAdmins[j].String() + }) + sort.Slice(sent, func(i, j int) bool { + return sent[i].UserID.String() < sent[j].UserID.String() + }) + + for i, templateAdmin := range templateAdmins { + verifyNotification(t, templateAdmin, sent[i], []map[string]any{ { - "failed_builds": []map[string]interface{}{ - {"build_number": int32(8), "workspace_name": w4.Name, "workspace_owner_username": user2.Username}, + "name": t1.Name, + "display_name": t1.DisplayName, + "failed_builds": int64(3), + "total_builds": int64(4), + "versions": []map[string]any{ + { + "failed_builds": []map[string]any{ + {"build_number": int32(7), "workspace_name": w3.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(1), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + }, + "failed_count": 2, + "template_version_name": t1v1.Name, + }, + { + "failed_builds": []map[string]any{ + {"build_number": int32(3), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + }, + "failed_count": 1, + "template_version_name": t1v2.Name, + }, }, - "failed_count": 1, - "template_version_name": t2v1.Name, }, { - "failed_builds": []map[string]interface{}{ - {"build_number": int32(6), "workspace_name": w2.Name, "workspace_owner_username": user2.Username}, - {"build_number": int32(5), "workspace_name": w2.Name, "workspace_owner_username": user2.Username}, + "name": t2.Name, + "display_name": t2.DisplayName, + "failed_builds": int64(3), + "total_builds": int64(5), + "versions": []map[string]any{ + { + "failed_builds": []map[string]any{ + {"build_number": int32(8), "workspace_name": w4.Name, "workspace_owner_username": user2.Username}, + }, + "failed_count": 1, + "template_version_name": t2v1.Name, + }, + { + "failed_builds": []map[string]any{ + {"build_number": int32(6), "workspace_name": w2.Name, "workspace_owner_username": user2.Username}, + {"build_number": int32(5), "workspace_name": w2.Name, "workspace_owner_username": user2.Username}, + }, + "failed_count": 2, + "template_version_name": t2v2.Name, + }, }, - "failed_count": 2, - "template_version_name": t2v2.Name, }, }) } @@ -279,14 +298,33 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { // Then: we should see the failed job in the report sent = notifEnq.Sent() require.Len(t, sent, 2) // a new failed job should be reported - for i, templateAdmin := range []database.User{templateAdmin1, templateAdmin2} { - verifyNotification(t, templateAdmin, sent[i], t1, 1, 1, []map[string]interface{}{ + + templateAdmins = []uuid.UUID{templateAdmin1.ID, templateAdmin2.ID} + + // Ensure consistent order for tests + sort.Slice(templateAdmins, func(i, j int) bool { + return templateAdmins[i].String() < templateAdmins[j].String() + }) + sort.Slice(sent, func(i, j int) bool { + return sent[i].UserID.String() < sent[j].UserID.String() + }) + + for i, templateAdmin := range templateAdmins { + verifyNotification(t, templateAdmin, sent[i], []map[string]any{ { - "failed_builds": []map[string]interface{}{ - {"build_number": int32(77), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + "name": t1.Name, + "display_name": t1.DisplayName, + "failed_builds": int64(1), + "total_builds": int64(1), + "versions": []map[string]any{ + { + "failed_builds": []map[string]any{ + {"build_number": int32(77), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + }, + "failed_count": 1, + "template_version_name": t1v2.Name, + }, }, - "failed_count": 1, - "template_version_name": t1v2.Name, }, }) } @@ -295,17 +333,13 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { t.Run("TooManyFailedBuilds_SecondRun_Report", func(t *testing.T) { t.Parallel() - verifyNotification := func(t *testing.T, recipient database.User, notif *notificationstest.FakeNotification, tmpl database.Template, failedBuilds, totalBuilds int64, templateVersions []map[string]interface{}) { + verifyNotification := func(t *testing.T, recipient database.User, notif *notificationstest.FakeNotification, templates []map[string]any) { t.Helper() require.Equal(t, recipient.ID, notif.UserID) require.Equal(t, notifications.TemplateWorkspaceBuildsFailedReport, notif.TemplateID) - require.Equal(t, tmpl.Name, notif.Labels["template_name"]) - require.Equal(t, tmpl.DisplayName, notif.Labels["template_display_name"]) - require.Equal(t, failedBuilds, notif.Data["failed_builds"]) - require.Equal(t, totalBuilds, notif.Data["total_builds"]) require.Equal(t, "week", notif.Data["report_frequency"]) - require.Equal(t, templateVersions, notif.Data["template_versions"]) + require.Equal(t, templates, notif.Data["templates"]) } // Setup @@ -369,38 +403,46 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { sent := notifEnq.Sent() require.Len(t, sent, 1) // 1 template, 1 template admin - verifyNotification(t, templateAdmin1, sent[0], t1, 46, 47, []map[string]interface{}{ + verifyNotification(t, templateAdmin1, sent[0], []map[string]any{ { - "failed_builds": []map[string]interface{}{ - {"build_number": int32(23), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(22), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(21), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(20), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(19), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(18), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(17), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(16), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(15), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(14), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - }, - "failed_count": 23, - "template_version_name": t1v1.Name, - }, - { - "failed_builds": []map[string]interface{}{ - {"build_number": int32(123), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(122), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(121), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(120), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(119), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(118), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(117), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(116), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(115), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(114), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + "name": t1.Name, + "display_name": t1.DisplayName, + "failed_builds": int64(46), + "total_builds": int64(47), + "versions": []map[string]any{ + { + "failed_builds": []map[string]any{ + {"build_number": int32(23), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(22), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(21), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(20), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(19), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(18), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(17), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(16), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(15), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(14), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + }, + "failed_count": 23, + "template_version_name": t1v1.Name, + }, + { + "failed_builds": []map[string]any{ + {"build_number": int32(123), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(122), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(121), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(120), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(119), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(118), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(117), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(116), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(115), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(114), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + }, + "failed_count": 23, + "template_version_name": t1v2.Name, + }, }, - "failed_count": 23, - "template_version_name": t1v2.Name, }, }) }) diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden index f3edc6ac05d02..a758a4be70c4c 100644 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden +++ b/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden @@ -1,6 +1,6 @@ From: system@coder.com To: bobby@coder.com -Subject: Workspace builds failed for template "Bobby First Template" +Subject: Failed workspace builds report Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48 Date: Fri, 11 Oct 2024 09:03:06 +0000 Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 @@ -12,12 +12,14 @@ Content-Type: text/plain; charset=UTF-8 Hi Bobby, -Template Bobby First Template has failed to build 4/55 times over the last = -week. +The following templates have had build failures over the last week: + +Bobby First Template failed to build 4/55 times +Bobby Second Template failed to build 5/50 times Report: -bobby-template-version-1 failed 3 times: +Bobby First Template@bobby-template-version-1 failed 3 times: mtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/12= 34) @@ -25,16 +27,30 @@ johndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/b= uilds/5678) jack / workwork / #774 (http://test.com/@jack/workwork/builds/774) -bobby-template-version-2 failed 1 time: +Bobby First Template@ failed 1 time: ben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/88= 88) +Bobby Second Template@bobby-template-version-1 failed 3 times: + +daniellemaywood / workspace-9 / #9234 (http://test.com/@daniellemaywood/wor= +kspace-9/builds/9234) +johndoe / my-workspace-7 / #8678 (http://test.com/@johndoe/my-workspace-7/b= +uilds/8678) +jack / workworkwork / #374 (http://test.com/@jack/workworkwork/builds/374) + +Bobby Second Template@ failed 2 times: + +ben / more-cool-workspace / #8878 (http://test.com/@ben/more-cool-workspace= +/builds/8878) +ben / less-cool-workspace / #8848 (http://test.com/@ben/less-cool-workspace= +/builds/8848) + We recommend reviewing these issues to ensure future builds are successful. -View workspaces: http://test.com/workspaces?filter=3Dtemplate%3Abobby-first= --template +View workspaces: http://test.com/workspaces?filter=3Dtemplate%3A --bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 Content-Transfer-Encoding: quoted-printable @@ -46,8 +62,7 @@ Content-Type: text/html; charset=UTF-8 - Workspace builds failed for template "Bobby First Template"</tit= -le> + <title>Failed workspace builds report

- Workspace builds failed for template "Bobby First Template" + Failed workspace builds report

Hi Bobby,

-

Template Bobby First Template has failed to bui= -ld 455 times over the last week.

+

The following templates have had build failures over the last we= +ek:

+ +
    +
  • Bobby First Template failed to build 4&f= +rasl;55 times

  • + +
  • Bobby Second Template failed to build 5&= +frasl;50 times

  • +

Report:

-

bobby-template-version-1 failed 3 times:

+

Bobby First Template@bobby-template-version-1 failed 3 times:

-

bobby-template-version-2 failed 1 time:

+

Bobby First Template@ failed 1 time:

+

Bobby Second Template@bobby-template-version-1<= +/strong> failed 3 times:

+ + + +

Bobby Second Template@ failed 2 times:= +

+ + +

We recommend reviewing these issues to ensure future builds are successf= ul.

=20 - + " style=3D"display: inline-block; padding: 13px 24px; background-color: #0= +20617; color: #f8fafc; text-decoration: none; border-radius: 8px; margin: 0= + 4px;"> View workspaces =20 diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden index 987d97b91c029..9149f13afc259 100644 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden +++ b/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden @@ -3,7 +3,7 @@ "msg_id": "00000000-0000-0000-0000-000000000000", "payload": { "_version": "1.2", - "notification_name": "Report: Workspace Builds Failed For Template", + "notification_name": "Report: Workspace Builds Failed", "notification_template_id": "00000000-0000-0000-0000-000000000000", "user_id": "00000000-0000-0000-0000-000000000000", "user_email": "bobby@coder.com", @@ -12,56 +12,104 @@ "actions": [ { "label": "View workspaces", - "url": "http://test.com/workspaces?filter=template%3Abobby-first-template" + "url": "http://test.com/workspaces?filter=template%3A\u003cno value\u003e" } ], - "labels": { - "template_display_name": "Bobby First Template", - "template_name": "bobby-first-template" - }, + "labels": {}, "data": { - "failed_builds": 4, "report_frequency": "week", - "template_versions": [ + "templates": [ { - "failed_builds": [ - { - "build_number": 1234, - "workspace_name": "workspace-1", - "workspace_owner_username": "mtojek" - }, + "display_name": "Bobby First Template", + "failed_builds": 4, + "name": "bobby-first-template", + "total_builds": 55, + "versions": [ { - "build_number": 5678, - "workspace_name": "my-workspace-3", - "workspace_owner_username": "johndoe" + "failed_builds": [ + { + "build_number": 1234, + "workspace_name": "workspace-1", + "workspace_owner_username": "mtojek" + }, + { + "build_number": 5678, + "workspace_name": "my-workspace-3", + "workspace_owner_username": "johndoe" + }, + { + "build_number": 774, + "workspace_name": "workwork", + "workspace_owner_username": "jack" + } + ], + "failed_count": 3, + "template_version_name": "bobby-template-version-1" }, { - "build_number": 774, - "workspace_name": "workwork", - "workspace_owner_username": "jack" + "failed_builds": [ + { + "build_number": 8888, + "workspace_name": "cool-workspace", + "workspace_owner_username": "ben" + } + ], + "failed_count": 1, + "template_version_count": "bobby-template-version-2" } - ], - "failed_count": 3, - "template_version_name": "bobby-template-version-1" + ] }, { - "failed_builds": [ + "display_name": "Bobby Second Template", + "failed_builds": 5, + "name": "bobby-second-template", + "total_builds": 50, + "versions": [ + { + "failed_builds": [ + { + "build_number": 9234, + "workspace_name": "workspace-9", + "workspace_owner_username": "daniellemaywood" + }, + { + "build_number": 8678, + "workspace_name": "my-workspace-7", + "workspace_owner_username": "johndoe" + }, + { + "build_number": 374, + "workspace_name": "workworkwork", + "workspace_owner_username": "jack" + } + ], + "failed_count": 3, + "template_version_name": "bobby-template-version-1" + }, { - "build_number": 8888, - "workspace_name": "cool-workspace", - "workspace_owner_username": "ben" + "failed_builds": [ + { + "build_number": 8878, + "workspace_name": "more-cool-workspace", + "workspace_owner_username": "ben" + }, + { + "build_number": 8848, + "workspace_name": "less-cool-workspace", + "workspace_owner_username": "ben" + } + ], + "failed_count": 2, + "template_version_count": "bobby-template-version-2" } - ], - "failed_count": 1, - "template_version_name": "bobby-template-version-2" + ] } - ], - "total_builds": 55 + ] }, "targets": null }, - "title": "Workspace builds failed for template \"Bobby First Template\"", - "title_markdown": "Workspace builds failed for template \"Bobby First Template\"", - "body": "Template Bobby First Template has failed to build 4/55 times over the last week.\n\nReport:\n\nbobby-template-version-1 failed 3 times:\n\nmtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/1234)\njohndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/builds/5678)\njack / workwork / #774 (http://test.com/@jack/workwork/builds/774)\n\nbobby-template-version-2 failed 1 time:\n\nben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/8888)\n\nWe recommend reviewing these issues to ensure future builds are successful.", - "body_markdown": "Template **Bobby First Template** has failed to build 4/55 times over the last week.\n\n**Report:**\n\n**bobby-template-version-1** failed 3 times:\n\n* [mtojek / workspace-1 / #1234](http://test.com/@mtojek/workspace-1/builds/1234)\n* [johndoe / my-workspace-3 / #5678](http://test.com/@johndoe/my-workspace-3/builds/5678)\n* [jack / workwork / #774](http://test.com/@jack/workwork/builds/774)\n\n**bobby-template-version-2** failed 1 time:\n\n* [ben / cool-workspace / #8888](http://test.com/@ben/cool-workspace/builds/8888)\n\nWe recommend reviewing these issues to ensure future builds are successful." + "title": "Failed workspace builds report", + "title_markdown": "Failed workspace builds report", + "body": "The following templates have had build failures over the last week:\n\nBobby First Template failed to build 4/55 times\nBobby Second Template failed to build 5/50 times\n\nReport:\n\nBobby First Template@bobby-template-version-1 failed 3 times:\n\nmtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/1234)\njohndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/builds/5678)\njack / workwork / #774 (http://test.com/@jack/workwork/builds/774)\n\nBobby First Template@ failed 1 time:\n\nben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/8888)\n\nBobby Second Template@bobby-template-version-1 failed 3 times:\n\ndaniellemaywood / workspace-9 / #9234 (http://test.com/@daniellemaywood/workspace-9/builds/9234)\njohndoe / my-workspace-7 / #8678 (http://test.com/@johndoe/my-workspace-7/builds/8678)\njack / workworkwork / #374 (http://test.com/@jack/workworkwork/builds/374)\n\nBobby Second Template@ failed 2 times:\n\nben / more-cool-workspace / #8878 (http://test.com/@ben/more-cool-workspace/builds/8878)\nben / less-cool-workspace / #8848 (http://test.com/@ben/less-cool-workspace/builds/8848)\n\nWe recommend reviewing these issues to ensure future builds are successful.", + "body_markdown": "The following templates have had build failures over the last week:\n\n- **Bobby First Template** failed to build 4/55 times\n\n- **Bobby Second Template** failed to build 5/50 times\n\n\n**Report:**\n\n\n**Bobby First Template**@**bobby-template-version-1** failed 3 times:\n\n* [mtojek / workspace-1 / #1234](http://test.com/@mtojek/workspace-1/builds/1234)\n\n* [johndoe / my-workspace-3 / #5678](http://test.com/@johndoe/my-workspace-3/builds/5678)\n\n* [jack / workwork / #774](http://test.com/@jack/workwork/builds/774)\n\n\n**Bobby First Template**@**\u003cno value\u003e** failed 1 time:\n\n* [ben / cool-workspace / #8888](http://test.com/@ben/cool-workspace/builds/8888)\n\n\n\n\n**Bobby Second Template**@**bobby-template-version-1** failed 3 times:\n\n* [daniellemaywood / workspace-9 / #9234](http://test.com/@daniellemaywood/workspace-9/builds/9234)\n\n* [johndoe / my-workspace-7 / #8678](http://test.com/@johndoe/my-workspace-7/builds/8678)\n\n* [jack / workworkwork / #374](http://test.com/@jack/workworkwork/builds/374)\n\n\n**Bobby Second Template**@**\u003cno value\u003e** failed 2 times:\n\n* [ben / more-cool-workspace / #8878](http://test.com/@ben/more-cool-workspace/builds/8878)\n\n* [ben / less-cool-workspace / #8848](http://test.com/@ben/less-cool-workspace/builds/8848)\n\n\n\n\nWe recommend reviewing these issues to ensure future builds are successful." } \ No newline at end of file From 18a9a2efdd1971ab00944a870490cf02d5450558 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 9 Apr 2025 14:49:47 +0000 Subject: [PATCH 2/8] chore: fix broken test, update template --- ...6_group_build_failure_notifications.up.sql | 5 +- coderd/notifications/notifications_test.go | 8 +- ...ateWorkspaceBuildsFailedReport.html.golden | 77 ++++++++++--------- ...ateWorkspaceBuildsFailedReport.json.golden | 8 +- 4 files changed, 52 insertions(+), 46 deletions(-) diff --git a/coderd/database/migrations/000316_group_build_failure_notifications.up.sql b/coderd/database/migrations/000316_group_build_failure_notifications.up.sql index a98f1e49b7062..fa518e7a50fa7 100644 --- a/coderd/database/migrations/000316_group_build_failure_notifications.up.sql +++ b/coderd/database/migrations/000316_group_build_failure_notifications.up.sql @@ -10,10 +10,11 @@ E'The following templates have had build failures over the last {{.Data.report_f **Report:** {{range $template := .Data.templates}} +**{{$template.display_name}}** {{range $version := $template.versions}} -**{{$template.display_name}}**@**{{$version.template_version_name}}** failed {{$version.failed_count}} time{{if gt $version.failed_count 1.0}}s{{end}}: +- **{{$version.template_version_name}}** failed {{$version.failed_count}} time{{if gt $version.failed_count 1.0}}s{{end}}: {{range $build := $version.failed_builds}} -* [{{$build.workspace_owner_username}} / {{$build.workspace_name}} / #{{$build.build_number}}]({{base_url}}/@{{$build.workspace_owner_username}}/{{$build.workspace_name}}/builds/{{$build.build_number}}) + - [{{$build.workspace_owner_username}} / {{$build.workspace_name}} / #{{$build.build_number}}]({{base_url}}/@{{$build.workspace_owner_username}}/{{$build.workspace_name}}/builds/{{$build.build_number}}) {{end}} {{end}} {{end}} diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index f294b7a40a141..e3bbbc87fdf34 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -1011,8 +1011,8 @@ func TestNotificationTemplates_Golden(t *testing.T) { }, }, { - "template_version_count": "bobby-template-version-2", - "failed_count": 1.0, + "template_version_name": "bobby-template-version-2", + "failed_count": 1.0, "failed_builds": []map[string]any{ { "workspace_owner_username": "ben", @@ -1051,8 +1051,8 @@ func TestNotificationTemplates_Golden(t *testing.T) { }, }, { - "template_version_count": "bobby-template-version-2", - "failed_count": 2.0, + "template_version_name": "bobby-template-version-2", + "failed_count": 2.0, "failed_builds": []map[string]any{ { "workspace_owner_username": "ben", diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden index a758a4be70c4c..7cbb9efdc9231 100644 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden +++ b/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden @@ -19,33 +19,34 @@ Bobby Second Template failed to build 5/50 times Report: -Bobby First Template@bobby-template-version-1 failed 3 times: +Bobby First Template + +bobby-template-version-1 failed 3 times: + mtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/build= +s/1234) + johndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace= +-3/builds/5678) + jack / workwork / #774 (http://test.com/@jack/workwork/builds/774) +bobby-template-version-2 failed 1 time: + ben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/build= +s/8888) + + +Bobby Second Template + +bobby-template-version-1 failed 3 times: + daniellemaywood / workspace-9 / #9234 (http://test.com/@daniellemaywood= +/workspace-9/builds/9234) + johndoe / my-workspace-7 / #8678 (http://test.com/@johndoe/my-workspace= +-7/builds/8678) + jack / workworkwork / #374 (http://test.com/@jack/workworkwork/builds/3= +74) +bobby-template-version-2 failed 2 times: + ben / more-cool-workspace / #8878 (http://test.com/@ben/more-cool-works= +pace/builds/8878) + ben / less-cool-workspace / #8848 (http://test.com/@ben/less-cool-works= +pace/builds/8848) -mtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/12= -34) -johndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/b= -uilds/5678) -jack / workwork / #774 (http://test.com/@jack/workwork/builds/774) - -Bobby First Template@ failed 1 time: - -ben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/88= -88) - -Bobby Second Template@bobby-template-version-1 failed 3 times: - -daniellemaywood / workspace-9 / #9234 (http://test.com/@daniellemaywood/wor= -kspace-9/builds/9234) -johndoe / my-workspace-7 / #8678 (http://test.com/@johndoe/my-workspace-7/b= -uilds/8678) -jack / workworkwork / #374 (http://test.com/@jack/workworkwork/builds/374) - -Bobby Second Template@ failed 2 times: - -ben / more-cool-workspace / #8878 (http://test.com/@ben/more-cool-workspace= -/builds/8878) -ben / less-cool-workspace / #8848 (http://test.com/@ben/less-cool-workspace= -/builds/8848) We recommend reviewing these issues to ensure future builds are successful. @@ -94,8 +95,10 @@ frasl;50 times

Report:

-

Bobby First Template@bobby-template-version-1 failed 3 times:

+

Bobby First Template

+ + -

Bobby First Template@ failed 1 time: +

  • bobby-template-version-2 failed 1 time:

  • -

    Bobby Second Template@bobby-template-version-1<= -/strong> failed 3 times:

    +

    Bobby Second Template

    + + -

    Bobby Second Template@ failed 2 times:= -

    +
  • bobby-template-version-2 failed 2 times:

  • We recommend reviewing these issues to ensure future builds are successf= diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden index 9149f13afc259..ef7079c505149 100644 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden +++ b/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden @@ -55,7 +55,7 @@ } ], "failed_count": 1, - "template_version_count": "bobby-template-version-2" + "template_version_name": "bobby-template-version-2" } ] }, @@ -100,7 +100,7 @@ } ], "failed_count": 2, - "template_version_count": "bobby-template-version-2" + "template_version_name": "bobby-template-version-2" } ] } @@ -110,6 +110,6 @@ }, "title": "Failed workspace builds report", "title_markdown": "Failed workspace builds report", - "body": "The following templates have had build failures over the last week:\n\nBobby First Template failed to build 4/55 times\nBobby Second Template failed to build 5/50 times\n\nReport:\n\nBobby First Template@bobby-template-version-1 failed 3 times:\n\nmtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/1234)\njohndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/builds/5678)\njack / workwork / #774 (http://test.com/@jack/workwork/builds/774)\n\nBobby First Template@ failed 1 time:\n\nben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/8888)\n\nBobby Second Template@bobby-template-version-1 failed 3 times:\n\ndaniellemaywood / workspace-9 / #9234 (http://test.com/@daniellemaywood/workspace-9/builds/9234)\njohndoe / my-workspace-7 / #8678 (http://test.com/@johndoe/my-workspace-7/builds/8678)\njack / workworkwork / #374 (http://test.com/@jack/workworkwork/builds/374)\n\nBobby Second Template@ failed 2 times:\n\nben / more-cool-workspace / #8878 (http://test.com/@ben/more-cool-workspace/builds/8878)\nben / less-cool-workspace / #8848 (http://test.com/@ben/less-cool-workspace/builds/8848)\n\nWe recommend reviewing these issues to ensure future builds are successful.", - "body_markdown": "The following templates have had build failures over the last week:\n\n- **Bobby First Template** failed to build 4/55 times\n\n- **Bobby Second Template** failed to build 5/50 times\n\n\n**Report:**\n\n\n**Bobby First Template**@**bobby-template-version-1** failed 3 times:\n\n* [mtojek / workspace-1 / #1234](http://test.com/@mtojek/workspace-1/builds/1234)\n\n* [johndoe / my-workspace-3 / #5678](http://test.com/@johndoe/my-workspace-3/builds/5678)\n\n* [jack / workwork / #774](http://test.com/@jack/workwork/builds/774)\n\n\n**Bobby First Template**@**\u003cno value\u003e** failed 1 time:\n\n* [ben / cool-workspace / #8888](http://test.com/@ben/cool-workspace/builds/8888)\n\n\n\n\n**Bobby Second Template**@**bobby-template-version-1** failed 3 times:\n\n* [daniellemaywood / workspace-9 / #9234](http://test.com/@daniellemaywood/workspace-9/builds/9234)\n\n* [johndoe / my-workspace-7 / #8678](http://test.com/@johndoe/my-workspace-7/builds/8678)\n\n* [jack / workworkwork / #374](http://test.com/@jack/workworkwork/builds/374)\n\n\n**Bobby Second Template**@**\u003cno value\u003e** failed 2 times:\n\n* [ben / more-cool-workspace / #8878](http://test.com/@ben/more-cool-workspace/builds/8878)\n\n* [ben / less-cool-workspace / #8848](http://test.com/@ben/less-cool-workspace/builds/8848)\n\n\n\n\nWe recommend reviewing these issues to ensure future builds are successful." + "body": "The following templates have had build failures over the last week:\n\nBobby First Template failed to build 4/55 times\nBobby Second Template failed to build 5/50 times\n\nReport:\n\nBobby First Template\n\nbobby-template-version-1 failed 3 times:\n mtojek / workspace-1 / #1234 (http://test.com/@mtojek/workspace-1/builds/1234)\n johndoe / my-workspace-3 / #5678 (http://test.com/@johndoe/my-workspace-3/builds/5678)\n jack / workwork / #774 (http://test.com/@jack/workwork/builds/774)\nbobby-template-version-2 failed 1 time:\n ben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/8888)\n\n\nBobby Second Template\n\nbobby-template-version-1 failed 3 times:\n daniellemaywood / workspace-9 / #9234 (http://test.com/@daniellemaywood/workspace-9/builds/9234)\n johndoe / my-workspace-7 / #8678 (http://test.com/@johndoe/my-workspace-7/builds/8678)\n jack / workworkwork / #374 (http://test.com/@jack/workworkwork/builds/374)\nbobby-template-version-2 failed 2 times:\n ben / more-cool-workspace / #8878 (http://test.com/@ben/more-cool-workspace/builds/8878)\n ben / less-cool-workspace / #8848 (http://test.com/@ben/less-cool-workspace/builds/8848)\n\n\nWe recommend reviewing these issues to ensure future builds are successful.", + "body_markdown": "The following templates have had build failures over the last week:\n\n- **Bobby First Template** failed to build 4/55 times\n\n- **Bobby Second Template** failed to build 5/50 times\n\n\n**Report:**\n\n**Bobby First Template**\n\n- **bobby-template-version-1** failed 3 times:\n\n - [mtojek / workspace-1 / #1234](http://test.com/@mtojek/workspace-1/builds/1234)\n\n - [johndoe / my-workspace-3 / #5678](http://test.com/@johndoe/my-workspace-3/builds/5678)\n\n - [jack / workwork / #774](http://test.com/@jack/workwork/builds/774)\n\n\n- **bobby-template-version-2** failed 1 time:\n\n - [ben / cool-workspace / #8888](http://test.com/@ben/cool-workspace/builds/8888)\n\n\n\n**Bobby Second Template**\n\n- **bobby-template-version-1** failed 3 times:\n\n - [daniellemaywood / workspace-9 / #9234](http://test.com/@daniellemaywood/workspace-9/builds/9234)\n\n - [johndoe / my-workspace-7 / #8678](http://test.com/@johndoe/my-workspace-7/builds/8678)\n\n - [jack / workworkwork / #374](http://test.com/@jack/workworkwork/builds/374)\n\n\n- **bobby-template-version-2** failed 2 times:\n\n - [ben / more-cool-workspace / #8878](http://test.com/@ben/more-cool-workspace/builds/8878)\n\n - [ben / less-cool-workspace / #8848](http://test.com/@ben/less-cool-workspace/builds/8848)\n\n\n\n\nWe recommend reviewing these issues to ensure future builds are successful." } \ No newline at end of file From c84aaee330a2e82a1bfe87e89416282b02801770 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 10 Apr 2025 10:02:36 +0000 Subject: [PATCH 3/8] chore: update call to action The CTA should link to a list of every affected workspace --- ...group_build_failure_notifications.down.sql | 21 +++++++++++++++++++ ...6_group_build_failure_notifications.up.sql | 8 ++++++- coderd/notifications/notifications_test.go | 9 ++++++++ ...ateWorkspaceBuildsFailedReport.html.golden | 19 ++++++++++++----- ...ateWorkspaceBuildsFailedReport.json.golden | 11 +++++++++- 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/coderd/database/migrations/000316_group_build_failure_notifications.down.sql b/coderd/database/migrations/000316_group_build_failure_notifications.down.sql index e69de29bb2d1d..13508bb8556c8 100644 --- a/coderd/database/migrations/000316_group_build_failure_notifications.down.sql +++ b/coderd/database/migrations/000316_group_build_failure_notifications.down.sql @@ -0,0 +1,21 @@ +UPDATE notification_templates +SET + name = 'Report: Workspace Builds Failed For Template', + title_template = E'Workspace builds failed for template "{{.Labels.template_display_name}}"', + body_template = E'Template **{{.Labels.template_display_name}}** has failed to build {{.Data.failed_builds}}/{{.Data.total_builds}} times over the last {{.Data.report_frequency}}. + +**Report:** +{{range $version := .Data.template_versions}} +**{{$version.template_version_name}}** failed {{$version.failed_count}} time{{if gt $version.failed_count 1.0}}s{{end}}: +{{range $build := $version.failed_builds}} +* [{{$build.workspace_owner_username}} / {{$build.workspace_name}} / #{{$build.build_number}}]({{base_url}}/@{{$build.workspace_owner_username}}/{{$build.workspace_name}}/builds/{{$build.build_number}}) +{{- end}} +{{end}} +We recommend reviewing these issues to ensure future builds are successful.', + actions = '[ + { + "label": "View workspaces", + "url": "{{ base_url }}/workspaces?filter=template%3A{{.Labels.template_name}}" + } + ]'::jsonb) +WHERE id = '34a20db2-e9cc-4a93-b0e4-8569699d7a00'; diff --git a/coderd/database/migrations/000316_group_build_failure_notifications.up.sql b/coderd/database/migrations/000316_group_build_failure_notifications.up.sql index fa518e7a50fa7..e3c4e79fc6d35 100644 --- a/coderd/database/migrations/000316_group_build_failure_notifications.up.sql +++ b/coderd/database/migrations/000316_group_build_failure_notifications.up.sql @@ -19,5 +19,11 @@ E'The following templates have had build failures over the last {{.Data.report_f {{end}} {{end}} -We recommend reviewing these issues to ensure future builds are successful.' +We recommend reviewing these issues to ensure future builds are successful.', + actions = '[ + { + "label": "View workspaces", + "url": "{{ base_url }}/workspaces?filter={{$first := true}}{{range $template := .Data.templates}}{{range $version := $template.versions}}{{range $build := $version.failed_builds}}{{if not $first}}+{{else}}{{$first = false}}{{end}}id%3A{{$build.workspace_id}}{{end}}{{end}}{{end}}" + } + ]'::jsonb WHERE id = '34a20db2-e9cc-4a93-b0e4-8569699d7a00'; diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index e3bbbc87fdf34..5f6c221e7beb5 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -996,16 +996,19 @@ func TestNotificationTemplates_Golden(t *testing.T) { { "workspace_owner_username": "mtojek", "workspace_name": "workspace-1", + "workspace_id": "24f5bd8f-1566-4374-9734-c3efa0454dc7", "build_number": 1234.0, }, { "workspace_owner_username": "johndoe", "workspace_name": "my-workspace-3", + "workspace_id": "372a194b-dcde-43f1-b7cf-8a2f3d3114a0", "build_number": 5678.0, }, { "workspace_owner_username": "jack", "workspace_name": "workwork", + "workspace_id": "1386d294-19c1-4351-89e2-6cae1afb9bfe", "build_number": 774.0, }, }, @@ -1017,6 +1020,7 @@ func TestNotificationTemplates_Golden(t *testing.T) { { "workspace_owner_username": "ben", "workspace_name": "cool-workspace", + "workspace_id": "86fd99b1-1b6e-4b7e-b58e-0aee6e35c159", "build_number": 8888.0, }, }, @@ -1036,16 +1040,19 @@ func TestNotificationTemplates_Golden(t *testing.T) { { "workspace_owner_username": "daniellemaywood", "workspace_name": "workspace-9", + "workspace_id": "cd469690-b6eb-4123-b759-980be7a7b278", "build_number": 9234.0, }, { "workspace_owner_username": "johndoe", "workspace_name": "my-workspace-7", + "workspace_id": "c447d472-0800-4529-a836-788754d5e27d", "build_number": 8678.0, }, { "workspace_owner_username": "jack", "workspace_name": "workworkwork", + "workspace_id": "919db6df-48f0-4dc1-b357-9036a2c40f86", "build_number": 374.0, }, }, @@ -1057,11 +1064,13 @@ func TestNotificationTemplates_Golden(t *testing.T) { { "workspace_owner_username": "ben", "workspace_name": "more-cool-workspace", + "workspace_id": "c8fb0652-9290-4bf2-a711-71b910243ac2", "build_number": 8878.0, }, { "workspace_owner_username": "ben", "workspace_name": "less-cool-workspace", + "workspace_id": "703d718d-2234-4990-9a02-5b1df6cf462a", "build_number": 8848.0, }, }, diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden index 7cbb9efdc9231..9699486bf9cc8 100644 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden +++ b/coderd/notifications/testdata/rendered-templates/smtp/TemplateWorkspaceBuildsFailedReport.html.golden @@ -51,7 +51,12 @@ pace/builds/8848) We recommend reviewing these issues to ensure future builds are successful. -View workspaces: http://test.com/workspaces?filter=3Dtemplate%3A +View workspaces: http://test.com/workspaces?filter=3Did%3A24f5bd8f-1566-437= +4-9734-c3efa0454dc7+id%3A372a194b-dcde-43f1-b7cf-8a2f3d3114a0+id%3A1386d294= +-19c1-4351-89e2-6cae1afb9bfe+id%3A86fd99b1-1b6e-4b7e-b58e-0aee6e35c159+id%3= +Acd469690-b6eb-4123-b759-980be7a7b278+id%3Ac447d472-0800-4529-a836-788754d5= +e27d+id%3A919db6df-48f0-4dc1-b357-9036a2c40f86+id%3Ac8fb0652-9290-4bf2-a711= +-71b910243ac2+id%3A703d718d-2234-4990-9a02-5b1df6cf462a --bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 Content-Transfer-Encoding: quoted-printable @@ -152,10 +157,14 @@ ul.

    =20 - " style=3D"display: inline-block; padding: 13px 24px; background-color: #0= -20617; color: #f8fafc; text-decoration: none; border-radius: 8px; margin: 0= - 4px;"> + View workspaces =20 diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden index ef7079c505149..78c8ba2a3195c 100644 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden +++ b/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden @@ -12,7 +12,7 @@ "actions": [ { "label": "View workspaces", - "url": "http://test.com/workspaces?filter=template%3A\u003cno value\u003e" + "url": "http://test.com/workspaces?filter=id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000+id%3A00000000-0000-0000-0000-000000000000" } ], "labels": {}, @@ -29,16 +29,19 @@ "failed_builds": [ { "build_number": 1234, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "workspace-1", "workspace_owner_username": "mtojek" }, { "build_number": 5678, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "my-workspace-3", "workspace_owner_username": "johndoe" }, { "build_number": 774, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "workwork", "workspace_owner_username": "jack" } @@ -50,6 +53,7 @@ "failed_builds": [ { "build_number": 8888, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "cool-workspace", "workspace_owner_username": "ben" } @@ -69,16 +73,19 @@ "failed_builds": [ { "build_number": 9234, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "workspace-9", "workspace_owner_username": "daniellemaywood" }, { "build_number": 8678, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "my-workspace-7", "workspace_owner_username": "johndoe" }, { "build_number": 374, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "workworkwork", "workspace_owner_username": "jack" } @@ -90,11 +97,13 @@ "failed_builds": [ { "build_number": 8878, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "more-cool-workspace", "workspace_owner_username": "ben" }, { "build_number": 8848, + "workspace_id": "00000000-0000-0000-0000-000000000000", "workspace_name": "less-cool-workspace", "workspace_owner_username": "ben" } From 16af318da6ee6b7d28399619f1ca57096902751f Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 10 Apr 2025 10:24:40 +0000 Subject: [PATCH 4/8] chore: oops --- .../000316_group_build_failure_notifications.down.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/migrations/000316_group_build_failure_notifications.down.sql b/coderd/database/migrations/000316_group_build_failure_notifications.down.sql index 13508bb8556c8..3ea2e98ff19e1 100644 --- a/coderd/database/migrations/000316_group_build_failure_notifications.down.sql +++ b/coderd/database/migrations/000316_group_build_failure_notifications.down.sql @@ -17,5 +17,5 @@ We recommend reviewing these issues to ensure future builds are successful.', "label": "View workspaces", "url": "{{ base_url }}/workspaces?filter=template%3A{{.Labels.template_name}}" } - ]'::jsonb) + ]'::jsonb WHERE id = '34a20db2-e9cc-4a93-b0e4-8569699d7a00'; From 382e68942cd3bea23347996f4691b3dc15a0feae Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 10 Apr 2025 10:48:35 +0000 Subject: [PATCH 5/8] test: workspace ID is present in builds --- coderd/database/queries.sql.go | 11 ++-- coderd/database/queries/workspacebuilds.sql | 1 + coderd/notifications/reports/generator.go | 2 + .../reports/generator_internal_test.go | 54 +++++++++---------- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b93ad49f8f9d4..25bfe1db63bb3 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -16334,6 +16334,7 @@ SELECT tv.name AS template_version_name, u.username AS workspace_owner_username, w.name AS workspace_name, + w.id AS workspace_id, wb.build_number AS workspace_build_number FROM workspace_build_with_user AS wb @@ -16372,10 +16373,11 @@ type GetFailedWorkspaceBuildsByTemplateIDParams struct { } type GetFailedWorkspaceBuildsByTemplateIDRow struct { - TemplateVersionName string `db:"template_version_name" json:"template_version_name"` - WorkspaceOwnerUsername string `db:"workspace_owner_username" json:"workspace_owner_username"` - WorkspaceName string `db:"workspace_name" json:"workspace_name"` - WorkspaceBuildNumber int32 `db:"workspace_build_number" json:"workspace_build_number"` + TemplateVersionName string `db:"template_version_name" json:"template_version_name"` + WorkspaceOwnerUsername string `db:"workspace_owner_username" json:"workspace_owner_username"` + WorkspaceName string `db:"workspace_name" json:"workspace_name"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + WorkspaceBuildNumber int32 `db:"workspace_build_number" json:"workspace_build_number"` } func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg GetFailedWorkspaceBuildsByTemplateIDParams) ([]GetFailedWorkspaceBuildsByTemplateIDRow, error) { @@ -16391,6 +16393,7 @@ func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, a &i.TemplateVersionName, &i.WorkspaceOwnerUsername, &i.WorkspaceName, + &i.WorkspaceID, &i.WorkspaceBuildNumber, ); err != nil { return nil, err diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql index da349fa1441b3..34ef639a1694b 100644 --- a/coderd/database/queries/workspacebuilds.sql +++ b/coderd/database/queries/workspacebuilds.sql @@ -213,6 +213,7 @@ SELECT tv.name AS template_version_name, u.username AS workspace_owner_username, w.name AS workspace_name, + w.id AS workspace_id, wb.build_number AS workspace_build_number FROM workspace_build_with_user AS wb diff --git a/coderd/notifications/reports/generator.go b/coderd/notifications/reports/generator.go index 9f001687e00aa..6b7dbd0c5b7b9 100644 --- a/coderd/notifications/reports/generator.go +++ b/coderd/notifications/reports/generator.go @@ -249,6 +249,7 @@ func buildDataForReportFailedWorkspaceBuilds(reports []adminReport) map[string]a { "workspace_owner_username": failedBuild.WorkspaceOwnerUsername, "workspace_name": failedBuild.WorkspaceName, + "workspace_id": failedBuild.WorkspaceID, "build_number": failedBuild.WorkspaceBuildNumber, }, }, @@ -267,6 +268,7 @@ func buildDataForReportFailedWorkspaceBuilds(reports []adminReport) map[string]a builds = append(builds, map[string]any{ "workspace_owner_username": failedBuild.WorkspaceOwnerUsername, "workspace_name": failedBuild.WorkspaceName, + "workspace_id": failedBuild.WorkspaceID, "build_number": failedBuild.WorkspaceBuildNumber, }) tv["failed_builds"] = builds diff --git a/coderd/notifications/reports/generator_internal_test.go b/coderd/notifications/reports/generator_internal_test.go index e246007d3d124..f61064c4e0b23 100644 --- a/coderd/notifications/reports/generator_internal_test.go +++ b/coderd/notifications/reports/generator_internal_test.go @@ -231,15 +231,15 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { "versions": []map[string]any{ { "failed_builds": []map[string]any{ - {"build_number": int32(7), "workspace_name": w3.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(1), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(7), "workspace_name": w3.Name, "workspace_id": w3.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(1), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, }, "failed_count": 2, "template_version_name": t1v1.Name, }, { "failed_builds": []map[string]any{ - {"build_number": int32(3), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(3), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, }, "failed_count": 1, "template_version_name": t1v2.Name, @@ -254,15 +254,15 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { "versions": []map[string]any{ { "failed_builds": []map[string]any{ - {"build_number": int32(8), "workspace_name": w4.Name, "workspace_owner_username": user2.Username}, + {"build_number": int32(8), "workspace_name": w4.Name, "workspace_id": w4.ID, "workspace_owner_username": user2.Username}, }, "failed_count": 1, "template_version_name": t2v1.Name, }, { "failed_builds": []map[string]any{ - {"build_number": int32(6), "workspace_name": w2.Name, "workspace_owner_username": user2.Username}, - {"build_number": int32(5), "workspace_name": w2.Name, "workspace_owner_username": user2.Username}, + {"build_number": int32(6), "workspace_name": w2.Name, "workspace_id": w2.ID, "workspace_owner_username": user2.Username}, + {"build_number": int32(5), "workspace_name": w2.Name, "workspace_id": w2.ID, "workspace_owner_username": user2.Username}, }, "failed_count": 2, "template_version_name": t2v2.Name, @@ -319,7 +319,7 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { "versions": []map[string]any{ { "failed_builds": []map[string]any{ - {"build_number": int32(77), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(77), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, }, "failed_count": 1, "template_version_name": t1v2.Name, @@ -412,32 +412,32 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { "versions": []map[string]any{ { "failed_builds": []map[string]any{ - {"build_number": int32(23), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(22), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(21), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(20), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(19), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(18), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(17), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(16), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(15), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(14), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(23), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(22), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(21), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(20), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(19), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(18), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(17), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(16), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(15), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(14), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, }, "failed_count": 23, "template_version_name": t1v1.Name, }, { "failed_builds": []map[string]any{ - {"build_number": int32(123), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(122), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(121), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(120), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(119), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(118), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(117), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(116), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(115), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, - {"build_number": int32(114), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, + {"build_number": int32(123), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(122), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(121), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(120), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(119), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(118), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(117), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(116), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(115), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, + {"build_number": int32(114), "workspace_name": w1.Name, "workspace_id": w1.ID, "workspace_owner_username": user1.Username}, }, "failed_count": 23, "template_version_name": t1v2.Name, From 3ab9253f3f895f310cf0e09f5987fe5d426040d7 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 10 Apr 2025 11:04:30 +0000 Subject: [PATCH 6/8] chore: dbmem, need I say more? --- coderd/database/dbmem/dbmem.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index deafdc42e0216..cf8cf00ca9eed 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3291,6 +3291,7 @@ func (q *FakeQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, } workspaceBuildStats = append(workspaceBuildStats, database.GetFailedWorkspaceBuildsByTemplateIDRow{ + WorkspaceID: w.ID, WorkspaceName: w.Name, WorkspaceOwnerUsername: workspaceOwner.Username, TemplateVersionName: templateVersion.Name, From 6ab46780765a46fbd01e5cb17ebf1beda7c0398f Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 10 Apr 2025 11:39:58 +0000 Subject: [PATCH 7/8] chore: nit --- coderd/notifications/notifications_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 5f6c221e7beb5..2ceb1404d6940 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -978,7 +978,6 @@ func TestNotificationTemplates_Golden(t *testing.T) { UserName: "Bobby", UserEmail: "bobby@coder.com", UserUsername: "bobby", - Labels: map[string]string{}, // We need to use floats as `json.Unmarshal` unmarshal numbers in `map[string]any` to floats. Data: map[string]any{ "report_frequency": "week", From 962470b14889757cbe93ad3c54e510ea943e19c1 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 10 Apr 2025 12:08:44 +0000 Subject: [PATCH 8/8] chore: huh --- coderd/notifications/notifications_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 2ceb1404d6940..5f6c221e7beb5 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -978,6 +978,7 @@ func TestNotificationTemplates_Golden(t *testing.T) { UserName: "Bobby", UserEmail: "bobby@coder.com", UserUsername: "bobby", + Labels: map[string]string{}, // We need to use floats as `json.Unmarshal` unmarshal numbers in `map[string]any` to floats. Data: map[string]any{ "report_frequency": "week",