@@ -4,11 +4,16 @@ import (
4
4
"context"
5
5
"database/sql"
6
6
"io"
7
+ "slices"
8
+ "sort"
7
9
"time"
8
10
9
- "cdr.dev/slog"
10
11
"golang.org/x/xerrors"
11
12
13
+ "cdr.dev/slog"
14
+
15
+ "github.com/google/uuid"
16
+
12
17
"github.com/coder/coder/v2/coderd/database"
13
18
"github.com/coder/coder/v2/coderd/database/dbauthz"
14
19
"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -91,16 +96,9 @@ func (i *reportGenerator) Close() error {
91
96
return nil
92
97
}
93
98
94
- func reportFailedWorkspaceBuilds (ctx context.Context , logger slog.Logger , db database.Store , _ notifications.Enqueuer , clk quartz.Clock ) error {
99
+ func reportFailedWorkspaceBuilds (ctx context.Context , logger slog.Logger , db database.Store , enqueuer notifications.Enqueuer , clk quartz.Clock ) error {
95
100
const frequencyDays = 7
96
101
97
- templateAdmins , err := db .GetUsers (ctx , database.GetUsersParams {
98
- RbacRole : []string {codersdk .RoleTemplateAdmin },
99
- })
100
- if err != nil {
101
- return xerrors .Errorf ("unable to fetch template admins: %w" , err )
102
- }
103
-
104
102
templates , err := db .GetTemplatesWithFilter (ctx , database.GetTemplatesWithFilterParams {
105
103
Deleted : false ,
106
104
Deprecated : sql.NullBool {Bool : false , Valid : true },
@@ -110,16 +108,69 @@ func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db dat
110
108
}
111
109
112
110
for _ , template := range templates {
113
- // 1. Fetch failed builds.
114
- // 2. If failed builds == 0, continue.
115
- // 3. Fetch template RW users.
116
- // 4. For user := range template admins + RW users:
117
- // 1. Check if report is enabled for the person.
118
- // 2. Check `report_generator_log`.
119
- // 3. If sent recently, continue
120
- // 4. Lazy-render the report.
121
- // 5. Send notification
122
- // 6. Upsert into `report_generator_log`.
111
+ failedBuilds , err := db .GetFailedWorkspaceBuildsByTemplateID (ctx , database.GetFailedWorkspaceBuildsByTemplateIDParams {
112
+ TemplateID : template .ID ,
113
+ Since : dbtime .Time (clk .Now ()).UTC (),
114
+ })
115
+ if err != nil {
116
+ logger .Error (ctx , "unable to fetch failed workspace builds" , slog .F ("template_id" , template .ID ), slog .Error (err ))
117
+ continue
118
+ }
119
+
120
+ templateAdmins , err := findTemplateAdmins (ctx , db , template )
121
+ if err != nil {
122
+ logger .Error (ctx , "unable to find template admins" , slog .F ("template_id" , template .ID ), slog .Error (err ))
123
+ continue
124
+ }
125
+
126
+ for _ , templateAdmin := range templateAdmins {
127
+ // TODO Check if report is enabled for the person.
128
+ // TODO Check `report_generator_log`.
129
+ // TODO If sent recently, continue
130
+
131
+ if len (failedBuilds ) == 0 {
132
+ err = db .UpsertReportGeneratorLog (ctx , database.UpsertReportGeneratorLogParams {
133
+ UserID : templateAdmin .ID ,
134
+ NotificationTemplateID : notifications .TemplateWorkspaceBuildsFailedReport ,
135
+ LastGeneratedAt : dbtime .Time (clk .Now ()).UTC (),
136
+ })
137
+ if err != nil {
138
+ logger .Error (ctx , "unable to update report generator logs" , slog .F ("template_id" , template .ID ), slog .F ("user_id" , templateAdmin .ID ), slog .F ("failed_builds" , len (failedBuilds )), slog .Error (err ))
139
+ continue
140
+ }
141
+ }
142
+
143
+ // TODO Lazy-render the report.
144
+ reportData := map [string ]any {}
145
+
146
+ templateDisplayName := template .DisplayName
147
+ if templateDisplayName == "" {
148
+ templateDisplayName = template .Name
149
+ }
150
+
151
+ if _ , err := enqueuer .EnqueueData (ctx , templateAdmin .ID , notifications .TemplateWorkspaceBuildsFailedReport ,
152
+ map [string ]string {
153
+ "template_name" : template .Name ,
154
+ "template_display_name" : templateDisplayName ,
155
+ },
156
+ reportData ,
157
+ "report_generator" ,
158
+ template .ID , template .OrganizationID ,
159
+ ); err != nil {
160
+ logger .Warn (ctx , "failed to send a report with failed workspace builds" , slog .Error (err ))
161
+ }
162
+
163
+ err = db .UpsertReportGeneratorLog (ctx , database.UpsertReportGeneratorLogParams {
164
+ UserID : templateAdmin .ID ,
165
+ NotificationTemplateID : notifications .TemplateWorkspaceBuildsFailedReport ,
166
+ LastGeneratedAt : dbtime .Time (clk .Now ()).UTC (),
167
+ })
168
+ if err != nil {
169
+ logger .Error (ctx , "unable to update report generator logs" , slog .F ("template_id" , template .ID ), slog .F ("user_id" , templateAdmin .ID ), slog .F ("failed_builds" , len (failedBuilds )), slog .Error (err ))
170
+ continue
171
+ }
172
+ }
173
+
123
174
}
124
175
125
176
err = db .DeleteOldReportGeneratorLogs (ctx , frequencyDays )
@@ -128,3 +179,38 @@ func reportFailedWorkspaceBuilds(ctx context.Context, logger slog.Logger, db dat
128
179
}
129
180
return nil
130
181
}
182
+
183
+ func findTemplateAdmins (ctx context.Context , db database.Store , template database.Template ) ([]database.GetUsersRow , error ) {
184
+ users , err := db .GetUsers (ctx , database.GetUsersParams {
185
+ RbacRole : []string {codersdk .RoleTemplateAdmin },
186
+ })
187
+ if err != nil {
188
+ return nil , xerrors .Errorf ("unable to fetch template admins: %w" , err )
189
+ }
190
+
191
+ usersByIDs := map [uuid.UUID ]database.GetUsersRow {}
192
+ var userIDs []uuid.UUID
193
+ for _ , user := range users {
194
+ usersByIDs [user .ID ] = user
195
+ userIDs = append (userIDs , user .ID )
196
+ }
197
+
198
+ var templateAdmins []database.GetUsersRow
199
+ if len (userIDs ) > 0 {
200
+ orgIDsByMemberIDs , err := db .GetOrganizationIDsByMemberIDs (ctx , userIDs )
201
+ if err != nil {
202
+ return nil , xerrors .Errorf ("unable to fetch organization IDs by member IDs: %w" , err )
203
+ }
204
+
205
+ for _ , entry := range orgIDsByMemberIDs {
206
+ if slices .Contains (entry .OrganizationIDs , template .OrganizationID ) {
207
+ templateAdmins = append (templateAdmins , usersByIDs [entry .UserID ])
208
+ }
209
+ }
210
+ }
211
+ sort .Slice (templateAdmins , func (i , j int ) bool {
212
+ return templateAdmins [i ].Username < templateAdmins [j ].Username
213
+ })
214
+
215
+ return templateAdmins , nil
216
+ }
0 commit comments