From 42f0607d2acbab0cf3baef596b736104138f0547 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:21:51 +0000 Subject: [PATCH 1/2] feat!: remove has_ai_task and the task build reasons Drop has_ai_task from template versions and workspace builds, and remove the task build reasons. Map automatic task pauses to autostop and manual pauses or resumes to initiator in workspace builds and build orchestrations. Replacing the build_reason enum rewrites workspace_builds; the timing test exercises 100,000 builds. The down migration restores the schema but cannot recover original reasons or flag values, so restored flags are false. --- coderd/database/dbauthz/dbauthz.go | 2 +- coderd/database/dbauthz/dbauthz_test.go | 3 +- coderd/database/dbfake/dbfake.go | 7 - coderd/database/dbgen/dbgen.go | 8 +- coderd/database/dump.sql | 11 +- ...as_ai_task_and_task_build_reasons.down.sql | 91 ++++++ ..._has_ai_task_and_task_build_reasons.up.sql | 99 ++++++ coderd/database/migrations/migrate_test.go | 288 ++++++++++++++++++ coderd/database/modelqueries.go | 2 - coderd/database/models.go | 15 +- coderd/database/queries.sql.go | 146 +++------ coderd/database/queries/templates.sql | 6 - coderd/database/queries/templateversions.sql | 5 +- coderd/database/queries/workspacebuilds.sql | 1 - coderd/database/queries/workspaces.sql | 16 - .../provisionerdserver/provisionerdserver.go | 6 - docs/admin/security/audit-logs.md | 4 +- enterprise/audit/table.go | 2 - 18 files changed, 533 insertions(+), 179 deletions(-) create mode 100644 coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.down.sql create mode 100644 coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.up.sql diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 14f7f33c7e4e6..4b281f074c8b6 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -8335,7 +8335,7 @@ func (q *querier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context. } func (q *querier) UpdateTemplateVersionFlagsByJobID(ctx context.Context, arg database.UpdateTemplateVersionFlagsByJobIDParams) error { - // An actor is allowed to update the template version ai task and external agent flag if they are authorized to update the template. + // An actor is allowed to update the template version external agent flag if they are authorized to update the template. tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID) if err != nil { return err diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 3cd7b3a3faa9e..9f87cfec00850 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2906,7 +2906,7 @@ func (s *MethodTestSuite) TestTemplate() { s.Run("UpdateTemplateVersionFlagsByJobID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { t := testutil.Fake(s.T(), faker, database.Template{}) tv := testutil.Fake(s.T(), faker, database.TemplateVersion{TemplateID: uuid.NullUUID{UUID: t.ID, Valid: true}}) - arg := database.UpdateTemplateVersionFlagsByJobIDParams{JobID: tv.JobID, HasAITask: sql.NullBool{Bool: true, Valid: true}, HasExternalAgent: sql.NullBool{Bool: true, Valid: true}} + arg := database.UpdateTemplateVersionFlagsByJobIDParams{JobID: tv.JobID, HasExternalAgent: sql.NullBool{Bool: true, Valid: true}} dbm.EXPECT().GetTemplateVersionByJobID(gomock.Any(), tv.JobID).Return(tv, nil).AnyTimes() dbm.EXPECT().GetTemplateByID(gomock.Any(), t.ID).Return(t, nil).AnyTimes() dbm.EXPECT().UpdateTemplateVersionFlagsByJobID(gomock.Any(), arg).Return(nil).AnyTimes() @@ -4453,7 +4453,6 @@ func (s *MethodTestSuite) TestWorkspace() { dbm.EXPECT().UpdateWorkspaceBuildFlagsByID(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() check.Args(database.UpdateWorkspaceBuildFlagsByIDParams{ ID: b.ID, - HasAITask: sql.NullBool{Bool: true, Valid: true}, HasExternalAgent: sql.NullBool{Bool: true, Valid: true}, UpdatedAt: b.UpdatedAt, }).Asserts(w, policy.ActionUpdate) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index d2d0834df662c..1bcd25408a35a 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -297,13 +297,6 @@ func (b WorkspaceBuildBuilder) doInTX() WorkspaceResponse { b.seed.ID = uuid.New() b.seed.JobID = jobID - if b.taskAppID != uuid.Nil { - b.seed.HasAITask = sql.NullBool{ - Bool: true, - Valid: true, - } - } - resp := WorkspaceResponse{ AgentToken: b.agentToken, Agents: make([]database.WorkspaceAgent, 0), diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index ad67930ddfec0..b6a8f82867ce5 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -936,7 +936,6 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil buildID := takeFirst(orig.ID, uuid.New()) jobID := takeFirst(orig.JobID, uuid.New()) - hasAITask := takeFirst(orig.HasAITask, sql.NullBool{}) hasExternalAgent := takeFirst(orig.HasExternalAgent, sql.NullBool{}) var build database.WorkspaceBuild err := db.InTx(func(db database.Store) error { @@ -971,10 +970,9 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil require.NoError(t, err) } - if hasAITask.Valid || hasExternalAgent.Valid { + if hasExternalAgent.Valid { require.NoError(t, db.UpdateWorkspaceBuildFlagsByID(genCtx, database.UpdateWorkspaceBuildFlagsByIDParams{ ID: buildID, - HasAITask: hasAITask, HasExternalAgent: hasExternalAgent, UpdatedAt: dbtime.Now(), })) @@ -1592,7 +1590,6 @@ func ExternalAuthLink(t testing.TB, db database.Store, orig database.ExternalAut func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVersion) database.TemplateVersion { var version database.TemplateVersion - hasAITask := takeFirst(orig.HasAITask, sql.NullBool{}) hasExternalAgent := takeFirst(orig.HasExternalAgent, sql.NullBool{}) jobID := takeFirst(orig.JobID, uuid.New()) err := db.InTx(func(db database.Store) error { @@ -1614,10 +1611,9 @@ func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVers return err } - if hasAITask.Valid || hasExternalAgent.Valid { + if hasExternalAgent.Valid { require.NoError(t, db.UpdateTemplateVersionFlagsByJobID(genCtx, database.UpdateTemplateVersionFlagsByJobIDParams{ JobID: jobID, - HasAITask: hasAITask, HasExternalAgent: hasExternalAgent, UpdatedAt: dbtime.Now(), })) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index abbfda38e5c9b..d33a18ef14b3f 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -334,10 +334,7 @@ CREATE TYPE build_reason AS ENUM ( 'cli', 'ssh_connection', 'vscode_connection', - 'jetbrains_connection', - 'task_auto_pause', - 'task_manual_pause', - 'task_resume' + 'jetbrains_connection' ); CREATE TYPE chat_client_type AS ENUM ( @@ -3189,7 +3186,6 @@ CREATE TABLE workspace_builds ( daily_cost integer DEFAULT 0 NOT NULL, max_deadline timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL, template_version_preset_id uuid, - has_ai_task boolean, has_external_agent boolean, notified_autostop_deadline timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL, CONSTRAINT workspace_builds_deadline_below_max_deadline CHECK ((((deadline <> '0001-01-01 00:00:00+00'::timestamp with time zone) AND (deadline <= max_deadline)) OR (max_deadline = '0001-01-01 00:00:00+00'::timestamp with time zone))) @@ -3500,7 +3496,6 @@ CREATE TABLE template_versions ( message character varying(1048576) DEFAULT ''::character varying NOT NULL, archived boolean DEFAULT false NOT NULL, source_example_id text, - has_ai_task boolean, has_external_agent boolean ); @@ -3522,7 +3517,6 @@ CREATE VIEW template_version_with_user AS template_versions.message, template_versions.archived, template_versions.source_example_id, - template_versions.has_ai_task, template_versions.has_external_agent, COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username, @@ -4132,7 +4126,6 @@ CREATE VIEW workspace_build_with_user AS workspace_builds.daily_cost, workspace_builds.max_deadline, workspace_builds.template_version_preset_id, - workspace_builds.has_ai_task, workspace_builds.has_external_agent, workspace_builds.notified_autostop_deadline, COALESCE(visible_users.avatar_url, ''::text) AS initiator_by_avatar_url, @@ -5019,8 +5012,6 @@ CREATE INDEX idx_telemetry_locks_period_ending_at ON telemetry_locks USING btree CREATE UNIQUE INDEX idx_template_version_presets_default ON template_version_presets USING btree (template_version_id) WHERE (is_default = true); -CREATE INDEX idx_template_versions_has_ai_task ON template_versions USING btree (has_ai_task); - CREATE UNIQUE INDEX idx_unique_preset_name ON template_version_presets USING btree (name, template_version_id); CREATE UNIQUE INDEX idx_usage_events_agent_runtime ON usage_events USING btree (event_type, created_at) WHERE (event_type = 'hb_agent_runtime_v1'::text); diff --git a/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.down.sql b/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.down.sql new file mode 100644 index 0000000000000..d633f79969934 --- /dev/null +++ b/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.down.sql @@ -0,0 +1,91 @@ +-- Drop views before modifying underlying tables and columns. +DROP VIEW IF EXISTS template_version_with_user; +DROP VIEW IF EXISTS workspace_build_with_user; + +-- Recreate has_ai_task columns and index. The dropped values are not +-- recoverable, so backfill false to satisfy older migrations that restore +-- NOT NULL on the way further down. +ALTER TABLE template_versions ADD COLUMN has_ai_task boolean DEFAULT false; +ALTER TABLE template_versions ALTER COLUMN has_ai_task DROP DEFAULT; +CREATE INDEX idx_template_versions_has_ai_task ON template_versions USING btree (has_ai_task); +ALTER TABLE workspace_builds ADD COLUMN has_ai_task boolean DEFAULT false; +ALTER TABLE workspace_builds ALTER COLUMN has_ai_task DROP DEFAULT; + +-- Recreate build_reason enum with task values. +ALTER TYPE build_reason RENAME TO build_reason_old; + +CREATE TYPE build_reason AS ENUM ( + 'initiator', + 'autostart', + 'autostop', + 'dormancy', + 'failedstop', + 'autodelete', + 'dashboard', + 'cli', + 'ssh_connection', + 'vscode_connection', + 'jetbrains_connection', + 'task_auto_pause', + 'task_manual_pause', + 'task_resume' +); + +ALTER TABLE workspace_builds ALTER COLUMN reason DROP DEFAULT; +ALTER TABLE workspace_builds ALTER COLUMN reason TYPE build_reason USING (reason::text::build_reason); +ALTER TABLE workspace_builds ALTER COLUMN reason SET DEFAULT 'initiator'::build_reason; + +ALTER TABLE workspace_build_orchestrations ALTER COLUMN child_reason TYPE build_reason USING (child_reason::text::build_reason); + +DROP TYPE build_reason_old; + +-- Recreate views. +CREATE VIEW template_version_with_user AS + SELECT template_versions.id, + template_versions.template_id, + template_versions.organization_id, + template_versions.created_at, + template_versions.updated_at, + template_versions.name, + template_versions.readme, + template_versions.job_id, + template_versions.created_by, + template_versions.external_auth_providers, + template_versions.message, + template_versions.archived, + template_versions.source_example_id, + template_versions.has_ai_task, + template_versions.has_external_agent, + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name + FROM (template_versions + LEFT JOIN visible_users ON ((template_versions.created_by = visible_users.id))); + +COMMENT ON VIEW template_version_with_user IS 'Joins in the username + avatar url of the created by user.'; + +CREATE VIEW workspace_build_with_user AS + SELECT workspace_builds.id, + workspace_builds.created_at, + workspace_builds.updated_at, + workspace_builds.workspace_id, + workspace_builds.template_version_id, + workspace_builds.build_number, + workspace_builds.transition, + workspace_builds.initiator_id, + workspace_builds.job_id, + workspace_builds.deadline, + workspace_builds.reason, + workspace_builds.daily_cost, + workspace_builds.max_deadline, + workspace_builds.template_version_preset_id, + workspace_builds.has_ai_task, + workspace_builds.has_external_agent, + workspace_builds.notified_autostop_deadline, + COALESCE(visible_users.avatar_url, ''::text) AS initiator_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS initiator_by_username, + COALESCE(visible_users.name, ''::text) AS initiator_by_name + FROM (workspace_builds + LEFT JOIN visible_users ON ((workspace_builds.initiator_id = visible_users.id))); + +COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.'; diff --git a/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.up.sql b/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.up.sql new file mode 100644 index 0000000000000..9de1cdfe69510 --- /dev/null +++ b/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.up.sql @@ -0,0 +1,99 @@ +DROP VIEW IF EXISTS template_version_with_user; +DROP VIEW IF EXISTS workspace_build_with_user; + +-- Drop has_ai_task index and columns. +DROP INDEX IF EXISTS idx_template_versions_has_ai_task; +ALTER TABLE template_versions DROP COLUMN IF EXISTS has_ai_task; +ALTER TABLE workspace_builds DROP COLUMN IF EXISTS has_ai_task; + +-- Remove task-related build reasons from build_reason enum. Automatic +-- pauses stay system-attributed; manual pauses and resumes were created by +-- authenticated user requests, so they keep user attribution as initiator. +UPDATE workspace_builds +SET reason = 'autostop' +WHERE reason::text = 'task_auto_pause'; + +UPDATE workspace_builds +SET reason = 'initiator' +WHERE reason::text IN ('task_manual_pause', 'task_resume'); + +UPDATE workspace_build_orchestrations +SET child_reason = 'autostop' +WHERE child_reason::text = 'task_auto_pause'; + +UPDATE workspace_build_orchestrations +SET child_reason = 'initiator' +WHERE child_reason::text IN ('task_manual_pause', 'task_resume'); + +ALTER TYPE build_reason RENAME TO build_reason_old; + +CREATE TYPE build_reason AS ENUM ( + 'initiator', + 'autostart', + 'autostop', + 'dormancy', + 'failedstop', + 'autodelete', + 'dashboard', + 'cli', + 'ssh_connection', + 'vscode_connection', + 'jetbrains_connection' +); + +ALTER TABLE workspace_builds ALTER COLUMN reason DROP DEFAULT; +ALTER TABLE workspace_builds ALTER COLUMN reason TYPE build_reason USING (reason::text::build_reason); +ALTER TABLE workspace_builds ALTER COLUMN reason SET DEFAULT 'initiator'::build_reason; + +ALTER TABLE workspace_build_orchestrations ALTER COLUMN child_reason TYPE build_reason USING (child_reason::text::build_reason); + +DROP TYPE build_reason_old; + +-- Recreate views without task columns. +CREATE VIEW template_version_with_user AS + SELECT template_versions.id, + template_versions.template_id, + template_versions.organization_id, + template_versions.created_at, + template_versions.updated_at, + template_versions.name, + template_versions.readme, + template_versions.job_id, + template_versions.created_by, + template_versions.external_auth_providers, + template_versions.message, + template_versions.archived, + template_versions.source_example_id, + template_versions.has_external_agent, + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name + FROM (template_versions + LEFT JOIN visible_users ON ((template_versions.created_by = visible_users.id))); + +COMMENT ON VIEW template_version_with_user IS 'Joins in the username + avatar url of the created by user.'; + +CREATE VIEW workspace_build_with_user AS + SELECT workspace_builds.id, + workspace_builds.created_at, + workspace_builds.updated_at, + workspace_builds.workspace_id, + workspace_builds.template_version_id, + workspace_builds.build_number, + workspace_builds.transition, + workspace_builds.initiator_id, + workspace_builds.job_id, + workspace_builds.deadline, + workspace_builds.reason, + workspace_builds.daily_cost, + workspace_builds.max_deadline, + workspace_builds.template_version_preset_id, + workspace_builds.has_external_agent, + workspace_builds.notified_autostop_deadline, + COALESCE(visible_users.avatar_url, ''::text) AS initiator_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS initiator_by_username, + COALESCE(visible_users.name, ''::text) AS initiator_by_name + FROM (workspace_builds + LEFT JOIN visible_users ON ((workspace_builds.initiator_id = visible_users.id))); + +COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.'; diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index 9844d8ec4bdbc..27af55f0799a8 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -1190,6 +1190,294 @@ func TestMigration000475AgentsAccessOrgRole(t *testing.T) { ) } +func TestMigration000592RemoveHasAITaskAndTaskBuildReasons(t *testing.T) { + t.Parallel() + + const migrationVersion = 592 + 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) + orgID, userID, templateID, versionID, templateJobID, workspaceID := uuid.New(), uuid.New(), uuid.New(), uuid.New(), uuid.New(), uuid.New() + buildIDs := []uuid.UUID{uuid.New(), uuid.New(), uuid.New(), uuid.New(), uuid.New()} + fixtures := []struct { + query string + args []any + }{ + { + "INSERT INTO organizations (id, name, display_name, description, created_at, updated_at, default_org_member_roles) VALUES ($1, $2, '', '', $3, $3, '{}')", + []any{orgID, orgID.String(), now}, + }, + { + "INSERT INTO users (id, username, email, hashed_password, created_at, updated_at) VALUES ($1, $2, $3, '', $4, $4)", + []any{userID, userID.String(), userID.String() + "@test.com", now}, + }, + { + "INSERT INTO provisioner_jobs (id, created_at, updated_at, organization_id, initiator_id, provisioner, storage_method, file_id, type, input) VALUES ($1, $2, $2, $3, $4, 'echo', 'file', $1, 'template_version_import', '{}')", + []any{templateJobID, now, orgID, userID}, + }, + { + "INSERT INTO template_versions (id, organization_id, name, readme, created_at, updated_at, job_id, created_by, has_ai_task) VALUES ($1, $2, 'v1', '', $3, $3, $4, $5, true)", + []any{versionID, orgID, now, templateJobID, userID}, + }, + { + "INSERT INTO templates (id, organization_id, name, created_at, updated_at, provisioner, active_version_id, created_by) VALUES ($1, $2, 'build-reasons', $3, $3, 'echo', $4, $5)", + []any{templateID, orgID, now, versionID, userID}, + }, + { + "UPDATE template_versions SET template_id = $1 WHERE id = $2", + []any{templateID, versionID}, + }, + { + "INSERT INTO workspaces (id, created_at, updated_at, owner_id, organization_id, template_id, name) VALUES ($1, $2, $2, $3, $4, $5, 'build-reasons')", + []any{workspaceID, now, userID, orgID, templateID}, + }, + } + for _, fixture := range fixtures { + _, err := sqlDB.ExecContext(ctx, fixture.query, fixture.args...) + require.NoError(t, err) + } + for i, reason := range []string{"task_auto_pause", "task_manual_pause", "task_resume", "initiator", "autostop"} { + jobID := uuid.New() + _, err = sqlDB.ExecContext(ctx, + "INSERT INTO provisioner_jobs (id, created_at, updated_at, organization_id, initiator_id, provisioner, storage_method, file_id, type, input) VALUES ($1, $2, $2, $3, $4, 'echo', 'file', $1, 'workspace_build', '{}')", + jobID, now, orgID, userID) + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, + "INSERT INTO workspace_builds (id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, reason, has_ai_task) VALUES ($1, $2, $2, $3, $4, $5, 'start', $6, $7, $8, $9)", + buildIDs[i], now, workspaceID, versionID, i+1, userID, jobID, reason, i == 0) + require.NoError(t, err) + } + _, err = sqlDB.ExecContext(ctx, + "INSERT INTO workspace_build_orchestrations (id, created_at, updated_at, workspace_id, parent_build_id, child_transition, child_reason) VALUES ($1, $2, $2, $3, $4, 'start', 'task_auto_pause')", + uuid.New(), now, workspaceID, buildIDs[0]) + require.NoError(t, err) + + version, _, err := next() + require.NoError(t, err) + require.EqualValues(t, migrationVersion, version) + var count int + const columnsQuery = "SELECT count(*) FROM information_schema.columns WHERE table_schema = 'public' AND table_name IN ('template_versions', 'workspace_builds') AND column_name = 'has_ai_task'" + require.NoError(t, sqlDB.QueryRowContext(ctx, columnsQuery).Scan(&count)) + require.Zero(t, count) + const indexQuery = "SELECT count(*) FROM pg_indexes WHERE schemaname = 'public' AND indexname = 'idx_template_versions_has_ai_task'" + require.NoError(t, sqlDB.QueryRowContext(ctx, indexQuery).Scan(&count)) + require.Zero(t, count) + var reasons pq.StringArray + const reasonsQuery = "SELECT array_agg(reason::text ORDER BY build_number) FROM workspace_builds WHERE workspace_id = $1" + wantReasons := pq.StringArray{"autostop", "initiator", "initiator", "initiator", "autostop"} + require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery, workspaceID).Scan(&reasons)) + require.Equal(t, wantReasons, reasons) + var childReason string + require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT child_reason FROM workspace_build_orchestrations WHERE workspace_id = $1", workspaceID).Scan(&childReason)) + require.Equal(t, "autostop", childReason) + var enumValues pq.StringArray + const enumQuery = "SELECT enum_range(NULL::build_reason)::text[]" + require.NoError(t, sqlDB.QueryRowContext(ctx, enumQuery).Scan(&enumValues)) + wantEnum := pq.StringArray{"initiator", "autostart", "autostop", "dormancy", "failedstop", "autodelete", "dashboard", "cli", "ssh_connection", "vscode_connection", "jetbrains_connection"} + require.Equal(t, wantEnum, enumValues) + require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM template_version_with_user").Scan(&count)) + require.Equal(t, 1, count) + require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM workspace_build_with_user").Scan(&count)) + require.Equal(t, len(buildIDs), count) + + downSQL, err := os.ReadFile("000592_remove_has_ai_task_and_task_build_reasons.down.sql") + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, string(downSQL)) + require.NoError(t, err) + require.NoError(t, sqlDB.QueryRowContext(ctx, columnsQuery).Scan(&count)) + require.Equal(t, 2, count) + require.NoError(t, sqlDB.QueryRowContext(ctx, indexQuery).Scan(&count)) + require.Equal(t, 1, count) + require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM template_versions WHERE has_ai_task IS FALSE").Scan(&count)) + require.Equal(t, 1, count) + require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM workspace_builds WHERE has_ai_task IS FALSE").Scan(&count)) + require.Equal(t, len(buildIDs), count) + require.NoError(t, sqlDB.QueryRowContext(ctx, enumQuery).Scan(&enumValues)) + require.Equal(t, append(wantEnum, "task_auto_pause", "task_manual_pause", "task_resume"), enumValues) + // The original reasons cannot be recovered after remapping. + require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery, workspaceID).Scan(&reasons)) + require.Equal(t, wantReasons, reasons) + require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT child_reason FROM workspace_build_orchestrations WHERE workspace_id = $1", workspaceID).Scan(&childReason)) + require.Equal(t, "autostop", childReason) + + upSQL, err := os.ReadFile("000592_remove_has_ai_task_and_task_build_reasons.up.sql") + require.NoError(t, err) + _, err = sqlDB.ExecContext(ctx, string(upSQL)) + require.NoError(t, err) +} + +func TestMigration000592TaskBuildReasonRewriteTiming(t *testing.T) { + t.Parallel() + + if testing.Short() { + t.SkipNow() + return + } + + // The enum replacement rewrites workspace_builds, risking slow upgrades + // on large deployments. + const ( + migrationVersion = 592 + workspaceCount = 1_000 + buildCount = 100_000 + buildsPerWorkspace = buildCount / workspaceCount + orchestrationCount = 5_000 + taskReasonBuilds = 3 * buildCount / 10 + ) + + 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.WaitSuperLong) + now := time.Now().UTC().Truncate(time.Microsecond) + + orgID := uuid.New() + userID := uuid.New() + templateID := uuid.New() + templateVersionID := uuid.New() + templateJobID := uuid.New() + + // Deterministic IDs let the bulk inserts join rows generated by + // generate_series without round-tripping IDs through the test. + const ( + workspaceIDPattern = "('a0000000-0000-4000-8000-' || lpad(%s::text, 12, '0'))::uuid" + jobIDPattern = "('b0000000-0000-4000-8000-' || lpad(%s::text, 12, '0'))::uuid" + buildIDPattern = "('c0000000-0000-4000-8000-' || lpad(%s::text, 12, '0'))::uuid" + ) + + fixtures := []struct { + query string + args []any + }{ + { + `INSERT INTO organizations (id, name, display_name, description, created_at, updated_at, default_org_member_roles) VALUES ($1, $2, $3, $4, $5, $6, '{}')`, + []any{orgID, "timing-org", "Timing Org", "", now, now}, + }, + { + `INSERT INTO users (id, username, email, hashed_password, created_at, updated_at, status, rbac_roles, login_type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, + []any{userID, "timinguser", "timing@example.com", []byte{}, now, now, "active", []byte("{}"), "password"}, + }, + { + `INSERT INTO provisioner_jobs (id, created_at, updated_at, started_at, completed_at, error, organization_id, initiator_id, provisioner, storage_method, file_id, type, input, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`, + []any{templateJobID, now, now, now, now, "", orgID, userID, "terraform", "file", uuid.New(), "template_version_import", []byte("{}"), []byte("{}")}, + }, + { + `INSERT INTO template_versions (id, organization_id, name, readme, created_at, updated_at, job_id, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, + []any{templateVersionID, orgID, "v1.0", "", now, now, templateJobID, userID}, + }, + { + `INSERT INTO templates (id, organization_id, name, created_at, updated_at, provisioner, active_version_id, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, + []any{templateID, orgID, "timing-template", now, now, "terraform", templateVersionID, userID}, + }, + { + `UPDATE template_versions SET template_id = $1 WHERE id = $2`, + []any{templateID, templateVersionID}, + }, + { + fmt.Sprintf(`INSERT INTO workspaces (id, created_at, updated_at, owner_id, organization_id, template_id, name, last_used_at) + SELECT `+workspaceIDPattern+`, $1, $1, $2, $3, $4, 'timing-ws-' || g, $1 + FROM generate_series(0, %d) AS g`, "g", workspaceCount-1), + []any{now, userID, orgID, templateID}, + }, + { + fmt.Sprintf(`INSERT INTO provisioner_jobs (id, created_at, updated_at, started_at, completed_at, error, organization_id, initiator_id, provisioner, storage_method, file_id, type, input, tags) + SELECT `+jobIDPattern+`, $1, $1, $1, $1, '', $2, $3, 'terraform', 'file', $4, 'workspace_build', '{}'::jsonb, '{}'::jsonb + FROM generate_series(0, %d) AS g`, "g", buildCount-1), + []any{now, orgID, userID, uuid.New()}, + }, + { + fmt.Sprintf(`INSERT INTO workspace_builds (id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, reason, has_ai_task) + SELECT `+buildIDPattern+`, $1, $1, `+fmt.Sprintf(workspaceIDPattern, "(g / %d)")+`, $2, (g %% %d) + 1, + CASE WHEN g %% 2 = 0 THEN 'start' ELSE 'stop' END::workspace_transition, $3, `+jobIDPattern+`, + CASE g %% 10 + WHEN 0 THEN 'task_auto_pause' + WHEN 1 THEN 'task_manual_pause' + WHEN 2 THEN 'task_resume' + WHEN 3 THEN 'autostart' + WHEN 4 THEN 'autostop' + ELSE 'initiator' + END::build_reason, + CASE WHEN g %% 10 < 3 THEN true END + FROM generate_series(0, %d) AS g`, "g", buildsPerWorkspace, buildsPerWorkspace, "g", buildCount-1), + []any{now, templateVersionID, userID}, + }, + { + fmt.Sprintf(`INSERT INTO workspace_build_orchestrations (id, created_at, updated_at, workspace_id, parent_build_id, child_transition, child_reason) + SELECT ('e0000000-0000-4000-8000-' || lpad(g::text, 12, '0'))::uuid, $1, $1, `+fmt.Sprintf(workspaceIDPattern, "(g * 20 / %d)")+`, `+fmt.Sprintf(buildIDPattern, "(g * 20)")+`, 'start', + CASE g %% 3 + WHEN 0 THEN 'task_auto_pause' + WHEN 1 THEN 'task_resume' + ELSE 'initiator' + END::build_reason + FROM generate_series(0, %d) AS g`, buildsPerWorkspace, orchestrationCount-1), + []any{now}, + }, + } + for i, fixture := range fixtures { + _, err := sqlDB.ExecContext(ctx, fixture.query, fixture.args...) + require.NoErrorf(t, err, "fixture %d", i) + } + + countByReason := func(table, column, reason string) int { + var n int + //nolint:gosec // Test-only query built from constant table/column names. + err := sqlDB.QueryRowContext(ctx, fmt.Sprintf("SELECT count(*) FROM %s WHERE %s::text = $1", table, column), reason).Scan(&n) + require.NoError(t, err) + return n + } + wantAutostop := countByReason("workspace_builds", "reason", "autostop") + countByReason("workspace_builds", "reason", "task_auto_pause") + wantInitiator := countByReason("workspace_builds", "reason", "initiator") + countByReason("workspace_builds", "reason", "task_manual_pause") + countByReason("workspace_builds", "reason", "task_resume") + wantChildAutostop := countByReason("workspace_build_orchestrations", "child_reason", "autostop") + countByReason("workspace_build_orchestrations", "child_reason", "task_auto_pause") + wantChildInitiator := countByReason("workspace_build_orchestrations", "child_reason", "initiator") + countByReason("workspace_build_orchestrations", "child_reason", "task_resume") + + start := time.Now() + version, _, err := next() + require.NoError(t, err) + require.EqualValues(t, migrationVersion, version) + elapsed := time.Since(start) + t.Logf("migration %d over %d workspace builds (%d with task reasons) and %d orchestrations took %s", migrationVersion, buildCount, taskReasonBuilds, orchestrationCount, elapsed) + // Bound migration time to catch regressions that could prolong upgrade downtime. + require.Lessf(t, elapsed, 2*time.Minute, "migration %d took %s at %d workspace builds", migrationVersion, elapsed, buildCount) + + var totalBuilds int + err = sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM workspace_builds").Scan(&totalBuilds) + require.NoError(t, err) + require.Equal(t, buildCount, totalBuilds) + require.Equal(t, wantAutostop, countByReason("workspace_builds", "reason", "autostop")) + require.Equal(t, wantInitiator, countByReason("workspace_builds", "reason", "initiator")) + require.Equal(t, wantChildAutostop, countByReason("workspace_build_orchestrations", "child_reason", "autostop")) + require.Equal(t, wantChildInitiator, countByReason("workspace_build_orchestrations", "child_reason", "initiator")) + + var count int + err = sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'workspace_builds' AND column_name = 'has_ai_task'").Scan(&count) + require.NoError(t, err) + require.Zero(t, count) +} + func TestMigration000587RemoveAgentsAccessRole(t *testing.T) { t.Parallel() diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 8345b8bd5a4b3..b0d06b65d5d72 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -145,7 +145,6 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate arg.FuzzyDisplayName, pq.Array(arg.IDs), arg.Deprecated, - arg.HasAITask, arg.AgentsAllowed, arg.AuthorID, arg.AuthorUsername, @@ -340,7 +339,6 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa arg.LastUsedBefore, arg.LastUsedAfter, arg.UsingActive, - arg.HasAITask, arg.HasExternalAgent, arg.Shared, arg.SharedWithUserID, diff --git a/coderd/database/models.go b/coderd/database/models.go index b5a1340b353c4..d60a493c22c61 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1366,9 +1366,6 @@ const ( BuildReasonSshConnection BuildReason = "ssh_connection" BuildReasonVscodeConnection BuildReason = "vscode_connection" BuildReasonJetbrainsConnection BuildReason = "jetbrains_connection" - BuildReasonTaskAutoPause BuildReason = "task_auto_pause" - BuildReasonTaskManualPause BuildReason = "task_manual_pause" - BuildReasonTaskResume BuildReason = "task_resume" ) func (e *BuildReason) Scan(src interface{}) error { @@ -1418,10 +1415,7 @@ func (e BuildReason) Valid() bool { BuildReasonCli, BuildReasonSshConnection, BuildReasonVscodeConnection, - BuildReasonJetbrainsConnection, - BuildReasonTaskAutoPause, - BuildReasonTaskManualPause, - BuildReasonTaskResume: + BuildReasonJetbrainsConnection: return true } return false @@ -1440,9 +1434,6 @@ func AllBuildReasonValues() []BuildReason { BuildReasonSshConnection, BuildReasonVscodeConnection, BuildReasonJetbrainsConnection, - BuildReasonTaskAutoPause, - BuildReasonTaskManualPause, - BuildReasonTaskResume, } } @@ -6214,7 +6205,6 @@ type TemplateVersion struct { Message string `db:"message" json:"message"` Archived bool `db:"archived" json:"archived"` SourceExampleID sql.NullString `db:"source_example_id" json:"source_example_id"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` @@ -6306,7 +6296,6 @@ type TemplateVersionTable struct { Message string `db:"message" json:"message"` Archived bool `db:"archived" json:"archived"` SourceExampleID sql.NullString `db:"source_example_id" json:"source_example_id"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` } @@ -6840,7 +6829,6 @@ type WorkspaceBuild struct { DailyCost int32 `db:"daily_cost" json:"daily_cost"` MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"` TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` NotifiedAutostopDeadline time.Time `db:"notified_autostop_deadline" json:"notified_autostop_deadline"` InitiatorByAvatarUrl string `db:"initiator_by_avatar_url" json:"initiator_by_avatar_url"` @@ -6897,7 +6885,6 @@ type WorkspaceBuildTable struct { DailyCost int32 `db:"daily_cost" json:"daily_cost"` MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"` TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` // The autostop deadline value that an autostop reminder notification was last sent for. Used for idempotence: when it equals the build deadline the reminder has already been sent, and it re-arms automatically when the deadline changes. NotifiedAutostopDeadline time.Time `db:"notified_autostop_deadline" json:"notified_autostop_deadline"` diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 26d2a934c618f..fe06b407691ee 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -28275,35 +28275,29 @@ WHERE END ELSE true END - -- Filter by has_ai_task in latest version - AND CASE - WHEN $9 :: boolean IS NOT NULL THEN - tv.has_ai_task = $9 :: boolean - ELSE true - END -- Filter by agents_allowed AND CASE - WHEN $10 :: boolean IS NOT NULL THEN - t.agents_allowed = $10 :: boolean + WHEN $9 :: boolean IS NOT NULL THEN + t.agents_allowed = $9 :: boolean ELSE true END -- Filter by author_id AND CASE - WHEN $11 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN - t.created_by = $11 + WHEN $10 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN + t.created_by = $10 ELSE true END -- Filter by author_username AND CASE - WHEN $12 :: text != '' THEN - t.created_by = (SELECT id FROM users WHERE lower(users.username) = lower($12) AND deleted = false) + WHEN $11 :: text != '' THEN + t.created_by = (SELECT id FROM users WHERE lower(users.username) = lower($11) AND deleted = false) ELSE true END -- Filter by has_external_agent in latest version AND CASE - WHEN $13 :: boolean IS NOT NULL THEN - tv.has_external_agent = $13 :: boolean + WHEN $12 :: boolean IS NOT NULL THEN + tv.has_external_agent = $12 :: boolean ELSE true END -- Authorize Filter clause will be injected below in GetAuthorizedTemplates @@ -28320,7 +28314,6 @@ type GetTemplatesWithFilterParams struct { FuzzyDisplayName string `db:"fuzzy_display_name" json:"fuzzy_display_name"` IDs []uuid.UUID `db:"ids" json:"ids"` Deprecated sql.NullBool `db:"deprecated" json:"deprecated"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` AgentsAllowed sql.NullBool `db:"agents_allowed" json:"agents_allowed"` AuthorID uuid.UUID `db:"author_id" json:"author_id"` AuthorUsername string `db:"author_username" json:"author_username"` @@ -28337,7 +28330,6 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate arg.FuzzyDisplayName, pq.Array(arg.IDs), arg.Deprecated, - arg.HasAITask, arg.AgentsAllowed, arg.AuthorID, arg.AuthorUsername, @@ -28848,7 +28840,7 @@ FROM -- Scope an archive to a single template and ignore already archived template versions ( SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent FROM template_versions WHERE @@ -28949,7 +28941,7 @@ func (q *sqlQuerier) ArchiveUnusedTemplateVersions(ctx context.Context, arg Arch const getPreviousTemplateVersion = `-- name: GetPreviousTemplateVersion :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -28987,7 +28979,6 @@ func (q *sqlQuerier) GetPreviousTemplateVersion(ctx context.Context, arg GetPrev &i.Message, &i.Archived, &i.SourceExampleID, - &i.HasAITask, &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, @@ -28998,7 +28989,7 @@ func (q *sqlQuerier) GetPreviousTemplateVersion(ctx context.Context, arg GetPrev const getTemplateVersionByID = `-- name: GetTemplateVersionByID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -29022,7 +29013,6 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) ( &i.Message, &i.Archived, &i.SourceExampleID, - &i.HasAITask, &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, @@ -29033,7 +29023,7 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) ( const getTemplateVersionByJobID = `-- name: GetTemplateVersionByJobID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -29057,7 +29047,6 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U &i.Message, &i.Archived, &i.SourceExampleID, - &i.HasAITask, &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, @@ -29068,7 +29057,7 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U const getTemplateVersionByTemplateIDAndName = `-- name: GetTemplateVersionByTemplateIDAndName :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -29098,7 +29087,6 @@ func (q *sqlQuerier) GetTemplateVersionByTemplateIDAndName(ctx context.Context, &i.Message, &i.Archived, &i.SourceExampleID, - &i.HasAITask, &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, @@ -29109,7 +29097,7 @@ func (q *sqlQuerier) GetTemplateVersionByTemplateIDAndName(ctx context.Context, const getTemplateVersionsByIDs = `-- name: GetTemplateVersionsByIDs :many SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -29139,7 +29127,6 @@ func (q *sqlQuerier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UU &i.Message, &i.Archived, &i.SourceExampleID, - &i.HasAITask, &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, @@ -29160,7 +29147,7 @@ func (q *sqlQuerier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UU const getTemplateVersionsByTemplateID = `-- name: GetTemplateVersionsByTemplateID :many SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -29237,7 +29224,6 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge &i.Message, &i.Archived, &i.SourceExampleID, - &i.HasAITask, &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, @@ -29257,7 +29243,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge } const getTemplateVersionsCreatedAfter = `-- name: GetTemplateVersionsCreatedAfter :many -SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE created_at > $1 +SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE created_at > $1 ` func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) { @@ -29283,7 +29269,6 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create &i.Message, &i.Archived, &i.SourceExampleID, - &i.HasAITask, &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, @@ -29450,27 +29435,20 @@ const updateTemplateVersionFlagsByJobID = `-- name: UpdateTemplateVersionFlagsBy UPDATE template_versions SET - has_ai_task = $2, - has_external_agent = $3, - updated_at = $4 + has_external_agent = $2, + updated_at = $3 WHERE job_id = $1 ` type UpdateTemplateVersionFlagsByJobIDParams struct { JobID uuid.UUID `db:"job_id" json:"job_id"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } func (q *sqlQuerier) UpdateTemplateVersionFlagsByJobID(ctx context.Context, arg UpdateTemplateVersionFlagsByJobIDParams) error { - _, err := q.db.ExecContext(ctx, updateTemplateVersionFlagsByJobID, - arg.JobID, - arg.HasAITask, - arg.HasExternalAgent, - arg.UpdatedAt, - ) + _, err := q.db.ExecContext(ctx, updateTemplateVersionFlagsByJobID, arg.JobID, arg.HasExternalAgent, arg.UpdatedAt) return err } @@ -33990,7 +33968,7 @@ const getAuthenticatedWorkspaceAgentAndBuildByAuthToken = `-- name: GetAuthentic SELECT workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, workspace_agents.api_version, workspace_agents.display_order, workspace_agents.parent_id, workspace_agents.api_key_scope, workspace_agents.deleted, - workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.has_external_agent, workspace_build_with_user.notified_autostop_deadline, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name, + workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_external_agent, workspace_build_with_user.notified_autostop_deadline, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name, tasks.id AS task_id FROM workspace_agents @@ -34134,7 +34112,6 @@ func (q *sqlQuerier) GetAuthenticatedWorkspaceAgentAndBuildByAuthToken(ctx conte &i.WorkspaceBuild.DailyCost, &i.WorkspaceBuild.MaxDeadline, &i.WorkspaceBuild.TemplateVersionPresetID, - &i.WorkspaceBuild.HasAITask, &i.WorkspaceBuild.HasExternalAgent, &i.WorkspaceBuild.NotifiedAutostopDeadline, &i.WorkspaceBuild.InitiatorByAvatarUrl, @@ -37673,7 +37650,7 @@ func (q *sqlQuerier) InsertWorkspaceBuildParameters(ctx context.Context, arg Ins } const getActiveWorkspaceBuildsByTemplateID = `-- name: GetActiveWorkspaceBuildsByTemplateID :many -SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.has_external_agent, wb.notified_autostop_deadline, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name +SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_external_agent, wb.notified_autostop_deadline, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name FROM ( SELECT workspace_id, MAX(build_number) as max_build_number @@ -37727,7 +37704,6 @@ func (q *sqlQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, t &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -37829,7 +37805,7 @@ func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, a const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -37858,7 +37834,6 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -37926,7 +37901,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx context. const getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many SELECT DISTINCT ON (workspace_id) - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -37959,7 +37934,6 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -37981,7 +37955,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, const getWorkspaceBuildByID = `-- name: GetWorkspaceBuildByID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -38008,7 +37982,6 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -38020,7 +37993,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W const getWorkspaceBuildByJobID = `-- name: GetWorkspaceBuildByJobID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -38047,7 +38020,6 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -38059,7 +38031,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU const getWorkspaceBuildByWorkspaceIDAndBuildNumber = `-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -38090,7 +38062,6 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Co &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -38267,7 +38238,7 @@ func (q *sqlQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, sinc const getWorkspaceBuildsByWorkspaceID = `-- name: GetWorkspaceBuildsByWorkspaceID :many SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -38337,7 +38308,6 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -38358,7 +38328,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge } const getWorkspaceBuildsCreatedAfter = `-- name: GetWorkspaceBuildsCreatedAfter :many -SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1 +SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_external_agent, notified_autostop_deadline, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) { @@ -38385,7 +38355,6 @@ func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, created &i.DailyCost, &i.MaxDeadline, &i.TemplateVersionPresetID, - &i.HasAITask, &i.HasExternalAgent, &i.NotifiedAutostopDeadline, &i.InitiatorByAvatarUrl, @@ -38522,26 +38491,19 @@ const updateWorkspaceBuildFlagsByID = `-- name: UpdateWorkspaceBuildFlagsByID :e UPDATE workspace_builds SET - has_ai_task = $1, - has_external_agent = $2, - updated_at = $3::timestamptz -WHERE id = $4::uuid + has_external_agent = $1, + updated_at = $2::timestamptz +WHERE id = $3::uuid ` type UpdateWorkspaceBuildFlagsByIDParams struct { - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` ID uuid.UUID `db:"id" json:"id"` } func (q *sqlQuerier) UpdateWorkspaceBuildFlagsByID(ctx context.Context, arg UpdateWorkspaceBuildFlagsByIDParams) error { - _, err := q.db.ExecContext(ctx, updateWorkspaceBuildFlagsByID, - arg.HasAITask, - arg.HasExternalAgent, - arg.UpdatedAt, - arg.ID, - ) + _, err := q.db.ExecContext(ctx, updateWorkspaceBuildFlagsByID, arg.HasExternalAgent, arg.UpdatedAt, arg.ID) return err } @@ -39808,7 +39770,6 @@ LEFT JOIN LATERAL ( workspace_builds.id, workspace_builds.transition, workspace_builds.template_version_id, - workspace_builds.has_ai_task, workspace_builds.has_external_agent, template_versions.name AS template_version_name, provisioner_jobs.id AS provisioner_job_id, @@ -40029,43 +39990,28 @@ WHERE (latest_build.template_version_id = template.active_version_id) = $19 :: boolean ELSE true END - -- Filter by has_ai_task, checks if this is a task workspace. - AND CASE - WHEN $20::boolean IS NOT NULL - THEN $20::boolean = EXISTS ( - SELECT - 1 - FROM - tasks - WHERE - -- Consider all tasks, deleting a task does not turn the - -- workspace into a non-task workspace. - tasks.workspace_id = workspaces.id - ) - ELSE true - END -- Filter by has_external_agent in latest build AND CASE - WHEN $21 :: boolean IS NOT NULL THEN - latest_build.has_external_agent = $21 :: boolean + WHEN $20 :: boolean IS NOT NULL THEN + latest_build.has_external_agent = $20 :: boolean ELSE true END -- Filter by shared status AND CASE - WHEN $22 :: boolean IS NOT NULL THEN - (workspaces.user_acl != '{}'::jsonb OR workspaces.group_acl != '{}'::jsonb) = $22 :: boolean + WHEN $21 :: boolean IS NOT NULL THEN + (workspaces.user_acl != '{}'::jsonb OR workspaces.group_acl != '{}'::jsonb) = $21 :: boolean ELSE true END -- Filter by shared_with_user_id AND CASE - WHEN $23 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN - workspaces.user_acl ? ($23 :: uuid) :: text + WHEN $22 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN + workspaces.user_acl ? ($22 :: uuid) :: text ELSE true END -- Filter by shared_with_group_id AND CASE - WHEN $24 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN - workspaces.group_acl ? ($24 :: uuid) :: text + WHEN $23 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN + workspaces.group_acl ? ($23 :: uuid) :: text ELSE true END @@ -40078,7 +40024,7 @@ WHERE filtered_workspaces fw ORDER BY -- To ensure that 'favorite' workspaces show up first in the list only for their owner. - CASE WHEN favorite AND owner_username = (SELECT users.username FROM users WHERE users.id = $25) THEN 0 ELSE 1 END ASC, + CASE WHEN favorite AND owner_username = (SELECT users.username FROM users WHERE users.id = $24) THEN 0 ELSE 1 END ASC, (latest_build_completed_at IS NOT NULL AND latest_build_canceled_at IS NULL AND latest_build_error IS NULL AND @@ -40087,11 +40033,11 @@ WHERE LOWER(name) ASC LIMIT CASE - WHEN $27 :: integer > 0 THEN - $27 + WHEN $26 :: integer > 0 THEN + $26 END OFFSET - $26 + $25 ), filtered_workspaces_order_with_summary AS ( SELECT fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.group_acl_display_info, fwo.user_acl_display_info, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_external_agent, fwo.latest_build_provisioner_job_id @@ -40144,7 +40090,7 @@ WHERE false, -- latest_build_has_external_agent '00000000-0000-0000-0000-000000000000'::uuid -- latest_build_provisioner_job_id WHERE - $28 :: boolean = true + $27 :: boolean = true ), total_count AS ( SELECT count(*) AS count @@ -40233,7 +40179,6 @@ type GetWorkspacesParams struct { LastUsedBefore time.Time `db:"last_used_before" json:"last_used_before"` LastUsedAfter time.Time `db:"last_used_after" json:"last_used_after"` UsingActive sql.NullBool `db:"using_active" json:"using_active"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` Shared sql.NullBool `db:"shared" json:"shared"` SharedWithUserID uuid.UUID `db:"shared_with_user_id" json:"shared_with_user_id"` @@ -40314,7 +40259,6 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) arg.LastUsedBefore, arg.LastUsedAfter, arg.UsingActive, - arg.HasAITask, arg.HasExternalAgent, arg.Shared, arg.SharedWithUserID, diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index ba4836086448c..d05c78a61ad3a 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -71,12 +71,6 @@ WHERE END ELSE true END - -- Filter by has_ai_task in latest version - AND CASE - WHEN sqlc.narg('has_ai_task') :: boolean IS NOT NULL THEN - tv.has_ai_task = sqlc.narg('has_ai_task') :: boolean - ELSE true - END -- Filter by agents_allowed AND CASE WHEN sqlc.narg('agents_allowed') :: boolean IS NOT NULL THEN diff --git a/coderd/database/queries/templateversions.sql b/coderd/database/queries/templateversions.sql index e68383aa0632e..4813c0f83d92c 100644 --- a/coderd/database/queries/templateversions.sql +++ b/coderd/database/queries/templateversions.sql @@ -230,8 +230,7 @@ RETURNING template_versions.id; UPDATE template_versions SET - has_ai_task = $2, - has_external_agent = $3, - updated_at = $4 + has_external_agent = $2, + updated_at = $3 WHERE job_id = $1; diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql index 390ffefab9e01..7afe146d39791 100644 --- a/coderd/database/queries/workspacebuilds.sql +++ b/coderd/database/queries/workspacebuilds.sql @@ -252,7 +252,6 @@ ORDER BY UPDATE workspace_builds SET - has_ai_task = @has_ai_task, has_external_agent = @has_external_agent, updated_at = @updated_at::timestamptz WHERE id = @id::uuid; diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 15251b56ca0a8..81d57c167f085 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -130,7 +130,6 @@ LEFT JOIN LATERAL ( workspace_builds.id, workspace_builds.transition, workspace_builds.template_version_id, - workspace_builds.has_ai_task, workspace_builds.has_external_agent, template_versions.name AS template_version_name, provisioner_jobs.id AS provisioner_job_id, @@ -351,21 +350,6 @@ WHERE (latest_build.template_version_id = template.active_version_id) = sqlc.narg('using_active') :: boolean ELSE true END - -- Filter by has_ai_task, checks if this is a task workspace. - AND CASE - WHEN sqlc.narg('has_ai_task')::boolean IS NOT NULL - THEN sqlc.narg('has_ai_task')::boolean = EXISTS ( - SELECT - 1 - FROM - tasks - WHERE - -- Consider all tasks, deleting a task does not turn the - -- workspace into a non-task workspace. - tasks.workspace_id = workspaces.id - ) - ELSE true - END -- Filter by has_external_agent in latest build AND CASE WHEN sqlc.narg('has_external_agent') :: boolean IS NOT NULL THEN diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 2052edd2c0b63..771b121a6d138 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2038,9 +2038,6 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro } err = db.UpdateTemplateVersionFlagsByJobID(ctx, database.UpdateTemplateVersionFlagsByJobIDParams{ JobID: jobID, - // Tasks are removed; the has_ai_task column is dropped in a - // follow-up migration. - HasAITask: sql.NullBool{Bool: false, Valid: true}, HasExternalAgent: sql.NullBool{ Bool: jobType.TemplateImport.HasExternalAgents, Valid: true, @@ -2320,9 +2317,6 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro }) if err := db.UpdateWorkspaceBuildFlagsByID(ctx, database.UpdateWorkspaceBuildFlagsByIDParams{ ID: workspaceBuild.ID, - // Tasks are removed; the has_ai_task column is dropped in a - // follow-up migration. - HasAITask: sql.NullBool{Bool: false, Valid: true}, HasExternalAgent: sql.NullBool{ Bool: hasExternalAgent, Valid: true, diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index c2e4d884b2bfa..8bfca3fe8b4da 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -45,11 +45,11 @@ We track the following resources: | PrebuildsSettings
| |
FieldTracked
idfalse
reconciliation_pausedtrue
| | RoleSyncSettings
| |
FieldTracked
fieldtrue
mappingtrue
| | Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
agents_allowedtrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
allow_workspace_renamestrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
cors_behaviortrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
disable_module_cachetrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_autostop_notifytrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
user_acltrue
| -| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
has_external_agentfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| +| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_external_agentfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| | User
create, write, delete | |
FieldTracked
avatar_urlfalse
chat_spend_limit_microstrue
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_service_accounttrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| | UserSecret
create, write, delete | |
FieldTracked
created_atfalse
descriptiontrue
enabledtrue
env_nametrue
file_pathtrue
idtrue
nametrue
updated_atfalse
user_idtrue
valuetrue
value_key_idfalse
| | UserSkill
create, write, delete | |
FieldTracked
contenttrue
created_atfalse
descriptiontrue
idtrue
nametrue
updated_atfalse
user_idtrue
| -| WorkspaceBuild
start, stop | |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
has_external_agentfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
notified_autostop_deadlinefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceBuild
start, stop | |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_external_agentfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
notified_autostop_deadlinefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| | WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
group_acltrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
user_acltrue
| diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index e4339af2507ce..823db47223d5a 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -154,7 +154,6 @@ var auditableResourcesTypes = map[any]map[string]Action{ "created_by_name": ActionIgnore, "archived": ActionTrack, "source_example_id": ActionIgnore, // Never changes. - "has_ai_task": ActionIgnore, // Never changes. "has_external_agent": ActionIgnore, // Never changes. }, &database.User{}: { @@ -217,7 +216,6 @@ var auditableResourcesTypes = map[any]map[string]Action{ "initiator_by_username": ActionIgnore, "initiator_by_name": ActionIgnore, "template_version_preset_id": ActionIgnore, // Never changes. - "has_ai_task": ActionIgnore, // Never changes. "has_external_agent": ActionIgnore, // Never changes. "notified_autostop_deadline": ActionIgnore, // Updated by the notification system, not by user action. }, From 5fc53ee9538d0cddda5731825703ed1306ee0253 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Fri, 11 Sep 2026 00:08:42 +0000 Subject: [PATCH 2/2] fix(coderd/database/migrations): renumber to 000593 and drop schema assertions from the test --- ...s_ai_task_and_task_build_reasons.down.sql} | 0 ...has_ai_task_and_task_build_reasons.up.sql} | 0 coderd/database/migrations/migrate_test.go | 29 ++++--------------- 3 files changed, 6 insertions(+), 23 deletions(-) rename coderd/database/migrations/{000592_remove_has_ai_task_and_task_build_reasons.down.sql => 000593_remove_has_ai_task_and_task_build_reasons.down.sql} (100%) rename coderd/database/migrations/{000592_remove_has_ai_task_and_task_build_reasons.up.sql => 000593_remove_has_ai_task_and_task_build_reasons.up.sql} (100%) diff --git a/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.down.sql b/coderd/database/migrations/000593_remove_has_ai_task_and_task_build_reasons.down.sql similarity index 100% rename from coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.down.sql rename to coderd/database/migrations/000593_remove_has_ai_task_and_task_build_reasons.down.sql diff --git a/coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.up.sql b/coderd/database/migrations/000593_remove_has_ai_task_and_task_build_reasons.up.sql similarity index 100% rename from coderd/database/migrations/000592_remove_has_ai_task_and_task_build_reasons.up.sql rename to coderd/database/migrations/000593_remove_has_ai_task_and_task_build_reasons.up.sql diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index 27af55f0799a8..19457e9ce9f37 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -1190,10 +1190,10 @@ func TestMigration000475AgentsAccessOrgRole(t *testing.T) { ) } -func TestMigration000592RemoveHasAITaskAndTaskBuildReasons(t *testing.T) { +func TestMigration000593RemoveHasAITaskAndTaskBuildReasons(t *testing.T) { t.Parallel() - const migrationVersion = 592 + const migrationVersion = 593 sqlDB := testSQLDB(t) next, err := migrations.Stepper(sqlDB) require.NoError(t, err) @@ -1269,12 +1269,6 @@ func TestMigration000592RemoveHasAITaskAndTaskBuildReasons(t *testing.T) { require.NoError(t, err) require.EqualValues(t, migrationVersion, version) var count int - const columnsQuery = "SELECT count(*) FROM information_schema.columns WHERE table_schema = 'public' AND table_name IN ('template_versions', 'workspace_builds') AND column_name = 'has_ai_task'" - require.NoError(t, sqlDB.QueryRowContext(ctx, columnsQuery).Scan(&count)) - require.Zero(t, count) - const indexQuery = "SELECT count(*) FROM pg_indexes WHERE schemaname = 'public' AND indexname = 'idx_template_versions_has_ai_task'" - require.NoError(t, sqlDB.QueryRowContext(ctx, indexQuery).Scan(&count)) - require.Zero(t, count) var reasons pq.StringArray const reasonsQuery = "SELECT array_agg(reason::text ORDER BY build_number) FROM workspace_builds WHERE workspace_id = $1" wantReasons := pq.StringArray{"autostop", "initiator", "initiator", "initiator", "autostop"} @@ -1283,43 +1277,32 @@ func TestMigration000592RemoveHasAITaskAndTaskBuildReasons(t *testing.T) { var childReason string require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT child_reason FROM workspace_build_orchestrations WHERE workspace_id = $1", workspaceID).Scan(&childReason)) require.Equal(t, "autostop", childReason) - var enumValues pq.StringArray - const enumQuery = "SELECT enum_range(NULL::build_reason)::text[]" - require.NoError(t, sqlDB.QueryRowContext(ctx, enumQuery).Scan(&enumValues)) - wantEnum := pq.StringArray{"initiator", "autostart", "autostop", "dormancy", "failedstop", "autodelete", "dashboard", "cli", "ssh_connection", "vscode_connection", "jetbrains_connection"} - require.Equal(t, wantEnum, enumValues) require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM template_version_with_user").Scan(&count)) require.Equal(t, 1, count) require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM workspace_build_with_user").Scan(&count)) require.Equal(t, len(buildIDs), count) - downSQL, err := os.ReadFile("000592_remove_has_ai_task_and_task_build_reasons.down.sql") + downSQL, err := os.ReadFile("000593_remove_has_ai_task_and_task_build_reasons.down.sql") require.NoError(t, err) _, err = sqlDB.ExecContext(ctx, string(downSQL)) require.NoError(t, err) - require.NoError(t, sqlDB.QueryRowContext(ctx, columnsQuery).Scan(&count)) - require.Equal(t, 2, count) - require.NoError(t, sqlDB.QueryRowContext(ctx, indexQuery).Scan(&count)) - require.Equal(t, 1, count) require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM template_versions WHERE has_ai_task IS FALSE").Scan(&count)) require.Equal(t, 1, count) require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT count(*) FROM workspace_builds WHERE has_ai_task IS FALSE").Scan(&count)) require.Equal(t, len(buildIDs), count) - require.NoError(t, sqlDB.QueryRowContext(ctx, enumQuery).Scan(&enumValues)) - require.Equal(t, append(wantEnum, "task_auto_pause", "task_manual_pause", "task_resume"), enumValues) // The original reasons cannot be recovered after remapping. require.NoError(t, sqlDB.QueryRowContext(ctx, reasonsQuery, workspaceID).Scan(&reasons)) require.Equal(t, wantReasons, reasons) require.NoError(t, sqlDB.QueryRowContext(ctx, "SELECT child_reason FROM workspace_build_orchestrations WHERE workspace_id = $1", workspaceID).Scan(&childReason)) require.Equal(t, "autostop", childReason) - upSQL, err := os.ReadFile("000592_remove_has_ai_task_and_task_build_reasons.up.sql") + upSQL, err := os.ReadFile("000593_remove_has_ai_task_and_task_build_reasons.up.sql") require.NoError(t, err) _, err = sqlDB.ExecContext(ctx, string(upSQL)) require.NoError(t, err) } -func TestMigration000592TaskBuildReasonRewriteTiming(t *testing.T) { +func TestMigration000593TaskBuildReasonRewriteTiming(t *testing.T) { t.Parallel() if testing.Short() { @@ -1330,7 +1313,7 @@ func TestMigration000592TaskBuildReasonRewriteTiming(t *testing.T) { // The enum replacement rewrites workspace_builds, risking slow upgrades // on large deployments. const ( - migrationVersion = 592 + migrationVersion = 593 workspaceCount = 1_000 buildCount = 100_000 buildsPerWorkspace = buildCount / workspaceCount