From a88ef0b856f4e15b18d700b06926e8f222536f54 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Tue, 8 Sep 2026 08:53:16 +0000 Subject: [PATCH 1/2] feat!: remove the task notification templates and AI seat usage reason Remove the six task notification templates and the task-alert dismissal setting. Remap task AI seat events to aibridge while preserving consumed seats, then remove task from the usage reason enum. The down migration restores template definitions and the enum value. Deleted preferences and notification history cannot be reconstructed, and remapped events cannot recover their original reason. --- coderd/database/dbauthz/dbauthz.go | 22 ---- coderd/database/dbauthz/dbauthz_test.go | 16 --- coderd/database/dbmetrics/querymetrics.go | 16 --- coderd/database/dbmock/dbmock.go | 30 ----- coderd/database/dump.sql | 3 +- ...move_task_notifications_and_usage.down.sql | 78 +++++++++++++ ...remove_task_notifications_and_usage.up.sql | 29 +++++ coderd/database/migrations/migrate_test.go | 110 ++++++++++++++++++ coderd/database/models.go | 5 +- coderd/database/querier.go | 2 - coderd/database/querier_test.go | 6 +- coderd/database/queries.sql.go | 44 ------- coderd/database/queries/users.sql | 23 ---- coderd/notifications/events.go | 10 -- coderd/notifications/notifications_test.go | 87 -------------- .../smtp/TemplateTaskCompleted.html.golden | 84 ------------- .../smtp/TemplateTaskFailed.html.golden | 85 -------------- .../smtp/TemplateTaskIdle.html.golden | 84 ------------- .../smtp/TemplateTaskPaused.html.golden | 85 -------------- .../smtp/TemplateTaskResumed.html.golden | 85 -------------- .../smtp/TemplateTaskWorking.html.golden | 85 -------------- .../webhook/TemplateTaskCompleted.json.golden | 33 ------ .../webhook/TemplateTaskFailed.json.golden | 33 ------ .../webhook/TemplateTaskIdle.json.golden | 33 ------ .../webhook/TemplateTaskPaused.json.golden | 35 ------ .../webhook/TemplateTaskResumed.json.golden | 34 ------ .../webhook/TemplateTaskWorking.json.golden | 33 ------ docs/admin/monitoring/notifications/index.md | 7 -- 28 files changed, 222 insertions(+), 975 deletions(-) create mode 100644 coderd/database/migrations/000591_remove_task_notifications_and_usage.down.sql create mode 100644 coderd/database/migrations/000591_remove_task_notifications_and_usage.up.sql delete mode 100644 coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskCompleted.html.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskFailed.html.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskIdle.html.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskPaused.html.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskResumed.html.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskWorking.html.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskCompleted.json.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskFailed.json.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskIdle.json.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskPaused.json.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskResumed.json.golden delete mode 100644 coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskWorking.json.golden diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 6aeccb925c3..14f7f33c7e4 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -5437,17 +5437,6 @@ func (q *querier) GetUserStatusCounts(ctx context.Context, arg database.GetUserS return q.db.GetUserStatusCounts(ctx, arg) } -func (q *querier) GetUserTaskNotificationAlertDismissed(ctx context.Context, userID uuid.UUID) (bool, error) { - user, err := q.db.GetUserByID(ctx, userID) - if err != nil { - return false, err - } - if err := q.authorizeContext(ctx, policy.ActionReadPersonal, user); err != nil { - return false, err - } - return q.db.GetUserTaskNotificationAlertDismissed(ctx, userID) -} - func (q *querier) GetUserThinkingDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) { user, err := q.db.GetUserByID(ctx, userID) if err != nil { @@ -8610,17 +8599,6 @@ func (q *querier) UpdateUserStatus(ctx context.Context, arg database.UpdateUserS return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateUserStatus)(ctx, arg) } -func (q *querier) UpdateUserTaskNotificationAlertDismissed(ctx context.Context, arg database.UpdateUserTaskNotificationAlertDismissedParams) (bool, error) { - user, err := q.db.GetUserByID(ctx, arg.UserID) - if err != nil { - return false, err - } - if err := q.authorizeContext(ctx, policy.ActionUpdatePersonal, user); err != nil { - return false, err - } - return q.db.UpdateUserTaskNotificationAlertDismissed(ctx, arg) -} - func (q *querier) UpdateUserTerminalFont(ctx context.Context, arg database.UpdateUserTerminalFontParams) (database.UserConfig, error) { u, err := q.db.GetUserByID(ctx, arg.UserID) if err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 2d6e3df865c..3cd7b3a3faa 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -8,7 +8,6 @@ import ( "maps" "net" "reflect" - "strconv" "testing" "time" @@ -3226,12 +3225,6 @@ func (s *MethodTestSuite) TestUser() { dbm.EXPECT().UpdateUserThemeDark(gomock.Any(), arg).Return(uc, nil).AnyTimes() check.Args(arg).Asserts(u, policy.ActionUpdatePersonal).Returns(uc) })) - s.Run("GetUserTaskNotificationAlertDismissed", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { - u := testutil.Fake(s.T(), faker, database.User{}) - dbm.EXPECT().GetUserByID(gomock.Any(), u.ID).Return(u, nil).AnyTimes() - dbm.EXPECT().GetUserTaskNotificationAlertDismissed(gomock.Any(), u.ID).Return(false, nil).AnyTimes() - check.Args(u.ID).Asserts(u, policy.ActionReadPersonal).Returns(false) - })) s.Run("GetUserChatCustomPrompt", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { u := testutil.Fake(s.T(), faker, database.User{}) dbm.EXPECT().GetUserByID(gomock.Any(), u.ID).Return(u, nil).AnyTimes() @@ -3410,15 +3403,6 @@ func (s *MethodTestSuite) TestUser() { dbm.EXPECT().DeleteUserChatCompactionThreshold(gomock.Any(), arg).Return(nil).AnyTimes() check.Args(arg).Asserts(u, policy.ActionUpdatePersonal) })) - s.Run("UpdateUserTaskNotificationAlertDismissed", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { - user := testutil.Fake(s.T(), faker, database.User{}) - userConfig := database.UserConfig{UserID: user.ID, Key: "task_notification_alert_dismissed", Value: "false"} - userConfigValue, _ := strconv.ParseBool(userConfig.Value) - arg := database.UpdateUserTaskNotificationAlertDismissedParams{UserID: user.ID, TaskNotificationAlertDismissed: userConfigValue} - dbm.EXPECT().GetUserByID(gomock.Any(), user.ID).Return(user, nil).AnyTimes() - dbm.EXPECT().UpdateUserTaskNotificationAlertDismissed(gomock.Any(), arg).Return(false, nil).AnyTimes() - check.Args(arg).Asserts(user, policy.ActionUpdatePersonal).Returns(userConfigValue) - })) s.Run("UpdateUserStatus", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { u := testutil.Fake(s.T(), faker, database.User{}) arg := database.UpdateUserStatusParams{ID: u.ID, Status: u.Status, UpdatedAt: u.UpdatedAt} diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 57b81e265d7..07b671294d1 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -3513,14 +3513,6 @@ func (m queryMetricsStore) GetUserStatusCounts(ctx context.Context, arg database return r0, r1 } -func (m queryMetricsStore) GetUserTaskNotificationAlertDismissed(ctx context.Context, userID uuid.UUID) (bool, error) { - start := time.Now() - r0, r1 := m.s.GetUserTaskNotificationAlertDismissed(ctx, userID) - m.queryLatencies.WithLabelValues("GetUserTaskNotificationAlertDismissed").Observe(time.Since(start).Seconds()) - m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetUserTaskNotificationAlertDismissed").Inc() - return r0, r1 -} - func (m queryMetricsStore) GetUserThinkingDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) { start := time.Now() r0, r1 := m.s.GetUserThinkingDisplayMode(ctx, userID) @@ -6009,14 +6001,6 @@ func (m queryMetricsStore) UpdateUserStatus(ctx context.Context, arg database.Up return r0, r1 } -func (m queryMetricsStore) UpdateUserTaskNotificationAlertDismissed(ctx context.Context, arg database.UpdateUserTaskNotificationAlertDismissedParams) (bool, error) { - start := time.Now() - r0, r1 := m.s.UpdateUserTaskNotificationAlertDismissed(ctx, arg) - m.queryLatencies.WithLabelValues("UpdateUserTaskNotificationAlertDismissed").Observe(time.Since(start).Seconds()) - m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateUserTaskNotificationAlertDismissed").Inc() - return r0, r1 -} - func (m queryMetricsStore) UpdateUserTerminalFont(ctx context.Context, arg database.UpdateUserTerminalFontParams) (database.UserConfig, error) { start := time.Now() r0, r1 := m.s.UpdateUserTerminalFont(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 8fa57a596c2..9185d784019 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -6615,21 +6615,6 @@ func (mr *MockStoreMockRecorder) GetUserStatusCounts(ctx, arg any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserStatusCounts", reflect.TypeOf((*MockStore)(nil).GetUserStatusCounts), ctx, arg) } -// GetUserTaskNotificationAlertDismissed mocks base method. -func (m *MockStore) GetUserTaskNotificationAlertDismissed(ctx context.Context, userID uuid.UUID) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUserTaskNotificationAlertDismissed", ctx, userID) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUserTaskNotificationAlertDismissed indicates an expected call of GetUserTaskNotificationAlertDismissed. -func (mr *MockStoreMockRecorder) GetUserTaskNotificationAlertDismissed(ctx, userID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserTaskNotificationAlertDismissed", reflect.TypeOf((*MockStore)(nil).GetUserTaskNotificationAlertDismissed), ctx, userID) -} - // GetUserThinkingDisplayMode mocks base method. func (m *MockStore) GetUserThinkingDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) { m.ctrl.T.Helper() @@ -11349,21 +11334,6 @@ func (mr *MockStoreMockRecorder) UpdateUserStatus(ctx, arg any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserStatus", reflect.TypeOf((*MockStore)(nil).UpdateUserStatus), ctx, arg) } -// UpdateUserTaskNotificationAlertDismissed mocks base method. -func (m *MockStore) UpdateUserTaskNotificationAlertDismissed(ctx context.Context, arg database.UpdateUserTaskNotificationAlertDismissedParams) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateUserTaskNotificationAlertDismissed", ctx, arg) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// UpdateUserTaskNotificationAlertDismissed indicates an expected call of UpdateUserTaskNotificationAlertDismissed. -func (mr *MockStoreMockRecorder) UpdateUserTaskNotificationAlertDismissed(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserTaskNotificationAlertDismissed", reflect.TypeOf((*MockStore)(nil).UpdateUserTaskNotificationAlertDismissed), ctx, arg) -} - // UpdateUserTerminalFont mocks base method. func (m *MockStore) UpdateUserTerminalFont(ctx context.Context, arg database.UpdateUserTerminalFontParams) (database.UserConfig, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index abbfda38e5c..26b27dc05d7 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -28,8 +28,7 @@ CREATE TYPE ai_provider_type AS ENUM ( ); CREATE TYPE ai_seat_usage_reason AS ENUM ( - 'aibridge', - 'task' + 'aibridge' ); CREATE TYPE aibridge_interception_error_type AS ENUM ( diff --git a/coderd/database/migrations/000591_remove_task_notifications_and_usage.down.sql b/coderd/database/migrations/000591_remove_task_notifications_and_usage.down.sql new file mode 100644 index 00000000000..c96cb172043 --- /dev/null +++ b/coderd/database/migrations/000591_remove_task_notifications_and_usage.down.sql @@ -0,0 +1,78 @@ +-- Restore task notification templates. Inbox notification history deleted +-- by the cascade is not recoverable. +INSERT INTO notification_templates ( + id, name, title_template, body_template, actions, "group", method, kind, enabled_by_default +) VALUES ( + 'bd4b7168-d05e-4e19-ad0f-3593b77aa90f', + 'Task Working', + E'Task ''{{.Labels.workspace}}'' is working', + E'The task ''{{.Labels.task}}'' transitioned to a working state.', + '[{"label": "View task", "url": "{{base_url}}/tasks/{{.UserUsername}}/{{.Labels.workspace}}"}, {"label": "View workspace", "url": "{{base_url}}/@{{.UserUsername}}/{{.Labels.workspace}}"}]'::jsonb, + 'Task Events', + NULL, + 'system'::notification_template_kind, + false +), ( + 'd4a6271c-cced-4ed0-84ad-afd02a9c7799', + 'Task Idle', + E'Task ''{{.Labels.workspace}}'' is idle', + E'The task ''{{.Labels.task}}'' is idle and ready for input.', + '[{"label": "View task", "url": "{{base_url}}/tasks/{{.UserUsername}}/{{.Labels.workspace}}"}, {"label": "View workspace", "url": "{{base_url}}/@{{.UserUsername}}/{{.Labels.workspace}}"}]'::jsonb, + 'Task Events', + NULL, + 'system'::notification_template_kind, + false +), ( + '8c5a4d12-9f7e-4b3a-a1c8-6e4f2d9b5a7c', + 'Task Completed', + E'Task ''{{.Labels.workspace}}'' completed', + E'The task ''{{.Labels.task}}'' has completed successfully.', + '[{"label": "View task", "url": "{{base_url}}/tasks/{{.UserUsername}}/{{.Labels.workspace}}"}, {"label": "View workspace", "url": "{{base_url}}/@{{.UserUsername}}/{{.Labels.workspace}}"}]'::jsonb, + 'Task Events', + NULL, + 'system'::notification_template_kind, + false +), ( + '3b7e8f1a-4c2d-49a6-b5e9-7f3a1c8d6b4e', + 'Task Failed', + E'Task ''{{.Labels.workspace}}'' failed', + E'The task ''{{.Labels.task}}'' has failed. Check the logs for more details.', + '[{"label": "View task", "url": "{{base_url}}/tasks/{{.UserUsername}}/{{.Labels.workspace}}"}, {"label": "View workspace", "url": "{{base_url}}/@{{.UserUsername}}/{{.Labels.workspace}}"}]'::jsonb, + 'Task Events', + NULL, + 'system'::notification_template_kind, + false +), ( + '2a74f3d3-ab09-4123-a4a5-ca238f4f65a1', + 'Task Paused', + E'Task ''{{.Labels.task}}'' is paused', + E'The task ''{{.Labels.task}}'' was paused ({{.Labels.pause_reason}}).', + '[{"label": "View task", "url": "{{base_url}}/tasks/{{.UserUsername}}/{{.Labels.task_id}}"}, {"label": "View workspace", "url": "{{base_url}}/@{{.UserUsername}}/{{.Labels.workspace}}"}]'::jsonb, + 'Task Events', + NULL, + 'system'::notification_template_kind, + true +), ( + '843ee9c3-a8fb-4846-afa9-977bec578649', + 'Task Resumed', + E'Task ''{{.Labels.task}}'' has resumed', + E'The task ''{{.Labels.task}}'' has resumed.', + '[{"label": "View task", "url": "{{base_url}}/tasks/{{.UserUsername}}/{{.Labels.task_id}}"}, {"label": "View workspace", "url": "{{base_url}}/@{{.UserUsername}}/{{.Labels.workspace}}"}]'::jsonb, + 'Task Events', + NULL, + 'system'::notification_template_kind, + true +); + +-- Restore the task AI seat usage reason. Remapped seat events are not +-- recoverable. +ALTER TYPE ai_seat_usage_reason RENAME TO ai_seat_usage_reason_old; + +CREATE TYPE ai_seat_usage_reason AS ENUM ( + 'aibridge', + 'task' +); + +ALTER TABLE ai_seat_state ALTER COLUMN last_event_type TYPE ai_seat_usage_reason USING (last_event_type::text::ai_seat_usage_reason); + +DROP TYPE ai_seat_usage_reason_old; diff --git a/coderd/database/migrations/000591_remove_task_notifications_and_usage.up.sql b/coderd/database/migrations/000591_remove_task_notifications_and_usage.up.sql new file mode 100644 index 00000000000..c52ae068534 --- /dev/null +++ b/coderd/database/migrations/000591_remove_task_notifications_and_usage.up.sql @@ -0,0 +1,29 @@ +-- Delete task notification templates and preferences. The delete cascades +-- to inbox_notifications and notification_messages: historical task +-- notifications are intentionally removed with the feature. +DELETE FROM notification_templates +WHERE id IN ( + 'bd4b7168-d05e-4e19-ad0f-3593b77aa90f', -- Task Working + 'd4a6271c-cced-4ed0-84ad-afd02a9c7799', -- Task Idle + '8c5a4d12-9f7e-4b3a-a1c8-6e4f2d9b5a7c', -- Task Completed + '3b7e8f1a-4c2d-49a6-b5e9-7f3a1c8d6b4e', -- Task Failed + '2a74f3d3-ab09-4123-a4a5-ca238f4f65a1', -- Task Paused + '843ee9c3-a8fb-4846-afa9-977bec578649' -- Task Resumed +); + +DELETE FROM user_configs +WHERE key = 'preference_task_notification_alert_dismissed'; + +-- Remove the task AI seat usage reason. Seats consumed by tasks stay +-- consumed; their last event type is remapped to aibridge. +UPDATE ai_seat_state SET last_event_type = 'aibridge' WHERE last_event_type::text = 'task'; + +ALTER TYPE ai_seat_usage_reason RENAME TO ai_seat_usage_reason_old; + +CREATE TYPE ai_seat_usage_reason AS ENUM ( + 'aibridge' +); + +ALTER TABLE ai_seat_state ALTER COLUMN last_event_type TYPE ai_seat_usage_reason USING (last_event_type::text::ai_seat_usage_reason); + +DROP TYPE ai_seat_usage_reason_old; diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index 9844d8ec4bd..a84f08ea420 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -1190,6 +1190,116 @@ func TestMigration000475AgentsAccessOrgRole(t *testing.T) { ) } +func TestMigration000591RemoveTaskNotificationsAndUsage(t *testing.T) { + t.Parallel() + + const migrationVersion = 591 + sqlDB := testSQLDB(t) + next, err := migrations.Stepper(sqlDB) + require.NoError(t, err) + for { + version, more, err := next() + require.NoError(t, err) + if !more { + t.Fatalf("migration %d not found", migrationVersion) + } + if version == migrationVersion-1 { + break + } + } + + ctx := testutil.Context(t, testutil.WaitLong) + now := time.Now().UTC().Truncate(time.Microsecond) + userID, bridgeUserID, orgID := uuid.New(), uuid.New(), uuid.New() + taskTemplateIDs := pq.StringArray{ + "bd4b7168-d05e-4e19-ad0f-3593b77aa90f", + "d4a6271c-cced-4ed0-84ad-afd02a9c7799", + "8c5a4d12-9f7e-4b3a-a1c8-6e4f2d9b5a7c", + "3b7e8f1a-4c2d-49a6-b5e9-7f3a1c8d6b4e", + "2a74f3d3-ab09-4123-a4a5-ca238f4f65a1", + "843ee9c3-a8fb-4846-afa9-977bec578649", + } + const otherTemplateID = "281fdf73-c6d6-4cbb-8ff5-888baf8a2fff" + + _, err = sqlDB.ExecContext(ctx, ` + INSERT INTO organizations (id, name, display_name, description, created_at, updated_at, default_org_member_roles) + VALUES ($1, $1::uuid::text, 'Notifications', '', $2, $2, '{}')`, orgID, now) + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, ` + INSERT INTO users (id, username, email, hashed_password, created_at, updated_at) + VALUES ($1, $1::uuid::text, $1::uuid::text || '@test.com', '', $3, $3), + ($2, $2::uuid::text, $2::uuid::text || '@test.com', '', $3, $3)`, userID, bridgeUserID, now) + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, ` + INSERT INTO notification_preferences (user_id, notification_template_id, disabled) + VALUES ($1, $2, true), ($1, $3, true)`, userID, taskTemplateIDs[0], otherTemplateID) + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, ` + INSERT INTO user_configs (user_id, key, value) + VALUES ($1, 'preference_task_notification_alert_dismissed', 'true')`, userID) + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, ` + INSERT INTO ai_seat_state (user_id, first_used_at, last_used_at, last_event_type, last_event_description, updated_at) + VALUES ($1, $3, $3, 'task', 'task usage', $3), + ($2, $3, $3, 'aibridge', 'bridge usage', $3)`, userID, bridgeUserID, now) + require.NoError(t, err) + + var count int + const taskTemplatesQuery = "SELECT count(*) FROM notification_templates WHERE id = ANY($1::uuid[])" + require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) + require.Equal(t, 6, count) + + version, _, err := next() + require.NoError(t, err) + require.EqualValues(t, migrationVersion, version) + + require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) + require.Zero(t, count) + require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM notification_templates WHERE id = $1", otherTemplateID).Scan(&count)) + require.Equal(t, 1, count) + var preferences pq.StringArray + require.NoError(t, sqlDB.QueryRowContext(ctx, + "SELECT ARRAY(SELECT notification_template_id::text FROM notification_preferences WHERE user_id = $1)", userID).Scan(&preferences)) + require.Equal(t, pq.StringArray{otherTemplateID}, preferences) + require.NoError(t, sqlDB.QueryRowContext(ctx, + "SELECT count(*) FROM user_configs WHERE user_id = $1 AND key = 'preference_task_notification_alert_dismissed'", userID).Scan(&count)) + require.Zero(t, count) + for id, description := range map[uuid.UUID]string{userID: "task usage", bridgeUserID: "bridge usage"} { + var event, gotDescription string + var firstUsed, lastUsed, updated time.Time + require.NoError(t, sqlDB.QueryRowContext(ctx, ` + SELECT last_event_type, last_event_description, first_used_at, last_used_at, updated_at + FROM ai_seat_state WHERE user_id = $1`, id).Scan(&event, &gotDescription, &firstUsed, &lastUsed, &updated)) + require.Equal(t, "aibridge", event) + require.Equal(t, description, gotDescription) + require.True(t, now.Equal(firstUsed)) + require.True(t, now.Equal(lastUsed)) + require.True(t, now.Equal(updated)) + } + var reasons pq.StringArray + const reasonsQuery = "SELECT enum_range(NULL::ai_seat_usage_reason)::text[]" + require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery).Scan(&reasons)) + require.Equal(t, pq.StringArray{"aibridge"}, reasons) + + downSQL, err := os.ReadFile("000591_remove_task_notifications_and_usage.down.sql") + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, string(downSQL)) + require.NoError(t, err) + require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) + require.Equal(t, 6, count) + require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery).Scan(&reasons)) + require.Equal(t, pq.StringArray{"aibridge", "task"}, reasons) + + upSQL, err := os.ReadFile("000591_remove_task_notifications_and_usage.up.sql") + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, string(upSQL)) + require.NoError(t, err) + require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) + require.Zero(t, count) + require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery).Scan(&reasons)) + require.Equal(t, pq.StringArray{"aibridge"}, reasons) +} + func TestMigration000587RemoveAgentsAccessRole(t *testing.T) { t.Parallel() diff --git a/coderd/database/models.go b/coderd/database/models.go index b5a1340b353..c1bfdb4fb1f 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -230,7 +230,6 @@ type AISeatUsageReason string const ( AISeatUsageReasonAibridge AISeatUsageReason = "aibridge" - AISeatUsageReasonTask AISeatUsageReason = "task" ) func (e *AISeatUsageReason) Scan(src interface{}) error { @@ -270,8 +269,7 @@ func (ns NullAISeatUsageReason) Value() (driver.Value, error) { func (e AISeatUsageReason) Valid() bool { switch e { - case AISeatUsageReasonAibridge, - AISeatUsageReasonTask: + case AISeatUsageReasonAibridge: return true } return false @@ -280,7 +278,6 @@ func (e AISeatUsageReason) Valid() bool { func AllAISeatUsageReasonValues() []AISeatUsageReason { return []AISeatUsageReason{ AISeatUsageReasonAibridge, - AISeatUsageReasonTask, } } diff --git a/coderd/database/querier.go b/coderd/database/querier.go index b7c063b98a0..cd88b60024f 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1019,7 +1019,6 @@ type sqlcQuerier interface { // GetUserStatusCounts returns the count of users in each status over time. // The time range is inclusively defined by the start_time and end_time parameters. GetUserStatusCounts(ctx context.Context, arg GetUserStatusCountsParams) ([]GetUserStatusCountsRow, error) - GetUserTaskNotificationAlertDismissed(ctx context.Context, userID uuid.UUID) (bool, error) GetUserThinkingDisplayMode(ctx context.Context, userID uuid.UUID) (string, error) GetUserWorkspaceBuildParameters(ctx context.Context, arg GetUserWorkspaceBuildParametersParams) ([]GetUserWorkspaceBuildParametersRow, error) // This will never return deleted users. @@ -1630,7 +1629,6 @@ type sqlcQuerier interface { UpdateUserShellToolDisplayMode(ctx context.Context, arg UpdateUserShellToolDisplayModeParams) (string, error) UpdateUserSkillByUserIDAndName(ctx context.Context, arg UpdateUserSkillByUserIDAndNameParams) (UserSkill, error) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) (User, error) - UpdateUserTaskNotificationAlertDismissed(ctx context.Context, arg UpdateUserTaskNotificationAlertDismissedParams) (bool, error) UpdateUserTerminalFont(ctx context.Context, arg UpdateUserTerminalFontParams) (UserConfig, error) UpdateUserThemeDark(ctx context.Context, arg UpdateUserThemeDarkParams) (UserConfig, error) UpdateUserThemeLight(ctx context.Context, arg UpdateUserThemeLightParams) (UserConfig, error) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index babef6c49ec..03d153019be 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -13700,7 +13700,7 @@ func TestUpsertAISeats(t *testing.T) { newRow, err := db.UpsertAISeatState(ctx, database.UpsertAISeatStateParams{ UserID: user.ID, FirstUsedAt: now.Add(time.Hour * -24), - LastEventType: database.AISeatUsageReasonTask, + LastEventType: database.AISeatUsageReasonAibridge, }) require.NoError(t, err) require.True(t, newRow) @@ -13708,7 +13708,7 @@ func TestUpsertAISeats(t *testing.T) { alreadyExists, err := db.UpsertAISeatState(ctx, database.UpsertAISeatStateParams{ UserID: user.ID, FirstUsedAt: now.Add(time.Hour * -23), - LastEventType: database.AISeatUsageReasonTask, + LastEventType: database.AISeatUsageReasonAibridge, }) require.NoError(t, err) require.False(t, alreadyExists) @@ -13716,7 +13716,7 @@ func TestUpsertAISeats(t *testing.T) { alreadyExists, err = db.UpsertAISeatState(ctx, database.UpsertAISeatStateParams{ UserID: user.ID, FirstUsedAt: now, - LastEventType: database.AISeatUsageReasonTask, + LastEventType: database.AISeatUsageReasonAibridge, }) require.NoError(t, err) require.False(t, alreadyExists) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 07ef2ceebde..26d2a934c61 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -31879,23 +31879,6 @@ func (q *sqlQuerier) GetUserShellToolDisplayMode(ctx context.Context, userID uui return shell_tool_display_mode, err } -const getUserTaskNotificationAlertDismissed = `-- name: GetUserTaskNotificationAlertDismissed :one -SELECT - value::boolean as task_notification_alert_dismissed -FROM - user_configs -WHERE - user_id = $1 - AND key = 'preference_task_notification_alert_dismissed' -` - -func (q *sqlQuerier) GetUserTaskNotificationAlertDismissed(ctx context.Context, userID uuid.UUID) (bool, error) { - row := q.db.QueryRowContext(ctx, getUserTaskNotificationAlertDismissed, userID) - var task_notification_alert_dismissed bool - err := row.Scan(&task_notification_alert_dismissed) - return task_notification_alert_dismissed, err -} - const getUserThinkingDisplayMode = `-- name: GetUserThinkingDisplayMode :one SELECT value AS thinking_display_mode @@ -32856,33 +32839,6 @@ func (q *sqlQuerier) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusP return i, err } -const updateUserTaskNotificationAlertDismissed = `-- name: UpdateUserTaskNotificationAlertDismissed :one -INSERT INTO - user_configs (user_id, key, value) -VALUES - ($1, 'preference_task_notification_alert_dismissed', ($2::boolean)::text) -ON CONFLICT - ON CONSTRAINT user_configs_pkey -DO UPDATE -SET - value = $2 -WHERE user_configs.user_id = $1 - AND user_configs.key = 'preference_task_notification_alert_dismissed' -RETURNING value::boolean AS task_notification_alert_dismissed -` - -type UpdateUserTaskNotificationAlertDismissedParams struct { - UserID uuid.UUID `db:"user_id" json:"user_id"` - TaskNotificationAlertDismissed bool `db:"task_notification_alert_dismissed" json:"task_notification_alert_dismissed"` -} - -func (q *sqlQuerier) UpdateUserTaskNotificationAlertDismissed(ctx context.Context, arg UpdateUserTaskNotificationAlertDismissedParams) (bool, error) { - row := q.db.QueryRowContext(ctx, updateUserTaskNotificationAlertDismissed, arg.UserID, arg.TaskNotificationAlertDismissed) - var task_notification_alert_dismissed bool - err := row.Scan(&task_notification_alert_dismissed) - return task_notification_alert_dismissed, err -} - const updateUserTerminalFont = `-- name: UpdateUserTerminalFont :one INSERT INTO user_configs (user_id, key, value) diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index fe6bbd291e6..3c4875d766d 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -284,29 +284,6 @@ END WHERE user_configs.user_id = @user_id AND user_configs.key = 'chat_debug_logging_enabled'; --- name: GetUserTaskNotificationAlertDismissed :one -SELECT - value::boolean as task_notification_alert_dismissed -FROM - user_configs -WHERE - user_id = @user_id - AND key = 'preference_task_notification_alert_dismissed'; - --- name: UpdateUserTaskNotificationAlertDismissed :one -INSERT INTO - user_configs (user_id, key, value) -VALUES - (@user_id, 'preference_task_notification_alert_dismissed', (@task_notification_alert_dismissed::boolean)::text) -ON CONFLICT - ON CONSTRAINT user_configs_pkey -DO UPDATE -SET - value = @task_notification_alert_dismissed -WHERE user_configs.user_id = @user_id - AND user_configs.key = 'preference_task_notification_alert_dismissed' -RETURNING value::boolean AS task_notification_alert_dismissed; - -- name: GetUserThinkingDisplayMode :one SELECT value AS thinking_display_mode diff --git a/coderd/notifications/events.go b/coderd/notifications/events.go index 2157bab035d..a00859c4dda 100644 --- a/coderd/notifications/events.go +++ b/coderd/notifications/events.go @@ -54,16 +54,6 @@ var ( TemplateCustomNotification = uuid.MustParse("39b1e189-c857-4b0c-877a-511144c18516") ) -// Task-related events. -var ( - TemplateTaskWorking = uuid.MustParse("bd4b7168-d05e-4e19-ad0f-3593b77aa90f") - TemplateTaskIdle = uuid.MustParse("d4a6271c-cced-4ed0-84ad-afd02a9c7799") - TemplateTaskCompleted = uuid.MustParse("8c5a4d12-9f7e-4b3a-a1c8-6e4f2d9b5a7c") - TemplateTaskFailed = uuid.MustParse("3b7e8f1a-4c2d-49a6-b5e9-7f3a1c8d6b4e") - TemplateTaskPaused = uuid.MustParse("2a74f3d3-ab09-4123-a4a5-ca238f4f65a1") - TemplateTaskResumed = uuid.MustParse("843ee9c3-a8fb-4846-afa9-977bec578649") -) - // Chat-related events. var ( TemplateChatAutoArchiveDigest = uuid.MustParse("764031be-4863-4220-867b-6ce1a1b7a5f5") diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 94b27895724..434361913be 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -1357,93 +1357,6 @@ func TestNotificationTemplates_Golden(t *testing.T) { Data: map[string]any{}, }, }, - { - name: "TemplateTaskWorking", - id: notifications.TemplateTaskWorking, - payload: types.MessagePayload{ - UserName: "Bobby", - UserEmail: "bobby@coder.com", - UserUsername: "bobby", - Labels: map[string]string{ - "task": "my-task", - "workspace": "my-workspace", - }, - Data: map[string]any{}, - }, - }, - { - name: "TemplateTaskIdle", - id: notifications.TemplateTaskIdle, - payload: types.MessagePayload{ - UserName: "Bobby", - UserEmail: "bobby@coder.com", - UserUsername: "bobby", - Labels: map[string]string{ - "task": "my-task", - "workspace": "my-workspace", - }, - Data: map[string]any{}, - }, - }, - { - name: "TemplateTaskCompleted", - id: notifications.TemplateTaskCompleted, - payload: types.MessagePayload{ - UserName: "Bobby", - UserEmail: "bobby@coder.com", - UserUsername: "bobby", - Labels: map[string]string{ - "task": "my-task", - "workspace": "my-workspace", - }, - Data: map[string]any{}, - }, - }, - { - name: "TemplateTaskFailed", - id: notifications.TemplateTaskFailed, - payload: types.MessagePayload{ - UserName: "Bobby", - UserEmail: "bobby@coder.com", - UserUsername: "bobby", - Labels: map[string]string{ - "task": "my-task", - "workspace": "my-workspace", - }, - Data: map[string]any{}, - }, - }, - { - name: "TemplateTaskPaused", - id: notifications.TemplateTaskPaused, - payload: types.MessagePayload{ - UserName: "Bobby", - UserEmail: "bobby@coder.com", - UserUsername: "bobby", - Labels: map[string]string{ - "task": "my-task", - "task_id": "00000000-0000-0000-0000-000000000000", - "workspace": "my-workspace", - "pause_reason": "idle timeout", - }, - Data: map[string]any{}, - }, - }, - { - name: "TemplateTaskResumed", - id: notifications.TemplateTaskResumed, - payload: types.MessagePayload{ - UserName: "Bobby", - UserEmail: "bobby@coder.com", - UserUsername: "bobby", - Labels: map[string]string{ - "task": "my-task", - "task_id": "00000000-0000-0000-0000-000000000001", - "workspace": "my-workspace", - }, - Data: map[string]any{}, - }, - }, { name: "TemplateChatShared", id: notifications.TemplateChatShared, diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskCompleted.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskCompleted.html.golden deleted file mode 100644 index b4a2d53763c..00000000000 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskCompleted.html.golden +++ /dev/null @@ -1,84 +0,0 @@ -From: system@coder.com -To: bobby@coder.com -Subject: Task 'my-workspace' completed -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 -MIME-Version: 1.0 - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; charset=UTF-8 - -Hi Bobby, - -The task 'my-task' has completed successfully. - - -View task: http://test.com/tasks/bobby/my-workspace - -View workspace: http://test.com/@bobby/my-workspace - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/html; charset=UTF-8 - - - - - - - Codestin Search App - - -
-
- 3D"Cod= -
-

- Task 'my-workspace' completed -

-
-

Hi Bobby,

-

The task ‘my-task’ has completed successfully.

-
-
- =20 - - View task - - =20 - - View workspace - - =20 -
-
-

© 2024 Coder. All rights reserved - h= -ttp://test.com

-

Click here to manage your notification = -settings

-

Stop receiving emails like this

-
-
- - - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4-- diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskFailed.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskFailed.html.golden deleted file mode 100644 index 1a17d690186..00000000000 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskFailed.html.golden +++ /dev/null @@ -1,85 +0,0 @@ -From: system@coder.com -To: bobby@coder.com -Subject: Task 'my-workspace' failed -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 -MIME-Version: 1.0 - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; charset=UTF-8 - -Hi Bobby, - -The task 'my-task' has failed. Check the logs for more details. - - -View task: http://test.com/tasks/bobby/my-workspace - -View workspace: http://test.com/@bobby/my-workspace - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/html; charset=UTF-8 - - - - - - - Codestin Search App - - -
-
- 3D"Cod= -
-

- Task 'my-workspace' failed -

-
-

Hi Bobby,

-

The task ‘my-task’ has failed. Check the logs for mo= -re details.

-
-
- =20 - - View task - - =20 - - View workspace - - =20 -
-
-

© 2024 Coder. All rights reserved - h= -ttp://test.com

-

Click here to manage your notification = -settings

-

Stop receiving emails like this

-
-
- - - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4-- diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskIdle.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskIdle.html.golden deleted file mode 100644 index 0c4fea1cf9d..00000000000 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskIdle.html.golden +++ /dev/null @@ -1,84 +0,0 @@ -From: system@coder.com -To: bobby@coder.com -Subject: Task 'my-workspace' is idle -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 -MIME-Version: 1.0 - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; charset=UTF-8 - -Hi Bobby, - -The task 'my-task' is idle and ready for input. - - -View task: http://test.com/tasks/bobby/my-workspace - -View workspace: http://test.com/@bobby/my-workspace - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/html; charset=UTF-8 - - - - - - - Codestin Search App - - -
-
- 3D"Cod= -
-

- Task 'my-workspace' is idle -

-
-

Hi Bobby,

-

The task ‘my-task’ is idle and ready for input.

-
-
- =20 - - View task - - =20 - - View workspace - - =20 -
-
-

© 2024 Coder. All rights reserved - h= -ttp://test.com

-

Click here to manage your notification = -settings

-

Stop receiving emails like this

-
-
- - - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4-- diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskPaused.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskPaused.html.golden deleted file mode 100644 index f22fc19c5b6..00000000000 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskPaused.html.golden +++ /dev/null @@ -1,85 +0,0 @@ -From: system@coder.com -To: bobby@coder.com -Subject: Task 'my-task' is paused -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 -MIME-Version: 1.0 - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; charset=UTF-8 - -Hi Bobby, - -The task 'my-task' was paused (idle timeout). - - -View task: http://test.com/tasks/bobby/00000000-0000-0000-0000-000000000000 - -View workspace: http://test.com/@bobby/my-workspace - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/html; charset=UTF-8 - - - - - - - Codestin Search App - - -
-
- 3D"Cod= -
-

- Task 'my-task' is paused -

-
-

Hi Bobby,

-

The task ‘my-task’ was paused (idle timeout).

-
-
- =20 - - View task - - =20 - - View workspace - - =20 -
-
-

© 2024 Coder. All rights reserved - h= -ttp://test.com

-

Click here to manage your notification = -settings

-

Stop receiving emails like this

-
-
- - - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4-- diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskResumed.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskResumed.html.golden deleted file mode 100644 index 4d71d3c8ec9..00000000000 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskResumed.html.golden +++ /dev/null @@ -1,85 +0,0 @@ -From: system@coder.com -To: bobby@coder.com -Subject: Task 'my-task' has resumed -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 -MIME-Version: 1.0 - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; charset=UTF-8 - -Hi Bobby, - -The task 'my-task' has resumed. - - -View task: http://test.com/tasks/bobby/00000000-0000-0000-0000-000000000001 - -View workspace: http://test.com/@bobby/my-workspace - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/html; charset=UTF-8 - - - - - - - Codestin Search App - - -
-
- 3D"Cod= -
-

- Task 'my-task' has resumed -

-
-

Hi Bobby,

-

The task ‘my-task’ has resumed.

-
-
- =20 - - View task - - =20 - - View workspace - - =20 -
-
-

© 2024 Coder. All rights reserved - h= -ttp://test.com

-

Click here to manage your notification = -settings

-

Stop receiving emails like this

-
-
- - - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4-- diff --git a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskWorking.html.golden b/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskWorking.html.golden deleted file mode 100644 index b0cc318c350..00000000000 --- a/coderd/notifications/testdata/rendered-templates/smtp/TemplateTaskWorking.html.golden +++ /dev/null @@ -1,85 +0,0 @@ -From: system@coder.com -To: bobby@coder.com -Subject: Task 'my-workspace' is working -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 -MIME-Version: 1.0 - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; charset=UTF-8 - -Hi Bobby, - -The task 'my-task' transitioned to a working state. - - -View task: http://test.com/tasks/bobby/my-workspace - -View workspace: http://test.com/@bobby/my-workspace - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/html; charset=UTF-8 - - - - - - - Codestin Search App - - -
-
- 3D"Cod= -
-

- Task 'my-workspace' is working -

-
-

Hi Bobby,

-

The task ‘my-task’ transitioned to a working state.<= -/p> -

-
- =20 - - View task - - =20 - - View workspace - - =20 -
-
-

© 2024 Coder. All rights reserved - h= -ttp://test.com

-

Click here to manage your notification = -settings

-

Stop receiving emails like this

-
-
- - - ---bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4-- diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskCompleted.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskCompleted.json.golden deleted file mode 100644 index 2336bf3162f..00000000000 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskCompleted.json.golden +++ /dev/null @@ -1,33 +0,0 @@ -{ - "_version": "1.1", - "msg_id": "00000000-0000-0000-0000-000000000000", - "payload": { - "_version": "1.2", - "notification_name": "Task Completed", - "notification_template_id": "00000000-0000-0000-0000-000000000000", - "user_id": "00000000-0000-0000-0000-000000000000", - "user_email": "bobby@coder.com", - "user_name": "Bobby", - "user_username": "bobby", - "actions": [ - { - "label": "View task", - "url": "http://test.com/tasks/bobby/my-workspace" - }, - { - "label": "View workspace", - "url": "http://test.com/@bobby/my-workspace" - } - ], - "labels": { - "task": "my-task", - "workspace": "my-workspace" - }, - "data": {}, - "targets": null - }, - "title": "Task 'my-workspace' completed", - "title_markdown": "Task 'my-workspace' completed", - "body": "The task 'my-task' has completed successfully.", - "body_markdown": "The task 'my-task' has completed successfully." -} \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskFailed.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskFailed.json.golden deleted file mode 100644 index 44788581a02..00000000000 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskFailed.json.golden +++ /dev/null @@ -1,33 +0,0 @@ -{ - "_version": "1.1", - "msg_id": "00000000-0000-0000-0000-000000000000", - "payload": { - "_version": "1.2", - "notification_name": "Task Failed", - "notification_template_id": "00000000-0000-0000-0000-000000000000", - "user_id": "00000000-0000-0000-0000-000000000000", - "user_email": "bobby@coder.com", - "user_name": "Bobby", - "user_username": "bobby", - "actions": [ - { - "label": "View task", - "url": "http://test.com/tasks/bobby/my-workspace" - }, - { - "label": "View workspace", - "url": "http://test.com/@bobby/my-workspace" - } - ], - "labels": { - "task": "my-task", - "workspace": "my-workspace" - }, - "data": {}, - "targets": null - }, - "title": "Task 'my-workspace' failed", - "title_markdown": "Task 'my-workspace' failed", - "body": "The task 'my-task' has failed. Check the logs for more details.", - "body_markdown": "The task 'my-task' has failed. Check the logs for more details." -} \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskIdle.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskIdle.json.golden deleted file mode 100644 index 44736053b8f..00000000000 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskIdle.json.golden +++ /dev/null @@ -1,33 +0,0 @@ -{ - "_version": "1.1", - "msg_id": "00000000-0000-0000-0000-000000000000", - "payload": { - "_version": "1.2", - "notification_name": "Task Idle", - "notification_template_id": "00000000-0000-0000-0000-000000000000", - "user_id": "00000000-0000-0000-0000-000000000000", - "user_email": "bobby@coder.com", - "user_name": "Bobby", - "user_username": "bobby", - "actions": [ - { - "label": "View task", - "url": "http://test.com/tasks/bobby/my-workspace" - }, - { - "label": "View workspace", - "url": "http://test.com/@bobby/my-workspace" - } - ], - "labels": { - "task": "my-task", - "workspace": "my-workspace" - }, - "data": {}, - "targets": null - }, - "title": "Task 'my-workspace' is idle", - "title_markdown": "Task 'my-workspace' is idle", - "body": "The task 'my-task' is idle and ready for input.", - "body_markdown": "The task 'my-task' is idle and ready for input." -} \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskPaused.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskPaused.json.golden deleted file mode 100644 index 2fa793fb1cf..00000000000 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskPaused.json.golden +++ /dev/null @@ -1,35 +0,0 @@ -{ - "_version": "1.1", - "msg_id": "00000000-0000-0000-0000-000000000000", - "payload": { - "_version": "1.2", - "notification_name": "Task Paused", - "notification_template_id": "00000000-0000-0000-0000-000000000000", - "user_id": "00000000-0000-0000-0000-000000000000", - "user_email": "bobby@coder.com", - "user_name": "Bobby", - "user_username": "bobby", - "actions": [ - { - "label": "View task", - "url": "http://test.com/tasks/bobby/00000000-0000-0000-0000-000000000000" - }, - { - "label": "View workspace", - "url": "http://test.com/@bobby/my-workspace" - } - ], - "labels": { - "pause_reason": "idle timeout", - "task": "my-task", - "task_id": "00000000-0000-0000-0000-000000000000", - "workspace": "my-workspace" - }, - "data": {}, - "targets": null - }, - "title": "Task 'my-task' is paused", - "title_markdown": "Task 'my-task' is paused", - "body": "The task 'my-task' was paused (idle timeout).", - "body_markdown": "The task 'my-task' was paused (idle timeout)." -} \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskResumed.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskResumed.json.golden deleted file mode 100644 index 1fa3a4149da..00000000000 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskResumed.json.golden +++ /dev/null @@ -1,34 +0,0 @@ -{ - "_version": "1.1", - "msg_id": "00000000-0000-0000-0000-000000000000", - "payload": { - "_version": "1.2", - "notification_name": "Task Resumed", - "notification_template_id": "00000000-0000-0000-0000-000000000000", - "user_id": "00000000-0000-0000-0000-000000000000", - "user_email": "bobby@coder.com", - "user_name": "Bobby", - "user_username": "bobby", - "actions": [ - { - "label": "View task", - "url": "http://test.com/tasks/bobby/00000000-0000-0000-0000-000000000000" - }, - { - "label": "View workspace", - "url": "http://test.com/@bobby/my-workspace" - } - ], - "labels": { - "task": "my-task", - "task_id": "00000000-0000-0000-0000-000000000000", - "workspace": "my-workspace" - }, - "data": {}, - "targets": null - }, - "title": "Task 'my-task' has resumed", - "title_markdown": "Task 'my-task' has resumed", - "body": "The task 'my-task' has resumed.", - "body_markdown": "The task 'my-task' has resumed." -} \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskWorking.json.golden b/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskWorking.json.golden deleted file mode 100644 index aba837ca775..00000000000 --- a/coderd/notifications/testdata/rendered-templates/webhook/TemplateTaskWorking.json.golden +++ /dev/null @@ -1,33 +0,0 @@ -{ - "_version": "1.1", - "msg_id": "00000000-0000-0000-0000-000000000000", - "payload": { - "_version": "1.2", - "notification_name": "Task Working", - "notification_template_id": "00000000-0000-0000-0000-000000000000", - "user_id": "00000000-0000-0000-0000-000000000000", - "user_email": "bobby@coder.com", - "user_name": "Bobby", - "user_username": "bobby", - "actions": [ - { - "label": "View task", - "url": "http://test.com/tasks/bobby/my-workspace" - }, - { - "label": "View workspace", - "url": "http://test.com/@bobby/my-workspace" - } - ], - "labels": { - "task": "my-task", - "workspace": "my-workspace" - }, - "data": {}, - "targets": null - }, - "title": "Task 'my-workspace' is working", - "title_markdown": "Task 'my-workspace' is working", - "body": "The task 'my-task' transitioned to a working state.", - "body_markdown": "The task 'my-task' transitioned to a working state." -} \ No newline at end of file diff --git a/docs/admin/monitoring/notifications/index.md b/docs/admin/monitoring/notifications/index.md index 9a58ebe7be2..8695ea79939 100644 --- a/docs/admin/monitoring/notifications/index.md +++ b/docs/admin/monitoring/notifications/index.md @@ -16,13 +16,6 @@ user(s) of the event. Coder supports the following list of events: -### Task Events - -These notifications are sent to the owner of the workspace where the task is running: - -- Task Idle -- Task Working - ### Template Events These notifications are sent to users with **template admin** roles: From c0f79e0160dec266b66d694bf3272b9710905e49 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:42:51 +0000 Subject: [PATCH 2/2] fix(coderd/database): keep the task AI seat usage reason and renumber to 000592 --- coderd/database/dump.sql | 3 +- ...000592_remove_task_notifications.down.sql} | 13 --- ...> 000592_remove_task_notifications.up.sql} | 14 --- coderd/database/migrations/migrate_test.go | 110 ------------------ coderd/database/models.go | 5 +- coderd/database/querier_test.go | 6 +- 6 files changed, 9 insertions(+), 142 deletions(-) rename coderd/database/migrations/{000591_remove_task_notifications_and_usage.down.sql => 000592_remove_task_notifications.down.sql} (88%) rename coderd/database/migrations/{000591_remove_task_notifications_and_usage.up.sql => 000592_remove_task_notifications.up.sql} (57%) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 26b27dc05d7..abbfda38e5c 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -28,7 +28,8 @@ CREATE TYPE ai_provider_type AS ENUM ( ); CREATE TYPE ai_seat_usage_reason AS ENUM ( - 'aibridge' + 'aibridge', + 'task' ); CREATE TYPE aibridge_interception_error_type AS ENUM ( diff --git a/coderd/database/migrations/000591_remove_task_notifications_and_usage.down.sql b/coderd/database/migrations/000592_remove_task_notifications.down.sql similarity index 88% rename from coderd/database/migrations/000591_remove_task_notifications_and_usage.down.sql rename to coderd/database/migrations/000592_remove_task_notifications.down.sql index c96cb172043..8ba8e02e6f4 100644 --- a/coderd/database/migrations/000591_remove_task_notifications_and_usage.down.sql +++ b/coderd/database/migrations/000592_remove_task_notifications.down.sql @@ -63,16 +63,3 @@ INSERT INTO notification_templates ( 'system'::notification_template_kind, true ); - --- Restore the task AI seat usage reason. Remapped seat events are not --- recoverable. -ALTER TYPE ai_seat_usage_reason RENAME TO ai_seat_usage_reason_old; - -CREATE TYPE ai_seat_usage_reason AS ENUM ( - 'aibridge', - 'task' -); - -ALTER TABLE ai_seat_state ALTER COLUMN last_event_type TYPE ai_seat_usage_reason USING (last_event_type::text::ai_seat_usage_reason); - -DROP TYPE ai_seat_usage_reason_old; diff --git a/coderd/database/migrations/000591_remove_task_notifications_and_usage.up.sql b/coderd/database/migrations/000592_remove_task_notifications.up.sql similarity index 57% rename from coderd/database/migrations/000591_remove_task_notifications_and_usage.up.sql rename to coderd/database/migrations/000592_remove_task_notifications.up.sql index c52ae068534..b44117b4465 100644 --- a/coderd/database/migrations/000591_remove_task_notifications_and_usage.up.sql +++ b/coderd/database/migrations/000592_remove_task_notifications.up.sql @@ -13,17 +13,3 @@ WHERE id IN ( DELETE FROM user_configs WHERE key = 'preference_task_notification_alert_dismissed'; - --- Remove the task AI seat usage reason. Seats consumed by tasks stay --- consumed; their last event type is remapped to aibridge. -UPDATE ai_seat_state SET last_event_type = 'aibridge' WHERE last_event_type::text = 'task'; - -ALTER TYPE ai_seat_usage_reason RENAME TO ai_seat_usage_reason_old; - -CREATE TYPE ai_seat_usage_reason AS ENUM ( - 'aibridge' -); - -ALTER TABLE ai_seat_state ALTER COLUMN last_event_type TYPE ai_seat_usage_reason USING (last_event_type::text::ai_seat_usage_reason); - -DROP TYPE ai_seat_usage_reason_old; diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index a84f08ea420..9844d8ec4bd 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -1190,116 +1190,6 @@ func TestMigration000475AgentsAccessOrgRole(t *testing.T) { ) } -func TestMigration000591RemoveTaskNotificationsAndUsage(t *testing.T) { - t.Parallel() - - const migrationVersion = 591 - sqlDB := testSQLDB(t) - next, err := migrations.Stepper(sqlDB) - require.NoError(t, err) - for { - version, more, err := next() - require.NoError(t, err) - if !more { - t.Fatalf("migration %d not found", migrationVersion) - } - if version == migrationVersion-1 { - break - } - } - - ctx := testutil.Context(t, testutil.WaitLong) - now := time.Now().UTC().Truncate(time.Microsecond) - userID, bridgeUserID, orgID := uuid.New(), uuid.New(), uuid.New() - taskTemplateIDs := pq.StringArray{ - "bd4b7168-d05e-4e19-ad0f-3593b77aa90f", - "d4a6271c-cced-4ed0-84ad-afd02a9c7799", - "8c5a4d12-9f7e-4b3a-a1c8-6e4f2d9b5a7c", - "3b7e8f1a-4c2d-49a6-b5e9-7f3a1c8d6b4e", - "2a74f3d3-ab09-4123-a4a5-ca238f4f65a1", - "843ee9c3-a8fb-4846-afa9-977bec578649", - } - const otherTemplateID = "281fdf73-c6d6-4cbb-8ff5-888baf8a2fff" - - _, err = sqlDB.ExecContext(ctx, ` - INSERT INTO organizations (id, name, display_name, description, created_at, updated_at, default_org_member_roles) - VALUES ($1, $1::uuid::text, 'Notifications', '', $2, $2, '{}')`, orgID, now) - require.NoError(t, err) - _, err = sqlDB.ExecContext(ctx, ` - INSERT INTO users (id, username, email, hashed_password, created_at, updated_at) - VALUES ($1, $1::uuid::text, $1::uuid::text || '@test.com', '', $3, $3), - ($2, $2::uuid::text, $2::uuid::text || '@test.com', '', $3, $3)`, userID, bridgeUserID, now) - require.NoError(t, err) - _, err = sqlDB.ExecContext(ctx, ` - INSERT INTO notification_preferences (user_id, notification_template_id, disabled) - VALUES ($1, $2, true), ($1, $3, true)`, userID, taskTemplateIDs[0], otherTemplateID) - require.NoError(t, err) - _, err = sqlDB.ExecContext(ctx, ` - INSERT INTO user_configs (user_id, key, value) - VALUES ($1, 'preference_task_notification_alert_dismissed', 'true')`, userID) - require.NoError(t, err) - _, err = sqlDB.ExecContext(ctx, ` - INSERT INTO ai_seat_state (user_id, first_used_at, last_used_at, last_event_type, last_event_description, updated_at) - VALUES ($1, $3, $3, 'task', 'task usage', $3), - ($2, $3, $3, 'aibridge', 'bridge usage', $3)`, userID, bridgeUserID, now) - require.NoError(t, err) - - var count int - const taskTemplatesQuery = "SELECT count(*) FROM notification_templates WHERE id = ANY($1::uuid[])" - require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) - require.Equal(t, 6, count) - - version, _, err := next() - require.NoError(t, err) - require.EqualValues(t, migrationVersion, version) - - require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) - require.Zero(t, count) - require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM notification_templates WHERE id = $1", otherTemplateID).Scan(&count)) - require.Equal(t, 1, count) - var preferences pq.StringArray - require.NoError(t, sqlDB.QueryRowContext(ctx, - "SELECT ARRAY(SELECT notification_template_id::text FROM notification_preferences WHERE user_id = $1)", userID).Scan(&preferences)) - require.Equal(t, pq.StringArray{otherTemplateID}, preferences) - require.NoError(t, sqlDB.QueryRowContext(ctx, - "SELECT count(*) FROM user_configs WHERE user_id = $1 AND key = 'preference_task_notification_alert_dismissed'", userID).Scan(&count)) - require.Zero(t, count) - for id, description := range map[uuid.UUID]string{userID: "task usage", bridgeUserID: "bridge usage"} { - var event, gotDescription string - var firstUsed, lastUsed, updated time.Time - require.NoError(t, sqlDB.QueryRowContext(ctx, ` - SELECT last_event_type, last_event_description, first_used_at, last_used_at, updated_at - FROM ai_seat_state WHERE user_id = $1`, id).Scan(&event, &gotDescription, &firstUsed, &lastUsed, &updated)) - require.Equal(t, "aibridge", event) - require.Equal(t, description, gotDescription) - require.True(t, now.Equal(firstUsed)) - require.True(t, now.Equal(lastUsed)) - require.True(t, now.Equal(updated)) - } - var reasons pq.StringArray - const reasonsQuery = "SELECT enum_range(NULL::ai_seat_usage_reason)::text[]" - require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery).Scan(&reasons)) - require.Equal(t, pq.StringArray{"aibridge"}, reasons) - - downSQL, err := os.ReadFile("000591_remove_task_notifications_and_usage.down.sql") - require.NoError(t, err) - _, err = sqlDB.ExecContext(ctx, string(downSQL)) - require.NoError(t, err) - require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) - require.Equal(t, 6, count) - require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery).Scan(&reasons)) - require.Equal(t, pq.StringArray{"aibridge", "task"}, reasons) - - upSQL, err := os.ReadFile("000591_remove_task_notifications_and_usage.up.sql") - require.NoError(t, err) - _, err = sqlDB.ExecContext(ctx, string(upSQL)) - require.NoError(t, err) - require.NoError(t, sqlDB.QueryRowContext(ctx, taskTemplatesQuery, taskTemplateIDs).Scan(&count)) - require.Zero(t, count) - require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery).Scan(&reasons)) - require.Equal(t, pq.StringArray{"aibridge"}, reasons) -} - func TestMigration000587RemoveAgentsAccessRole(t *testing.T) { t.Parallel() diff --git a/coderd/database/models.go b/coderd/database/models.go index c1bfdb4fb1f..b5a1340b353 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -230,6 +230,7 @@ type AISeatUsageReason string const ( AISeatUsageReasonAibridge AISeatUsageReason = "aibridge" + AISeatUsageReasonTask AISeatUsageReason = "task" ) func (e *AISeatUsageReason) Scan(src interface{}) error { @@ -269,7 +270,8 @@ func (ns NullAISeatUsageReason) Value() (driver.Value, error) { func (e AISeatUsageReason) Valid() bool { switch e { - case AISeatUsageReasonAibridge: + case AISeatUsageReasonAibridge, + AISeatUsageReasonTask: return true } return false @@ -278,6 +280,7 @@ func (e AISeatUsageReason) Valid() bool { func AllAISeatUsageReasonValues() []AISeatUsageReason { return []AISeatUsageReason{ AISeatUsageReasonAibridge, + AISeatUsageReasonTask, } } diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 03d153019be..babef6c49ec 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -13700,7 +13700,7 @@ func TestUpsertAISeats(t *testing.T) { newRow, err := db.UpsertAISeatState(ctx, database.UpsertAISeatStateParams{ UserID: user.ID, FirstUsedAt: now.Add(time.Hour * -24), - LastEventType: database.AISeatUsageReasonAibridge, + LastEventType: database.AISeatUsageReasonTask, }) require.NoError(t, err) require.True(t, newRow) @@ -13708,7 +13708,7 @@ func TestUpsertAISeats(t *testing.T) { alreadyExists, err := db.UpsertAISeatState(ctx, database.UpsertAISeatStateParams{ UserID: user.ID, FirstUsedAt: now.Add(time.Hour * -23), - LastEventType: database.AISeatUsageReasonAibridge, + LastEventType: database.AISeatUsageReasonTask, }) require.NoError(t, err) require.False(t, alreadyExists) @@ -13716,7 +13716,7 @@ func TestUpsertAISeats(t *testing.T) { alreadyExists, err = db.UpsertAISeatState(ctx, database.UpsertAISeatStateParams{ UserID: user.ID, FirstUsedAt: now, - LastEventType: database.AISeatUsageReasonAibridge, + LastEventType: database.AISeatUsageReasonTask, }) require.NoError(t, err) require.False(t, alreadyExists)