mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: notifications: report failed workspace builds (#14571)
This commit is contained in:
@ -1459,6 +1459,13 @@ func (q *querier) GetExternalAuthLinksByUserID(ctx context.Context, userID uuid.
|
||||
return fetchWithPostFilter(q.auth, policy.ActionReadPersonal, q.db.GetExternalAuthLinksByUserID)(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg database.GetFailedWorkspaceBuildsByTemplateIDParams) ([]database.GetFailedWorkspaceBuildsByTemplateIDRow, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetFailedWorkspaceBuildsByTemplateID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetFileByHashAndCreator(ctx context.Context, arg database.GetFileByHashAndCreatorParams) (database.File, error) {
|
||||
file, err := q.db.GetFileByHashAndCreator(ctx, arg)
|
||||
if err != nil {
|
||||
@ -1628,6 +1635,13 @@ func (q *querier) GetNotificationMessagesByStatus(ctx context.Context, arg datab
|
||||
return q.db.GetNotificationMessagesByStatus(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetNotificationReportGeneratorLogByTemplate(ctx context.Context, arg uuid.UUID) (database.NotificationReportGeneratorLog, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return database.NotificationReportGeneratorLog{}, err
|
||||
}
|
||||
return q.db.GetNotificationReportGeneratorLogByTemplate(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceNotificationTemplate); err != nil {
|
||||
return database.NotificationTemplate{}, err
|
||||
@ -2510,6 +2524,13 @@ func (q *querier) GetWorkspaceBuildParameters(ctx context.Context, workspaceBuil
|
||||
return q.db.GetWorkspaceBuildParameters(ctx, workspaceBuildID)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceBuildStatsByTemplates(ctx, since)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg database.GetWorkspaceBuildsByWorkspaceIDParams) ([]database.WorkspaceBuild, error) {
|
||||
if _, err := q.GetWorkspaceByID(ctx, arg.WorkspaceID); err != nil {
|
||||
return nil, err
|
||||
@ -3966,6 +3987,13 @@ func (q *querier) UpsertLogoURL(ctx context.Context, value string) error {
|
||||
return q.db.UpsertLogoURL(ctx, value)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertNotificationReportGeneratorLog(ctx context.Context, arg database.UpsertNotificationReportGeneratorLogParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpsertNotificationReportGeneratorLog(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertNotificationsSettings(ctx context.Context, value string) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return err
|
||||
|
@ -2819,6 +2819,28 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
Value: "value",
|
||||
}).Asserts(rbac.ResourceSystem, policy.ActionCreate)
|
||||
}))
|
||||
s.Run("GetFailedWorkspaceBuildsByTemplateID", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.GetFailedWorkspaceBuildsByTemplateIDParams{
|
||||
TemplateID: uuid.New(),
|
||||
Since: dbtime.Now(),
|
||||
}).Asserts(rbac.ResourceSystem, policy.ActionRead)
|
||||
}))
|
||||
s.Run("GetNotificationReportGeneratorLogByTemplate", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = db.UpsertNotificationReportGeneratorLog(context.Background(), database.UpsertNotificationReportGeneratorLogParams{
|
||||
NotificationTemplateID: notifications.TemplateWorkspaceBuildsFailedReport,
|
||||
LastGeneratedAt: dbtime.Now(),
|
||||
})
|
||||
check.Args(notifications.TemplateWorkspaceBuildsFailedReport).Asserts(rbac.ResourceSystem, policy.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceBuildStatsByTemplates", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(dbtime.Now()).Asserts(rbac.ResourceSystem, policy.ActionRead)
|
||||
}))
|
||||
s.Run("UpsertNotificationReportGeneratorLog", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.UpsertNotificationReportGeneratorLogParams{
|
||||
NotificationTemplateID: uuid.New(),
|
||||
LastGeneratedAt: dbtime.Now(),
|
||||
}).Asserts(rbac.ResourceSystem, policy.ActionCreate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestNotifications() {
|
||||
|
@ -187,53 +187,54 @@ type data struct {
|
||||
userLinks []database.UserLink
|
||||
|
||||
// New tables
|
||||
workspaceAgentStats []database.WorkspaceAgentStat
|
||||
auditLogs []database.AuditLog
|
||||
cryptoKeys []database.CryptoKey
|
||||
dbcryptKeys []database.DBCryptKey
|
||||
files []database.File
|
||||
externalAuthLinks []database.ExternalAuthLink
|
||||
gitSSHKey []database.GitSSHKey
|
||||
groupMembers []database.GroupMemberTable
|
||||
groups []database.Group
|
||||
jfrogXRayScans []database.JfrogXrayScan
|
||||
licenses []database.License
|
||||
notificationMessages []database.NotificationMessage
|
||||
notificationPreferences []database.NotificationPreference
|
||||
oauth2ProviderApps []database.OAuth2ProviderApp
|
||||
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
|
||||
oauth2ProviderAppCodes []database.OAuth2ProviderAppCode
|
||||
oauth2ProviderAppTokens []database.OAuth2ProviderAppToken
|
||||
parameterSchemas []database.ParameterSchema
|
||||
provisionerDaemons []database.ProvisionerDaemon
|
||||
provisionerJobLogs []database.ProvisionerJobLog
|
||||
provisionerJobs []database.ProvisionerJob
|
||||
provisionerKeys []database.ProvisionerKey
|
||||
replicas []database.Replica
|
||||
templateVersions []database.TemplateVersionTable
|
||||
templateVersionParameters []database.TemplateVersionParameter
|
||||
templateVersionVariables []database.TemplateVersionVariable
|
||||
templateVersionWorkspaceTags []database.TemplateVersionWorkspaceTag
|
||||
templates []database.TemplateTable
|
||||
templateUsageStats []database.TemplateUsageStat
|
||||
workspaceAgents []database.WorkspaceAgent
|
||||
workspaceAgentMetadata []database.WorkspaceAgentMetadatum
|
||||
workspaceAgentLogs []database.WorkspaceAgentLog
|
||||
workspaceAgentLogSources []database.WorkspaceAgentLogSource
|
||||
workspaceAgentScripts []database.WorkspaceAgentScript
|
||||
workspaceAgentPortShares []database.WorkspaceAgentPortShare
|
||||
workspaceApps []database.WorkspaceApp
|
||||
workspaceAppStatsLastInsertID int64
|
||||
workspaceAppStats []database.WorkspaceAppStat
|
||||
workspaceBuilds []database.WorkspaceBuild
|
||||
workspaceBuildParameters []database.WorkspaceBuildParameter
|
||||
workspaceResourceMetadata []database.WorkspaceResourceMetadatum
|
||||
workspaceResources []database.WorkspaceResource
|
||||
workspaces []database.Workspace
|
||||
workspaceProxies []database.WorkspaceProxy
|
||||
customRoles []database.CustomRole
|
||||
provisionerJobTimings []database.ProvisionerJobTiming
|
||||
runtimeConfig map[string]string
|
||||
auditLogs []database.AuditLog
|
||||
cryptoKeys []database.CryptoKey
|
||||
dbcryptKeys []database.DBCryptKey
|
||||
files []database.File
|
||||
externalAuthLinks []database.ExternalAuthLink
|
||||
gitSSHKey []database.GitSSHKey
|
||||
groupMembers []database.GroupMemberTable
|
||||
groups []database.Group
|
||||
jfrogXRayScans []database.JfrogXrayScan
|
||||
licenses []database.License
|
||||
notificationMessages []database.NotificationMessage
|
||||
notificationPreferences []database.NotificationPreference
|
||||
notificationReportGeneratorLogs []database.NotificationReportGeneratorLog
|
||||
oauth2ProviderApps []database.OAuth2ProviderApp
|
||||
oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret
|
||||
oauth2ProviderAppCodes []database.OAuth2ProviderAppCode
|
||||
oauth2ProviderAppTokens []database.OAuth2ProviderAppToken
|
||||
parameterSchemas []database.ParameterSchema
|
||||
provisionerDaemons []database.ProvisionerDaemon
|
||||
provisionerJobLogs []database.ProvisionerJobLog
|
||||
provisionerJobs []database.ProvisionerJob
|
||||
provisionerKeys []database.ProvisionerKey
|
||||
replicas []database.Replica
|
||||
templateVersions []database.TemplateVersionTable
|
||||
templateVersionParameters []database.TemplateVersionParameter
|
||||
templateVersionVariables []database.TemplateVersionVariable
|
||||
templateVersionWorkspaceTags []database.TemplateVersionWorkspaceTag
|
||||
templates []database.TemplateTable
|
||||
templateUsageStats []database.TemplateUsageStat
|
||||
workspaceAgents []database.WorkspaceAgent
|
||||
workspaceAgentMetadata []database.WorkspaceAgentMetadatum
|
||||
workspaceAgentLogs []database.WorkspaceAgentLog
|
||||
workspaceAgentLogSources []database.WorkspaceAgentLogSource
|
||||
workspaceAgentPortShares []database.WorkspaceAgentPortShare
|
||||
workspaceAgentScripts []database.WorkspaceAgentScript
|
||||
workspaceAgentStats []database.WorkspaceAgentStat
|
||||
workspaceApps []database.WorkspaceApp
|
||||
workspaceAppStatsLastInsertID int64
|
||||
workspaceAppStats []database.WorkspaceAppStat
|
||||
workspaceBuilds []database.WorkspaceBuild
|
||||
workspaceBuildParameters []database.WorkspaceBuildParameter
|
||||
workspaceResourceMetadata []database.WorkspaceResourceMetadatum
|
||||
workspaceResources []database.WorkspaceResource
|
||||
workspaces []database.Workspace
|
||||
workspaceProxies []database.WorkspaceProxy
|
||||
customRoles []database.CustomRole
|
||||
provisionerJobTimings []database.ProvisionerJobTiming
|
||||
runtimeConfig map[string]string
|
||||
// Locks is a map of lock names. Any keys within the map are currently
|
||||
// locked.
|
||||
locks map[int64]struct{}
|
||||
@ -2621,6 +2622,75 @@ func (q *FakeQuerier) GetExternalAuthLinksByUserID(_ context.Context, userID uui
|
||||
return gals, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg database.GetFailedWorkspaceBuildsByTemplateIDParams) ([]database.GetFailedWorkspaceBuildsByTemplateIDRow, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
workspaceBuildStats := []database.GetFailedWorkspaceBuildsByTemplateIDRow{}
|
||||
for _, wb := range q.workspaceBuilds {
|
||||
job, err := q.getProvisionerJobByIDNoLock(ctx, wb.JobID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get provisioner job by ID: %w", err)
|
||||
}
|
||||
|
||||
if job.JobStatus != database.ProvisionerJobStatusFailed {
|
||||
continue
|
||||
}
|
||||
|
||||
if !job.CompletedAt.Valid {
|
||||
continue
|
||||
}
|
||||
|
||||
if wb.CreatedAt.Before(arg.Since) {
|
||||
continue
|
||||
}
|
||||
|
||||
w, err := q.getWorkspaceByIDNoLock(ctx, wb.WorkspaceID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get workspace by ID: %w", err)
|
||||
}
|
||||
|
||||
t, err := q.getTemplateByIDNoLock(ctx, w.TemplateID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template by ID: %w", err)
|
||||
}
|
||||
|
||||
if t.ID != arg.TemplateID {
|
||||
continue
|
||||
}
|
||||
|
||||
workspaceOwner, err := q.getUserByIDNoLock(w.OwnerID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get user by ID: %w", err)
|
||||
}
|
||||
|
||||
templateVersion, err := q.getTemplateVersionByIDNoLock(ctx, wb.TemplateVersionID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template version by ID: %w", err)
|
||||
}
|
||||
|
||||
workspaceBuildStats = append(workspaceBuildStats, database.GetFailedWorkspaceBuildsByTemplateIDRow{
|
||||
WorkspaceName: w.Name,
|
||||
WorkspaceOwnerUsername: workspaceOwner.Username,
|
||||
TemplateVersionName: templateVersion.Name,
|
||||
WorkspaceBuildNumber: wb.BuildNumber,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(workspaceBuildStats, func(i, j int) bool {
|
||||
if workspaceBuildStats[i].TemplateVersionName != workspaceBuildStats[j].TemplateVersionName {
|
||||
return workspaceBuildStats[i].TemplateVersionName < workspaceBuildStats[j].TemplateVersionName
|
||||
}
|
||||
return workspaceBuildStats[i].WorkspaceBuildNumber > workspaceBuildStats[j].WorkspaceBuildNumber
|
||||
})
|
||||
return workspaceBuildStats, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetFileByHashAndCreator(_ context.Context, arg database.GetFileByHashAndCreatorParams) (database.File, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.File{}, err
|
||||
@ -3044,6 +3114,23 @@ func (q *FakeQuerier) GetNotificationMessagesByStatus(_ context.Context, arg dat
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetNotificationReportGeneratorLogByTemplate(_ context.Context, templateID uuid.UUID) (database.NotificationReportGeneratorLog, error) {
|
||||
err := validateDatabaseType(templateID)
|
||||
if err != nil {
|
||||
return database.NotificationReportGeneratorLog{}, err
|
||||
}
|
||||
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, record := range q.notificationReportGeneratorLogs {
|
||||
if record.NotificationTemplateID == templateID {
|
||||
return record, nil
|
||||
}
|
||||
}
|
||||
return database.NotificationReportGeneratorLog{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (*FakeQuerier) GetNotificationTemplateByID(_ context.Context, _ uuid.UUID) (database.NotificationTemplate, error) {
|
||||
// Not implementing this function because it relies on state in the database which is created with migrations.
|
||||
// We could consider using code-generation to align the database state and dbmem, but it's not worth it right now.
|
||||
@ -5964,6 +6051,63 @@ func (q *FakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBu
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
templateStats := map[uuid.UUID]database.GetWorkspaceBuildStatsByTemplatesRow{}
|
||||
for _, wb := range q.workspaceBuilds {
|
||||
job, err := q.getProvisionerJobByIDNoLock(ctx, wb.JobID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get provisioner job by ID: %w", err)
|
||||
}
|
||||
|
||||
if !job.CompletedAt.Valid {
|
||||
continue
|
||||
}
|
||||
|
||||
if wb.CreatedAt.Before(since) {
|
||||
continue
|
||||
}
|
||||
|
||||
w, err := q.getWorkspaceByIDNoLock(ctx, wb.WorkspaceID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get workspace by ID: %w", err)
|
||||
}
|
||||
|
||||
if _, ok := templateStats[w.TemplateID]; !ok {
|
||||
t, err := q.getTemplateByIDNoLock(ctx, w.TemplateID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template by ID: %w", err)
|
||||
}
|
||||
|
||||
templateStats[w.TemplateID] = database.GetWorkspaceBuildStatsByTemplatesRow{
|
||||
TemplateID: w.TemplateID,
|
||||
TemplateName: t.Name,
|
||||
TemplateDisplayName: t.DisplayName,
|
||||
TemplateOrganizationID: w.OrganizationID,
|
||||
}
|
||||
}
|
||||
|
||||
s := templateStats[w.TemplateID]
|
||||
s.TotalBuilds++
|
||||
if job.JobStatus == database.ProvisionerJobStatusFailed {
|
||||
s.FailedBuilds++
|
||||
}
|
||||
templateStats[w.TemplateID] = s
|
||||
}
|
||||
|
||||
rows := make([]database.GetWorkspaceBuildStatsByTemplatesRow, 0, len(templateStats))
|
||||
for _, ts := range templateStats {
|
||||
rows = append(rows, ts)
|
||||
}
|
||||
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
return rows[i].TemplateName < rows[j].TemplateName
|
||||
})
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context,
|
||||
params database.GetWorkspaceBuildsByWorkspaceIDParams,
|
||||
) ([]database.WorkspaceBuild, error) {
|
||||
@ -9440,6 +9584,26 @@ func (q *FakeQuerier) UpsertLogoURL(_ context.Context, data string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertNotificationReportGeneratorLog(_ context.Context, arg database.UpsertNotificationReportGeneratorLogParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, record := range q.notificationReportGeneratorLogs {
|
||||
if arg.NotificationTemplateID == record.NotificationTemplateID {
|
||||
q.notificationReportGeneratorLogs[i].LastGeneratedAt = arg.LastGeneratedAt
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
q.notificationReportGeneratorLogs = append(q.notificationReportGeneratorLogs, database.NotificationReportGeneratorLog(arg))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertNotificationsSettings(_ context.Context, data string) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
@ -634,6 +634,13 @@ func (m metricsStore) GetExternalAuthLinksByUserID(ctx context.Context, userID u
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg database.GetFailedWorkspaceBuildsByTemplateIDParams) ([]database.GetFailedWorkspaceBuildsByTemplateIDRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetFailedWorkspaceBuildsByTemplateID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("GetFailedWorkspaceBuildsByTemplateID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetFileByHashAndCreator(ctx context.Context, arg database.GetFileByHashAndCreatorParams) (database.File, error) {
|
||||
start := time.Now()
|
||||
file, err := m.s.GetFileByHashAndCreator(ctx, arg)
|
||||
@ -788,6 +795,13 @@ func (m metricsStore) GetNotificationMessagesByStatus(ctx context.Context, arg d
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetNotificationReportGeneratorLogByTemplate(ctx context.Context, arg uuid.UUID) (database.NotificationReportGeneratorLog, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetNotificationReportGeneratorLogByTemplate(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("GetNotificationReportGeneratorLogByTemplate").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetNotificationTemplateByID(ctx, id)
|
||||
@ -1474,6 +1488,13 @@ func (m metricsStore) GetWorkspaceBuildParameters(ctx context.Context, workspace
|
||||
return params, err
|
||||
}
|
||||
|
||||
func (m metricsStore) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetWorkspaceBuildStatsByTemplates(ctx, since)
|
||||
m.queryLatencies.WithLabelValues("GetWorkspaceBuildStatsByTemplates").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg database.GetWorkspaceBuildsByWorkspaceIDParams) ([]database.WorkspaceBuild, error) {
|
||||
start := time.Now()
|
||||
builds, err := m.s.GetWorkspaceBuildsByWorkspaceID(ctx, arg)
|
||||
@ -2517,6 +2538,13 @@ func (m metricsStore) UpsertLogoURL(ctx context.Context, value string) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertNotificationReportGeneratorLog(ctx context.Context, arg database.UpsertNotificationReportGeneratorLogParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertNotificationReportGeneratorLog(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpsertNotificationReportGeneratorLog").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertNotificationsSettings(ctx context.Context, value string) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertNotificationsSettings(ctx, value)
|
||||
|
@ -1253,6 +1253,21 @@ func (mr *MockStoreMockRecorder) GetExternalAuthLinksByUserID(arg0, arg1 any) *g
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExternalAuthLinksByUserID", reflect.TypeOf((*MockStore)(nil).GetExternalAuthLinksByUserID), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetFailedWorkspaceBuildsByTemplateID mocks base method.
|
||||
func (m *MockStore) GetFailedWorkspaceBuildsByTemplateID(arg0 context.Context, arg1 database.GetFailedWorkspaceBuildsByTemplateIDParams) ([]database.GetFailedWorkspaceBuildsByTemplateIDRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetFailedWorkspaceBuildsByTemplateID", arg0, arg1)
|
||||
ret0, _ := ret[0].([]database.GetFailedWorkspaceBuildsByTemplateIDRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetFailedWorkspaceBuildsByTemplateID indicates an expected call of GetFailedWorkspaceBuildsByTemplateID.
|
||||
func (mr *MockStoreMockRecorder) GetFailedWorkspaceBuildsByTemplateID(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFailedWorkspaceBuildsByTemplateID", reflect.TypeOf((*MockStore)(nil).GetFailedWorkspaceBuildsByTemplateID), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetFileByHashAndCreator mocks base method.
|
||||
func (m *MockStore) GetFileByHashAndCreator(arg0 context.Context, arg1 database.GetFileByHashAndCreatorParams) (database.File, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -1583,6 +1598,21 @@ func (mr *MockStoreMockRecorder) GetNotificationMessagesByStatus(arg0, arg1 any)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationMessagesByStatus", reflect.TypeOf((*MockStore)(nil).GetNotificationMessagesByStatus), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetNotificationReportGeneratorLogByTemplate mocks base method.
|
||||
func (m *MockStore) GetNotificationReportGeneratorLogByTemplate(arg0 context.Context, arg1 uuid.UUID) (database.NotificationReportGeneratorLog, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetNotificationReportGeneratorLogByTemplate", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.NotificationReportGeneratorLog)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetNotificationReportGeneratorLogByTemplate indicates an expected call of GetNotificationReportGeneratorLogByTemplate.
|
||||
func (mr *MockStoreMockRecorder) GetNotificationReportGeneratorLogByTemplate(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationReportGeneratorLogByTemplate", reflect.TypeOf((*MockStore)(nil).GetNotificationReportGeneratorLogByTemplate), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetNotificationTemplateByID mocks base method.
|
||||
func (m *MockStore) GetNotificationTemplateByID(arg0 context.Context, arg1 uuid.UUID) (database.NotificationTemplate, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -3083,6 +3113,21 @@ func (mr *MockStoreMockRecorder) GetWorkspaceBuildParameters(arg0, arg1 any) *go
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceBuildParameters", reflect.TypeOf((*MockStore)(nil).GetWorkspaceBuildParameters), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetWorkspaceBuildStatsByTemplates mocks base method.
|
||||
func (m *MockStore) GetWorkspaceBuildStatsByTemplates(arg0 context.Context, arg1 time.Time) ([]database.GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetWorkspaceBuildStatsByTemplates", arg0, arg1)
|
||||
ret0, _ := ret[0].([]database.GetWorkspaceBuildStatsByTemplatesRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetWorkspaceBuildStatsByTemplates indicates an expected call of GetWorkspaceBuildStatsByTemplates.
|
||||
func (mr *MockStoreMockRecorder) GetWorkspaceBuildStatsByTemplates(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceBuildStatsByTemplates", reflect.TypeOf((*MockStore)(nil).GetWorkspaceBuildStatsByTemplates), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetWorkspaceBuildsByWorkspaceID mocks base method.
|
||||
func (m *MockStore) GetWorkspaceBuildsByWorkspaceID(arg0 context.Context, arg1 database.GetWorkspaceBuildsByWorkspaceIDParams) ([]database.WorkspaceBuild, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -5287,6 +5332,20 @@ func (mr *MockStoreMockRecorder) UpsertLogoURL(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertLogoURL", reflect.TypeOf((*MockStore)(nil).UpsertLogoURL), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertNotificationReportGeneratorLog mocks base method.
|
||||
func (m *MockStore) UpsertNotificationReportGeneratorLog(arg0 context.Context, arg1 database.UpsertNotificationReportGeneratorLogParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertNotificationReportGeneratorLog", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpsertNotificationReportGeneratorLog indicates an expected call of UpsertNotificationReportGeneratorLog.
|
||||
func (mr *MockStoreMockRecorder) UpsertNotificationReportGeneratorLog(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertNotificationReportGeneratorLog", reflect.TypeOf((*MockStore)(nil).UpsertNotificationReportGeneratorLog), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertNotificationsSettings mocks base method.
|
||||
func (m *MockStore) UpsertNotificationsSettings(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
10
coderd/database/dump.sql
generated
10
coderd/database/dump.sql
generated
@ -751,6 +751,13 @@ CREATE TABLE notification_preferences (
|
||||
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE notification_report_generator_logs (
|
||||
notification_template_id uuid NOT NULL,
|
||||
last_generated_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE notification_report_generator_logs IS 'Log of generated reports for users.';
|
||||
|
||||
CREATE TABLE notification_templates (
|
||||
id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
@ -1726,6 +1733,9 @@ ALTER TABLE ONLY notification_messages
|
||||
ALTER TABLE ONLY notification_preferences
|
||||
ADD CONSTRAINT notification_preferences_pkey PRIMARY KEY (user_id, notification_template_id);
|
||||
|
||||
ALTER TABLE ONLY notification_report_generator_logs
|
||||
ADD CONSTRAINT notification_report_generator_logs_pkey PRIMARY KEY (notification_template_id);
|
||||
|
||||
ALTER TABLE ONLY notification_templates
|
||||
ADD CONSTRAINT notification_templates_name_key UNIQUE (name);
|
||||
|
||||
|
@ -10,6 +10,7 @@ const (
|
||||
LockIDEnterpriseDeploymentSetup
|
||||
LockIDDBRollup
|
||||
LockIDDBPurge
|
||||
LockIDNotificationsReportGenerator
|
||||
)
|
||||
|
||||
// GenLockID generates a unique and consistent lock ID from a given string.
|
||||
|
3
coderd/database/migrations/000253_email_reports.down.sql
Normal file
3
coderd/database/migrations/000253_email_reports.down.sql
Normal file
@ -0,0 +1,3 @@
|
||||
DELETE FROM notification_templates WHERE id = '34a20db2-e9cc-4a93-b0e4-8569699d7a00';
|
||||
|
||||
DROP TABLE notification_report_generator_logs;
|
30
coderd/database/migrations/000253_email_reports.up.sql
Normal file
30
coderd/database/migrations/000253_email_reports.up.sql
Normal file
@ -0,0 +1,30 @@
|
||||
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
|
||||
VALUES ('34a20db2-e9cc-4a93-b0e4-8569699d7a00', 'Report: Workspace Builds Failed For Template', E'Workspace builds failed for template "{{.Labels.template_display_name}}"',
|
||||
E'Hi {{.UserName}},
|
||||
|
||||
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}}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.',
|
||||
'Template Events', '[
|
||||
{
|
||||
"label": "View workspaces",
|
||||
"url": "{{ base_url }}/workspaces?filter=template%3A{{.Labels.template_name}}"
|
||||
}
|
||||
]'::jsonb);
|
||||
|
||||
CREATE TABLE notification_report_generator_logs
|
||||
(
|
||||
notification_template_id uuid NOT NULL,
|
||||
last_generated_at timestamp with time zone NOT NULL,
|
||||
|
||||
PRIMARY KEY (notification_template_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE notification_report_generator_logs IS 'Log of generated reports for users.';
|
@ -268,6 +268,7 @@ func TestMigrateUpWithFixtures(t *testing.T) {
|
||||
"template_version_variables",
|
||||
"dbcrypt_keys", // having zero rows is a valid state for this table
|
||||
"template_version_workspace_tags",
|
||||
"notification_report_generator_logs",
|
||||
}
|
||||
s := &tableStats{s: make(map[string]int)}
|
||||
|
||||
|
@ -2262,6 +2262,12 @@ type NotificationPreference struct {
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// Log of generated reports for users.
|
||||
type NotificationReportGeneratorLog struct {
|
||||
NotificationTemplateID uuid.UUID `db:"notification_template_id" json:"notification_template_id"`
|
||||
LastGeneratedAt time.Time `db:"last_generated_at" json:"last_generated_at"`
|
||||
}
|
||||
|
||||
// Templates from which to create notification messages.
|
||||
type NotificationTemplate struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
|
@ -144,6 +144,7 @@ type sqlcQuerier interface {
|
||||
GetDeploymentWorkspaceStats(ctx context.Context) (GetDeploymentWorkspaceStatsRow, error)
|
||||
GetExternalAuthLink(ctx context.Context, arg GetExternalAuthLinkParams) (ExternalAuthLink, error)
|
||||
GetExternalAuthLinksByUserID(ctx context.Context, userID uuid.UUID) ([]ExternalAuthLink, error)
|
||||
GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg GetFailedWorkspaceBuildsByTemplateIDParams) ([]GetFailedWorkspaceBuildsByTemplateIDRow, error)
|
||||
GetFileByHashAndCreator(ctx context.Context, arg GetFileByHashAndCreatorParams) (File, error)
|
||||
GetFileByID(ctx context.Context, id uuid.UUID) (File, error)
|
||||
// Get all templates that use a file.
|
||||
@ -170,6 +171,8 @@ type sqlcQuerier interface {
|
||||
GetLicenses(ctx context.Context) ([]License, error)
|
||||
GetLogoURL(ctx context.Context) (string, error)
|
||||
GetNotificationMessagesByStatus(ctx context.Context, arg GetNotificationMessagesByStatusParams) ([]NotificationMessage, error)
|
||||
// Fetch the notification report generator log indicating recent activity.
|
||||
GetNotificationReportGeneratorLogByTemplate(ctx context.Context, templateID uuid.UUID) (NotificationReportGeneratorLog, error)
|
||||
GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error)
|
||||
GetNotificationTemplatesByKind(ctx context.Context, kind NotificationTemplateKind) ([]NotificationTemplate, error)
|
||||
GetNotificationsSettings(ctx context.Context) (string, error)
|
||||
@ -307,6 +310,7 @@ type sqlcQuerier interface {
|
||||
GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error)
|
||||
GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error)
|
||||
GetWorkspaceBuildParameters(ctx context.Context, workspaceBuildID uuid.UUID) ([]WorkspaceBuildParameter, error)
|
||||
GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]GetWorkspaceBuildStatsByTemplatesRow, error)
|
||||
GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuild, error)
|
||||
GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error)
|
||||
GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (GetWorkspaceByAgentIDRow, error)
|
||||
@ -489,6 +493,8 @@ type sqlcQuerier interface {
|
||||
UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error
|
||||
UpsertLastUpdateCheck(ctx context.Context, value string) error
|
||||
UpsertLogoURL(ctx context.Context, value string) error
|
||||
// Insert or update notification report generator logs with recent activity.
|
||||
UpsertNotificationReportGeneratorLog(ctx context.Context, arg UpsertNotificationReportGeneratorLogParams) error
|
||||
UpsertNotificationsSettings(ctx context.Context, value string) error
|
||||
UpsertOAuthSigningKey(ctx context.Context, value string) error
|
||||
UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error)
|
||||
|
@ -3879,6 +3879,23 @@ func (q *sqlQuerier) GetNotificationMessagesByStatus(ctx context.Context, arg Ge
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getNotificationReportGeneratorLogByTemplate = `-- name: GetNotificationReportGeneratorLogByTemplate :one
|
||||
SELECT
|
||||
notification_template_id, last_generated_at
|
||||
FROM
|
||||
notification_report_generator_logs
|
||||
WHERE
|
||||
notification_template_id = $1::uuid
|
||||
`
|
||||
|
||||
// Fetch the notification report generator log indicating recent activity.
|
||||
func (q *sqlQuerier) GetNotificationReportGeneratorLogByTemplate(ctx context.Context, templateID uuid.UUID) (NotificationReportGeneratorLog, error) {
|
||||
row := q.db.QueryRowContext(ctx, getNotificationReportGeneratorLogByTemplate, templateID)
|
||||
var i NotificationReportGeneratorLog
|
||||
err := row.Scan(&i.NotificationTemplateID, &i.LastGeneratedAt)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getNotificationTemplateByID = `-- name: GetNotificationTemplateByID :one
|
||||
SELECT id, name, title_template, body_template, actions, "group", method, kind
|
||||
FROM notification_templates
|
||||
@ -4028,6 +4045,23 @@ func (q *sqlQuerier) UpdateUserNotificationPreferences(ctx context.Context, arg
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const upsertNotificationReportGeneratorLog = `-- name: UpsertNotificationReportGeneratorLog :exec
|
||||
INSERT INTO notification_report_generator_logs (notification_template_id, last_generated_at) VALUES ($1, $2)
|
||||
ON CONFLICT (notification_template_id) DO UPDATE set last_generated_at = EXCLUDED.last_generated_at
|
||||
WHERE notification_report_generator_logs.notification_template_id = EXCLUDED.notification_template_id
|
||||
`
|
||||
|
||||
type UpsertNotificationReportGeneratorLogParams struct {
|
||||
NotificationTemplateID uuid.UUID `db:"notification_template_id" json:"notification_template_id"`
|
||||
LastGeneratedAt time.Time `db:"last_generated_at" json:"last_generated_at"`
|
||||
}
|
||||
|
||||
// Insert or update notification report generator logs with recent activity.
|
||||
func (q *sqlQuerier) UpsertNotificationReportGeneratorLog(ctx context.Context, arg UpsertNotificationReportGeneratorLogParams) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertNotificationReportGeneratorLog, arg.NotificationTemplateID, arg.LastGeneratedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOAuth2ProviderAppByID = `-- name: DeleteOAuth2ProviderAppByID :exec
|
||||
DELETE FROM oauth2_provider_apps WHERE id = $1
|
||||
`
|
||||
@ -12896,6 +12930,83 @@ func (q *sqlQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, t
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getFailedWorkspaceBuildsByTemplateID = `-- name: GetFailedWorkspaceBuildsByTemplateID :many
|
||||
SELECT
|
||||
tv.name AS template_version_name,
|
||||
u.username AS workspace_owner_username,
|
||||
w.name AS workspace_name,
|
||||
wb.build_number AS workspace_build_number
|
||||
FROM
|
||||
workspace_build_with_user AS wb
|
||||
JOIN
|
||||
workspaces AS w
|
||||
ON
|
||||
wb.workspace_id = w.id
|
||||
JOIN
|
||||
users AS u
|
||||
ON
|
||||
w.owner_id = u.id
|
||||
JOIN
|
||||
provisioner_jobs AS pj
|
||||
ON
|
||||
wb.job_id = pj.id
|
||||
JOIN
|
||||
templates AS t
|
||||
ON
|
||||
w.template_id = t.id
|
||||
JOIN
|
||||
template_versions AS tv
|
||||
ON
|
||||
wb.template_version_id = tv.id
|
||||
WHERE
|
||||
w.template_id = $1
|
||||
AND wb.created_at >= $2
|
||||
AND pj.completed_at IS NOT NULL
|
||||
AND pj.job_status = 'failed'
|
||||
ORDER BY
|
||||
tv.name ASC, wb.build_number DESC
|
||||
`
|
||||
|
||||
type GetFailedWorkspaceBuildsByTemplateIDParams struct {
|
||||
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
|
||||
Since time.Time `db:"since" json:"since"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, arg GetFailedWorkspaceBuildsByTemplateIDParams) ([]GetFailedWorkspaceBuildsByTemplateIDRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getFailedWorkspaceBuildsByTemplateID, arg.TemplateID, arg.Since)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetFailedWorkspaceBuildsByTemplateIDRow
|
||||
for rows.Next() {
|
||||
var i GetFailedWorkspaceBuildsByTemplateIDRow
|
||||
if err := rows.Scan(
|
||||
&i.TemplateVersionName,
|
||||
&i.WorkspaceOwnerUsername,
|
||||
&i.WorkspaceName,
|
||||
&i.WorkspaceBuildNumber,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, initiator_by_avatar_url, initiator_by_username
|
||||
@ -13154,6 +13265,73 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Co
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceBuildStatsByTemplates = `-- name: GetWorkspaceBuildStatsByTemplates :many
|
||||
SELECT
|
||||
w.template_id,
|
||||
t.name AS template_name,
|
||||
t.display_name AS template_display_name,
|
||||
t.organization_id AS template_organization_id,
|
||||
COUNT(*) AS total_builds,
|
||||
COUNT(CASE WHEN pj.job_status = 'failed' THEN 1 END) AS failed_builds
|
||||
FROM
|
||||
workspace_build_with_user AS wb
|
||||
JOIN
|
||||
workspaces AS w ON
|
||||
wb.workspace_id = w.id
|
||||
JOIN
|
||||
provisioner_jobs AS pj ON
|
||||
wb.job_id = pj.id
|
||||
JOIN
|
||||
templates AS t ON
|
||||
w.template_id = t.id
|
||||
WHERE
|
||||
wb.created_at >= $1
|
||||
AND pj.completed_at IS NOT NULL
|
||||
GROUP BY
|
||||
w.template_id, template_name, template_display_name, template_organization_id
|
||||
ORDER BY
|
||||
template_name ASC
|
||||
`
|
||||
|
||||
type GetWorkspaceBuildStatsByTemplatesRow struct {
|
||||
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
|
||||
TemplateName string `db:"template_name" json:"template_name"`
|
||||
TemplateDisplayName string `db:"template_display_name" json:"template_display_name"`
|
||||
TemplateOrganizationID uuid.UUID `db:"template_organization_id" json:"template_organization_id"`
|
||||
TotalBuilds int64 `db:"total_builds" json:"total_builds"`
|
||||
FailedBuilds int64 `db:"failed_builds" json:"failed_builds"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]GetWorkspaceBuildStatsByTemplatesRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceBuildStatsByTemplates, since)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetWorkspaceBuildStatsByTemplatesRow
|
||||
for rows.Next() {
|
||||
var i GetWorkspaceBuildStatsByTemplatesRow
|
||||
if err := rows.Scan(
|
||||
&i.TemplateID,
|
||||
&i.TemplateName,
|
||||
&i.TemplateDisplayName,
|
||||
&i.TemplateOrganizationID,
|
||||
&i.TotalBuilds,
|
||||
&i.FailedBuilds,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWorkspaceBuildsByWorkspaceID = `-- name: GetWorkspaceBuildsByWorkspaceID :many
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, initiator_by_avatar_url, initiator_by_username
|
||||
|
@ -174,3 +174,18 @@ SELECT *
|
||||
FROM notification_templates
|
||||
WHERE kind = @kind::notification_template_kind
|
||||
ORDER BY name ASC;
|
||||
|
||||
-- name: GetNotificationReportGeneratorLogByTemplate :one
|
||||
-- Fetch the notification report generator log indicating recent activity.
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
notification_report_generator_logs
|
||||
WHERE
|
||||
notification_template_id = @template_id::uuid;
|
||||
|
||||
-- name: UpsertNotificationReportGeneratorLog :exec
|
||||
-- Insert or update notification report generator logs with recent activity.
|
||||
INSERT INTO notification_report_generator_logs (notification_template_id, last_generated_at) VALUES (@notification_template_id, @last_generated_at)
|
||||
ON CONFLICT (notification_template_id) DO UPDATE set last_generated_at = EXCLUDED.last_generated_at
|
||||
WHERE notification_report_generator_logs.notification_template_id = EXCLUDED.notification_template_id;
|
||||
|
@ -179,3 +179,66 @@ WHERE
|
||||
wb.transition = 'start'::workspace_transition
|
||||
AND
|
||||
pj.completed_at IS NOT NULL;
|
||||
|
||||
-- name: GetWorkspaceBuildStatsByTemplates :many
|
||||
SELECT
|
||||
w.template_id,
|
||||
t.name AS template_name,
|
||||
t.display_name AS template_display_name,
|
||||
t.organization_id AS template_organization_id,
|
||||
COUNT(*) AS total_builds,
|
||||
COUNT(CASE WHEN pj.job_status = 'failed' THEN 1 END) AS failed_builds
|
||||
FROM
|
||||
workspace_build_with_user AS wb
|
||||
JOIN
|
||||
workspaces AS w ON
|
||||
wb.workspace_id = w.id
|
||||
JOIN
|
||||
provisioner_jobs AS pj ON
|
||||
wb.job_id = pj.id
|
||||
JOIN
|
||||
templates AS t ON
|
||||
w.template_id = t.id
|
||||
WHERE
|
||||
wb.created_at >= @since
|
||||
AND pj.completed_at IS NOT NULL
|
||||
GROUP BY
|
||||
w.template_id, template_name, template_display_name, template_organization_id
|
||||
ORDER BY
|
||||
template_name ASC;
|
||||
|
||||
-- name: GetFailedWorkspaceBuildsByTemplateID :many
|
||||
SELECT
|
||||
tv.name AS template_version_name,
|
||||
u.username AS workspace_owner_username,
|
||||
w.name AS workspace_name,
|
||||
wb.build_number AS workspace_build_number
|
||||
FROM
|
||||
workspace_build_with_user AS wb
|
||||
JOIN
|
||||
workspaces AS w
|
||||
ON
|
||||
wb.workspace_id = w.id
|
||||
JOIN
|
||||
users AS u
|
||||
ON
|
||||
w.owner_id = u.id
|
||||
JOIN
|
||||
provisioner_jobs AS pj
|
||||
ON
|
||||
wb.job_id = pj.id
|
||||
JOIN
|
||||
templates AS t
|
||||
ON
|
||||
w.template_id = t.id
|
||||
JOIN
|
||||
template_versions AS tv
|
||||
ON
|
||||
wb.template_version_id = tv.id
|
||||
WHERE
|
||||
w.template_id = $1
|
||||
AND wb.created_at >= @since
|
||||
AND pj.completed_at IS NOT NULL
|
||||
AND pj.job_status = 'failed'
|
||||
ORDER BY
|
||||
tv.name ASC, wb.build_number DESC;
|
||||
|
@ -26,6 +26,7 @@ const (
|
||||
UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id);
|
||||
UniqueNotificationMessagesPkey UniqueConstraint = "notification_messages_pkey" // ALTER TABLE ONLY notification_messages ADD CONSTRAINT notification_messages_pkey PRIMARY KEY (id);
|
||||
UniqueNotificationPreferencesPkey UniqueConstraint = "notification_preferences_pkey" // ALTER TABLE ONLY notification_preferences ADD CONSTRAINT notification_preferences_pkey PRIMARY KEY (user_id, notification_template_id);
|
||||
UniqueNotificationReportGeneratorLogsPkey UniqueConstraint = "notification_report_generator_logs_pkey" // ALTER TABLE ONLY notification_report_generator_logs ADD CONSTRAINT notification_report_generator_logs_pkey PRIMARY KEY (notification_template_id);
|
||||
UniqueNotificationTemplatesNameKey UniqueConstraint = "notification_templates_name_key" // ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_name_key UNIQUE (name);
|
||||
UniqueNotificationTemplatesPkey UniqueConstraint = "notification_templates_pkey" // ALTER TABLE ONLY notification_templates ADD CONSTRAINT notification_templates_pkey PRIMARY KEY (id);
|
||||
UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id);
|
||||
|
Reference in New Issue
Block a user