From eb06a763766b6371cb38b0030b5e8e599d36178f Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Wed, 23 Apr 2025 18:15:16 +0500 Subject: [PATCH] =?UTF-8?q?Revert=20"feat(coderd/notifications):=20group?= =?UTF-8?q?=20workspace=20build=20failure=20report=20(che=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4ca425deccc6abfab7b0d6a8b928f3e63cba3a68. --- coderd/database/dbmem/dbmem.go | 1 - ...group_build_failure_notifications.down.sql | 21 -- ...6_group_build_failure_notifications.up.sql | 29 --- coderd/database/queries.sql.go | 11 +- coderd/database/queries/workspacebuilds.sql | 1 - coderd/notifications/notifications_test.go | 111 +++------- coderd/notifications/reports/generator.go | 160 ++++++-------- .../reports/generator_internal_test.go | 202 +++++++----------- ...ateWorkspaceBuildsFailedReport.html.golden | 131 +++--------- ...ateWorkspaceBuildsFailedReport.json.golden | 129 ++++------- 10 files changed, 245 insertions(+), 551 deletions(-) delete mode 100644 coderd/database/migrations/000316_group_build_failure_notifications.down.sql delete mode 100644 coderd/database/migrations/000316_group_build_failure_notifications.up.sql diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 19cbc16e63d0b..87275b1051efe 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3283,7 +3283,6 @@ func (q *FakeQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, } workspaceBuildStats = append(workspaceBuildStats, database.GetFailedWorkspaceBuildsByTemplateIDRow{ - WorkspaceID: w.ID, WorkspaceName: w.Name, WorkspaceOwnerUsername: workspaceOwner.Username, TemplateVersionName: templateVersion.Name, diff --git a/coderd/database/migrations/000316_group_build_failure_notifications.down.sql b/coderd/database/migrations/000316_group_build_failure_notifications.down.sql deleted file mode 100644 index 3ea2e98ff19e1..0000000000000 --- a/coderd/database/migrations/000316_group_build_failure_notifications.down.sql +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index e3c4e79fc6d35..0000000000000 --- a/coderd/database/migrations/000316_group_build_failure_notifications.up.sql +++ /dev/null @@ -1,29 +0,0 @@ -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}} -**{{$template.display_name}}** -{{range $version := $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}} -{{end}} - -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/database/queries.sql.go b/coderd/database/queries.sql.go index 4268e802fe4a2..81004abcd8a50 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -15779,7 +15779,6 @@ 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 @@ -15818,11 +15817,10 @@ 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"` - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` - 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"` + WorkspaceBuildNumber int32 `db:"workspace_build_number" json:"workspace_build_number"` } func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg GetFailedWorkspaceBuildsByTemplateIDParams) ([]GetFailedWorkspaceBuildsByTemplateIDRow, error) { @@ -15838,7 +15836,6 @@ 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 34ef639a1694b..da349fa1441b3 100644 --- a/coderd/database/queries/workspacebuilds.sql +++ b/coderd/database/queries/workspacebuilds.sql @@ -213,7 +213,6 @@ 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/notifications_test.go b/coderd/notifications/notifications_test.go index e9cb3e413aee5..9bf31384234ed 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -978,102 +978,45 @@ func TestNotificationTemplates_Golden(t *testing.T) { UserName: "Bobby", UserEmail: "bobby@coder.com", UserUsername: "bobby", - Labels: map[string]string{}, + Labels: map[string]string{ + "template_name": "bobby-first-template", + "template_display_name": "Bobby First Template", + }, // 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", - "templates": []map[string]any{ + "template_versions": []map[string]any{ { - "name": "bobby-first-template", - "display_name": "Bobby First Template", - "failed_builds": 4.0, - "total_builds": 55.0, - "versions": []map[string]any{ + "template_version_name": "bobby-template-version-1", + "failed_count": 3.0, + "failed_builds": []map[string]any{ { - "template_version_name": "bobby-template-version-1", - "failed_count": 3.0, - "failed_builds": []map[string]any{ - { - "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, - }, - }, + "workspace_owner_username": "mtojek", + "workspace_name": "workspace-1", + "build_number": 1234.0, }, { - "template_version_name": "bobby-template-version-2", - "failed_count": 1.0, - "failed_builds": []map[string]any{ - { - "workspace_owner_username": "ben", - "workspace_name": "cool-workspace", - "workspace_id": "86fd99b1-1b6e-4b7e-b58e-0aee6e35c159", - "build_number": 8888.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, }, }, }, { - "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", - "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, - }, - }, - }, + "template_version_name": "bobby-template-version-2", + "failed_count": 1.0, + "failed_builds": []map[string]any{ { - "template_version_name": "bobby-template-version-2", - "failed_count": 2.0, - "failed_builds": []map[string]any{ - { - "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, - }, - }, + "workspace_owner_username": "ben", + "workspace_name": "cool-workspace", + "build_number": 8888.0, }, }, }, diff --git a/coderd/notifications/reports/generator.go b/coderd/notifications/reports/generator.go index 6b7dbd0c5b7b9..2424498146c60 100644 --- a/coderd/notifications/reports/generator.go +++ b/coderd/notifications/reports/generator.go @@ -18,7 +18,6 @@ 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" ) @@ -103,11 +102,6 @@ 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) @@ -142,8 +136,6 @@ 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(): @@ -173,40 +165,33 @@ 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) - for _, templateAdmin := range templateAdmins { - adminReports := reports[templateAdmin.ID] - adminReports = append(adminReports, adminReport{ - failedBuilds: failedBuilds, - stats: stats, - }) - - reports[templateAdmin.ID] = adminReports - } - } - - for templateAdmin, reports := range reports { - select { - case <-ctx.Done(): - logger.Debug(ctx, "context is canceled, quitting", slog.Error(ctx.Err())) - break - default: + // Send reports to template admins + templateDisplayName := stats.TemplateDisplayName + if templateDisplayName == "" { + templateDisplayName = stats.TemplateName } - reportData := buildDataForReportFailedWorkspaceBuilds(reports) - - targets := []uuid.UUID{} - for _, report := range reports { - targets = append(targets, report.stats.TemplateID, report.stats.TemplateOrganizationID) - } + for _, templateAdmin := range templateAdmins { + select { + case <-ctx.Done(): + logger.Debug(ctx, "context is canceled, quitting", slog.Error(ctx.Err())) + break + default: + } - 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)) + 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)) + } } } @@ -228,71 +213,54 @@ func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db dat const workspaceBuildsLimitPerTemplateVersion = 10 -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, - "workspace_id": failedBuild.WorkspaceID, - "build_number": failedBuild.WorkspaceBuildNumber, - }, +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, }, - }) - 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, - "workspace_id": failedBuild.WorkspaceID, - "build_number": failedBuild.WorkspaceBuildNumber, - }) - tv["failed_builds"] = builds - } - templateVersions[c-1] = tv + }, + }) + continue } - templateDisplayName := report.stats.TemplateDisplayName - if templateDisplayName == "" { - templateDisplayName = report.stats.TemplateName + 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 } - - 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, - }) + templateVersions[c-1] = tv } return map[string]any{ - "report_frequency": failedWorkspaceBuildsReportFrequencyLabel, - "templates": templates, + "failed_builds": stats.FailedBuilds, + "total_builds": stats.TotalBuilds, + "report_frequency": failedWorkspaceBuildsReportFrequencyLabel, + "template_versions": templateVersions, } } diff --git a/coderd/notifications/reports/generator_internal_test.go b/coderd/notifications/reports/generator_internal_test.go index c0215e4854e08..a4330493f0aed 100644 --- a/coderd/notifications/reports/generator_internal_test.go +++ b/coderd/notifications/reports/generator_internal_test.go @@ -3,7 +3,6 @@ package reports import ( "context" "database/sql" - "sort" "testing" "time" @@ -119,13 +118,17 @@ 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, recipientID uuid.UUID, notif *notificationstest.FakeNotification, templates []map[string]any) { + verifyNotification := func(t *testing.T, recipient database.User, notif *notificationstest.FakeNotification, tmpl database.Template, failedBuilds, totalBuilds int64, templateVersions []map[string]interface{}) { t.Helper() - require.Equal(t, recipientID, notif.UserID) + 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, templates, notif.Data["templates"]) + require.Equal(t, templateVersions, notif.Data["template_versions"]) } // Setup @@ -209,65 +212,43 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { require.NoError(t, err) sent := notifEnq.Sent() - require.Len(t, sent, 2) // 2 templates, 2 template admins - - 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() - }) + 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, + }, + }) + } - for i, templateAdmin := range templateAdmins { - verifyNotification(t, templateAdmin, sent[i], []map[string]any{ + for i, templateAdmin := range []database.User{templateAdmin1, templateAdmin2} { + verifyNotification(t, templateAdmin, sent[i+2], t2, 3, 5, []map[string]interface{}{ { - "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_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_id": w1.ID, "workspace_owner_username": user1.Username}, - }, - "failed_count": 1, - "template_version_name": t1v2.Name, - }, + "failed_builds": []map[string]interface{}{ + {"build_number": int32(8), "workspace_name": w4.Name, "workspace_owner_username": user2.Username}, }, + "failed_count": 1, + "template_version_name": t2v1.Name, }, { - "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_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_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, - }, + "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}, }, + "failed_count": 2, + "template_version_name": t2v2.Name, }, }) } @@ -298,33 +279,14 @@ 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 - - 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{ + for i, templateAdmin := range []database.User{templateAdmin1, templateAdmin2} { + verifyNotification(t, templateAdmin, sent[i], t1, 1, 1, []map[string]interface{}{ { - "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_id": w1.ID, "workspace_owner_username": user1.Username}, - }, - "failed_count": 1, - "template_version_name": t1v2.Name, - }, + "failed_builds": []map[string]interface{}{ + {"build_number": int32(77), "workspace_name": w1.Name, "workspace_owner_username": user1.Username}, }, + "failed_count": 1, + "template_version_name": t1v2.Name, }, }) } @@ -333,13 +295,17 @@ 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, templates []map[string]any) { + verifyNotification := func(t *testing.T, recipient database.User, notif *notificationstest.FakeNotification, tmpl database.Template, failedBuilds, totalBuilds int64, templateVersions []map[string]interface{}) { 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, templates, notif.Data["templates"]) + require.Equal(t, templateVersions, notif.Data["template_versions"]) } // Setup @@ -403,46 +369,38 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { sent := notifEnq.Sent() require.Len(t, sent, 1) // 1 template, 1 template admin - verifyNotification(t, templateAdmin1, sent[0], []map[string]any{ + verifyNotification(t, templateAdmin1, sent[0], t1, 46, 47, []map[string]interface{}{ { - "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_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_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, - }, + "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}, }, + "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 9699486bf9cc8..f3edc6ac05d02 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: Failed workspace builds report +Subject: Workspace builds failed for template "Bobby First Template" 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,51 +12,29 @@ Content-Type: text/plain; charset=UTF-8 Hi Bobby, -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 +Template Bobby First Template has failed to build 4/55 times over the last = +week. Report: -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 +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-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) +bobby-template-version-2 failed 1 time: +ben / cool-workspace / #8888 (http://test.com/@ben/cool-workspace/builds/88= +88) We recommend reviewing these issues to ensure future builds are successful. -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 +View workspaces: http://test.com/workspaces?filter=3Dtemplate%3Abobby-first= +-template --bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 Content-Transfer-Encoding: quoted-printable @@ -68,7 +46,8 @@ Content-Type: text/html; charset=UTF-8 - Failed workspace builds report + Workspace builds failed for template "Bobby First Template"</tit= +le> </head> <body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-= ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel= @@ -83,73 +62,35 @@ er Logo" style=3D"height: 40px;" /> </div> <h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m= argin: 8px 0 32px; line-height: 1.5;"> - Failed workspace builds report + Workspace builds failed for template "Bobby First Template" </h1> <div style=3D"line-height: 1.5;"> <p>Hi Bobby,</p> - <p>The following templates have had build failures over the last we= -ek:</p> - -<ul> -<li><p><strong>Bobby First Template</strong> failed to build <sup>4</sup>&f= -rasl;<sub>55</sub> times</p></li> - -<li><p><strong>Bobby Second Template</strong> failed to build <sup>5</sup>&= -frasl;<sub>50</sub> times</p></li> -</ul> + <p>Template <strong>Bobby First Template</strong> has failed to bui= +ld <sup>4</sup>⁄<sub>55</sub> times over the last week.</p> <p><strong>Report:</strong></p> -<p><strong>Bobby First Template</strong></p> - -<ul> -<li><p><strong>bobby-template-version-1</strong> failed 3 times:</p> +<p><strong>bobby-template-version-1</strong> failed 3 times:</p> <ul> -<li><p><a href=3D"http://test.com/@mtojek/workspace-1/builds/1234">mtojek /= - workspace-1 / #1234</a></p></li> - -<li><p><a href=3D"http://test.com/@johndoe/my-workspace-3/builds/5678">john= -doe / my-workspace-3 / #5678</a></p></li> - -<li><p><a href=3D"http://test.com/@jack/workwork/builds/774">jack / workwor= -k / #774</a></p></li> -</ul></li> +<li><a href=3D"http://test.com/@mtojek/workspace-1/builds/1234">mtojek / wo= +rkspace-1 / #1234</a><br> +</li> +<li><a href=3D"http://test.com/@johndoe/my-workspace-3/builds/5678">johndoe= + / my-workspace-3 / #5678</a><br> +</li> +<li><a href=3D"http://test.com/@jack/workwork/builds/774">jack / workwork /= + #774</a><br> +</li> +</ul> -<li><p><strong>bobby-template-version-2</strong> failed 1 time:</p> +<p><strong>bobby-template-version-2</strong> failed 1 time:</p> <ul> <li><a href=3D"http://test.com/@ben/cool-workspace/builds/8888">ben / cool-= workspace / #8888</a><br> </li> -</ul></li> -</ul> - -<p><strong>Bobby Second Template</strong></p> - -<ul> -<li><p><strong>bobby-template-version-1</strong> failed 3 times:</p> - -<ul> -<li><p><a href=3D"http://test.com/@daniellemaywood/workspace-9/builds/9234"= ->daniellemaywood / workspace-9 / #9234</a></p></li> - -<li><p><a href=3D"http://test.com/@johndoe/my-workspace-7/builds/8678">john= -doe / my-workspace-7 / #8678</a></p></li> - -<li><p><a href=3D"http://test.com/@jack/workworkwork/builds/374">jack / wor= -kworkwork / #374</a></p></li> -</ul></li> - -<li><p><strong>bobby-template-version-2</strong> failed 2 times:</p> - -<ul> -<li><p><a href=3D"http://test.com/@ben/more-cool-workspace/builds/8878">ben= - / more-cool-workspace / #8878</a></p></li> - -<li><p><a href=3D"http://test.com/@ben/less-cool-workspace/builds/8848">ben= - / less-cool-workspace / #8848</a></p></li> -</ul></li> </ul> <p>We recommend reviewing these issues to ensure future builds are successf= @@ -157,14 +98,10 @@ ul.</p> </div> <div style=3D"text-align: center; margin-top: 32px;"> =20 - <a href=3D"http://test.com/workspaces?filter=3Did%3A24f5bd8f-1566-4= -374-9734-c3efa0454dc7+id%3A372a194b-dcde-43f1-b7cf-8a2f3d3114a0+id%3A1386d2= -94-19c1-4351-89e2-6cae1afb9bfe+id%3A86fd99b1-1b6e-4b7e-b58e-0aee6e35c159+id= -%3Acd469690-b6eb-4123-b759-980be7a7b278+id%3Ac447d472-0800-4529-a836-788754= -d5e27d+id%3A919db6df-48f0-4dc1-b357-9036a2c40f86+id%3Ac8fb0652-9290-4bf2-a7= -11-71b910243ac2+id%3A703d718d-2234-4990-9a02-5b1df6cf462a" style=3D"display= -: inline-block; padding: 13px 24px; background-color: #020617; color: #f8fa= -fc; text-decoration: none; border-radius: 8px; margin: 0 4px;"> + <a href=3D"http://test.com/workspaces?filter=3Dtemplate%3Abobby-fir= +st-template" style=3D"display: inline-block; padding: 13px 24px; background= +-color: #020617; color: #f8fafc; text-decoration: none; border-radius: 8px;= + margin: 0 4px;"> View workspaces </a> =20 diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateWorkspaceBuildsFailedReport.json.golden index 78c8ba2a3195c..987d97b91c029 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", + "notification_name": "Report: Workspace Builds Failed For Template", "notification_template_id": "00000000-0000-0000-0000-000000000000", "user_id": "00000000-0000-0000-0000-000000000000", "user_email": "bobby@coder.com", @@ -12,113 +12,56 @@ "actions": [ { "label": "View workspaces", - "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" + "url": "http://test.com/workspaces?filter=template%3Abobby-first-template" } ], - "labels": {}, + "labels": { + "template_display_name": "Bobby First Template", + "template_name": "bobby-first-template" + }, "data": { + "failed_builds": 4, "report_frequency": "week", - "templates": [ + "template_versions": [ { - "display_name": "Bobby First Template", - "failed_builds": 4, - "name": "bobby-first-template", - "total_builds": 55, - "versions": [ + "failed_builds": [ + { + "build_number": 1234, + "workspace_name": "workspace-1", + "workspace_owner_username": "mtojek" + }, { - "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" - } - ], - "failed_count": 3, - "template_version_name": "bobby-template-version-1" + "build_number": 5678, + "workspace_name": "my-workspace-3", + "workspace_owner_username": "johndoe" }, { - "failed_builds": [ - { - "build_number": 8888, - "workspace_id": "00000000-0000-0000-0000-000000000000", - "workspace_name": "cool-workspace", - "workspace_owner_username": "ben" - } - ], - "failed_count": 1, - "template_version_name": "bobby-template-version-2" + "build_number": 774, + "workspace_name": "workwork", + "workspace_owner_username": "jack" } - ] + ], + "failed_count": 3, + "template_version_name": "bobby-template-version-1" }, { - "display_name": "Bobby Second Template", - "failed_builds": 5, - "name": "bobby-second-template", - "total_builds": 50, - "versions": [ - { - "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" - } - ], - "failed_count": 3, - "template_version_name": "bobby-template-version-1" - }, + "failed_builds": [ { - "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" - } - ], - "failed_count": 2, - "template_version_name": "bobby-template-version-2" + "build_number": 8888, + "workspace_name": "cool-workspace", + "workspace_owner_username": "ben" } - ] + ], + "failed_count": 1, + "template_version_name": "bobby-template-version-2" } - ] + ], + "total_builds": 55 }, "targets": null }, - "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\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." + "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." } \ No newline at end of file <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <title>pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy