From acd12010f32e2d78cc51628747fd3588a22d07e9 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Mon, 16 Jun 2025 14:07:42 +0000 Subject: [PATCH 1/5] HasTemplateVersionsWithAITask --- coderd/database/queries/templateversions.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/coderd/database/queries/templateversions.sql b/coderd/database/queries/templateversions.sql index 0436a7f9ba3b9..a07d8f3349aec 100644 --- a/coderd/database/queries/templateversions.sql +++ b/coderd/database/queries/templateversions.sql @@ -225,3 +225,7 @@ FROM WHERE template_versions.id IN (archived_versions.id) RETURNING template_versions.id; + +-- name: HasTemplateVersionsWithAITask :one +-- Determines if the template versions table has any rows with has_ai_task = TRUE. +SELECT EXISTS (SELECT 1 FROM template_versions WHERE has_ai_task = TRUE); From c012da0f207cf69e1243bd2e476a9df037364924 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Mon, 16 Jun 2025 14:14:43 +0000 Subject: [PATCH 2/5] make gen --- coderd/database/dbauthz/dbauthz.go | 4 ++++ coderd/database/dbmem/dbmem.go | 4 ++++ coderd/database/dbmetrics/querymetrics.go | 7 +++++++ coderd/database/dbmock/dbmock.go | 15 +++++++++++++++ coderd/database/querier.go | 2 ++ coderd/database/queries.sql.go | 12 ++++++++++++ 6 files changed, 44 insertions(+) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 52a54df80532a..8f3a5e380c7c8 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3451,6 +3451,10 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti return q.db.GetWorkspacesEligibleForTransition(ctx, now) } +func (q *querier) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) { + panic("not implemented") +} + func (q *querier) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) { return insert(q.log, q.auth, rbac.ResourceApiKey.WithOwner(arg.UserID.String()), diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index eba4b945f06e1..ae387e169bc55 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -8491,6 +8491,10 @@ func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, no return workspaces, nil } +func (q *FakeQuerier) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) { + panic("not implemented") +} + func (q *FakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) { if err := validateDatabaseType(arg); err != nil { return database.APIKey{}, err diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index e208f9898cb1e..3b0503bebe96e 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -2041,6 +2041,13 @@ func (m queryMetricsStore) GetWorkspacesEligibleForTransition(ctx context.Contex return workspaces, err } +func (m queryMetricsStore) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) { + start := time.Now() + r0, r1 := m.s.HasTemplateVersionsWithAITask(ctx) + m.queryLatencies.WithLabelValues("HasTemplateVersionsWithAITask").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) { start := time.Now() key, err := m.s.InsertAPIKey(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index b6a04754f17b0..0608c00cba180 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -4292,6 +4292,21 @@ func (mr *MockStoreMockRecorder) GetWorkspacesEligibleForTransition(ctx, now any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspacesEligibleForTransition", reflect.TypeOf((*MockStore)(nil).GetWorkspacesEligibleForTransition), ctx, now) } +// HasTemplateVersionsWithAITask mocks base method. +func (m *MockStore) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasTemplateVersionsWithAITask", ctx) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasTemplateVersionsWithAITask indicates an expected call of HasTemplateVersionsWithAITask. +func (mr *MockStoreMockRecorder) HasTemplateVersionsWithAITask(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasTemplateVersionsWithAITask", reflect.TypeOf((*MockStore)(nil).HasTemplateVersionsWithAITask), ctx) +} + // InTx mocks base method. func (m *MockStore) InTx(arg0 func(database.Store) error, arg1 *database.TxOptions) error { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index b612143b63776..1c9d5a8be661a 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -462,6 +462,8 @@ type sqlcQuerier interface { GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]GetWorkspacesAndAgentsByOwnerIDRow, error) GetWorkspacesByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceTable, error) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error) + // Determines if the template versions table has any rows with has_ai_task = TRUE. + HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error) // We use the organization_id as the id // for simplicity since all users is diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 92c912a55705a..ed97fa0e33645 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -11796,6 +11796,18 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create return items, nil } +const hasTemplateVersionsWithAITask = `-- name: HasTemplateVersionsWithAITask :one +SELECT EXISTS (SELECT 1 FROM template_versions WHERE has_ai_task = TRUE) +` + +// Determines if the template versions table has any rows with has_ai_task = TRUE. +func (q *sqlQuerier) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) { + row := q.db.QueryRowContext(ctx, hasTemplateVersionsWithAITask) + var exists bool + err := row.Scan(&exists) + return exists, err +} + const insertTemplateVersion = `-- name: InsertTemplateVersion :exec INSERT INTO template_versions ( From 7c5eba8ae2997b9b2e47d2e18b4690ff4d56fe5e Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Mon, 16 Jun 2025 14:20:51 +0000 Subject: [PATCH 3/5] make gen impl --- coderd/database/dbauthz/dbauthz.go | 3 ++- coderd/database/dbauthz/dbauthz_test.go | 3 +++ coderd/database/dbmem/dbmem.go | 13 +++++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 8f3a5e380c7c8..6cbccc5b52d0d 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3452,7 +3452,8 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti } func (q *querier) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) { - panic("not implemented") + // Anyone can call HasTemplateVersionsWithAITask. + return q.db.HasTemplateVersionsWithAITask(ctx) } func (q *querier) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 50373fbeb72e6..16c66bf72ba4e 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4566,6 +4566,9 @@ func (s *MethodTestSuite) TestSystemFunctions() { s.Run("GetProvisionerJobByIDForUpdate", s.Subtest(func(db database.Store, check *expects) { check.Args(uuid.New()).Asserts(rbac.ResourceProvisionerJobs, policy.ActionRead).Errors(sql.ErrNoRows) })) + s.Run("HasTemplateVersionsWithAITask", s.Subtest(func(db database.Store, check *expects) { + check.Args().Asserts() + })) } func (s *MethodTestSuite) TestNotifications() { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index ae387e169bc55..bc3026234572d 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -8491,8 +8491,17 @@ func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, no return workspaces, nil } -func (q *FakeQuerier) HasTemplateVersionsWithAITask(ctx context.Context) (bool, error) { - panic("not implemented") +func (q *FakeQuerier) HasTemplateVersionsWithAITask(_ context.Context) (bool, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, templateVersion := range q.templateVersions { + if templateVersion.HasAITask { + return true, nil + } + } + + return false, nil } func (q *FakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) { From 2b59d482a16ad6b37e78b7f4b11b6dfb8d8172f0 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Mon, 16 Jun 2025 14:31:19 +0000 Subject: [PATCH 4/5] backend impl --- coderd/coderd.go | 1 + codersdk/deployment.go | 11 +++++++++ site/index.html | 1 + site/site.go | 26 ++++++++++++++++++++++ site/src/hooks/useEmbeddedMetadata.test.ts | 10 +++++++++ site/src/hooks/useEmbeddedMetadata.ts | 2 ++ site/src/testHelpers/entities.ts | 2 ++ 7 files changed, 53 insertions(+) diff --git a/coderd/coderd.go b/coderd/coderd.go index 24b34ea4db91a..2449eee0709c2 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -626,6 +626,7 @@ func New(options *Options) *API { Entitlements: options.Entitlements, Telemetry: options.Telemetry, Logger: options.Logger.Named("site"), + HideAITasks: options.DeploymentValues.HideAITasks.Value(), }) api.SiteHandler.Experiments.Store(&experiments) diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 23715e50a8aba..90e8a4c879ec5 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -399,6 +399,7 @@ type DeploymentValues struct { AdditionalCSPPolicy serpent.StringArray `json:"additional_csp_policy,omitempty" typescript:",notnull"` WorkspaceHostnameSuffix serpent.String `json:"workspace_hostname_suffix,omitempty" typescript:",notnull"` Prebuilds PrebuildsConfig `json:"workspace_prebuilds,omitempty" typescript:",notnull"` + HideAITasks serpent.Bool `json:"hide_ai_tasks,omitempty" typescript:",notnull"` Config serpent.YAMLConfigPath `json:"config,omitempty" typescript:",notnull"` WriteConfig serpent.Bool `json:"write_config,omitempty" typescript:",notnull"` @@ -3116,6 +3117,16 @@ Write out the current server config as YAML to stdout.`, YAML: "failure_hard_limit", Hidden: true, }, + { + Name: "Hide AI Tasks", + Description: "Hide AI tasks from the dashboard.", + Flag: "hide-ai-tasks", + Env: "CODER_HIDE_AI_TASKS", + Default: "false", + Value: &c.HideAITasks, + Group: &deploymentGroupClient, + YAML: "hideAITasks", + }, } return opts diff --git a/site/index.html b/site/index.html index b953abe052923..e3a5389efbdd0 100644 --- a/site/index.html +++ b/site/index.html @@ -25,6 +25,7 @@ + ; const emptyMetadata: RuntimeHtmlMetadata = { @@ -72,6 +74,10 @@ const emptyMetadata: RuntimeHtmlMetadata = { available: false, value: undefined, }, + tasksTabVisible: { + available: false, + value: undefined, + }, }; const populatedMetadata: RuntimeHtmlMetadata = { @@ -103,6 +109,10 @@ const populatedMetadata: RuntimeHtmlMetadata = { available: true, value: MockUserAppearanceSettings, }, + tasksTabVisible: { + available: true, + value: MockTasksTabVisible, + }, }; function seedInitialMetadata(metadataKey: string): () => void { diff --git a/site/src/hooks/useEmbeddedMetadata.ts b/site/src/hooks/useEmbeddedMetadata.ts index 35cd8614f408e..1dd2d7c2bbeeb 100644 --- a/site/src/hooks/useEmbeddedMetadata.ts +++ b/site/src/hooks/useEmbeddedMetadata.ts @@ -30,6 +30,7 @@ type AvailableMetadata = Readonly<{ entitlements: Entitlements; regions: readonly Region[]; "build-info": BuildInfoResponse; + tasksTabVisible: boolean; }>; export type MetadataKey = keyof AvailableMetadata; @@ -91,6 +92,7 @@ export class MetadataManager implements MetadataManagerApi { experiments: this.registerValue("experiments"), "build-info": this.registerValue("build-info"), regions: this.registerRegionValue(), + tasksTabVisible: this.registerValue("tasksTabVisible"), }; } diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 0201e4b563efc..b14c16172e875 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -534,6 +534,8 @@ export const MockUserAppearanceSettings: TypesGen.UserAppearanceSettings = { terminal_font: "", }; +export const MockTasksTabVisible: boolean = false; + export const MockOrganizationMember: TypesGen.OrganizationMemberWithUserData = { organization_id: MockOrganization.id, user_id: MockUserOwner.id, From 60b0e8120cbb91a1c3d2a8a6e558df5584f70078 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Tue, 17 Jun 2025 10:16:20 +0000 Subject: [PATCH 5/5] make gen after backend impl --- cli/testdata/coder_server_--help.golden | 3 +++ cli/testdata/server-config.yaml.golden | 3 +++ coderd/apidoc/docs.go | 3 +++ coderd/apidoc/swagger.json | 3 +++ docs/reference/api/general.md | 1 + docs/reference/api/schemas.md | 3 +++ docs/reference/cli/server.md | 11 +++++++++++ enterprise/cli/testdata/coder_server_--help.golden | 3 +++ site/src/api/typesGenerated.ts | 1 + 9 files changed, 31 insertions(+) diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 26e63ceb8418f..19857cf8ebe76 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -85,6 +85,9 @@ Clients include the Coder CLI, Coder Desktop, IDE extensions, and the web UI. is detected. By default it instructs users to update using 'curl -L https://coder.com/install.sh | sh'. + --hide-ai-tasks bool, $CODER_HIDE_AI_TASKS (default: false) + Hide AI tasks from the dashboard. + --ssh-config-options string-array, $CODER_SSH_CONFIG_OPTIONS These SSH config options will override the default SSH config options. Provide options in "key=value" or "key value" format separated by diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index cc064e8fa2d6e..8befccf3e320d 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -520,6 +520,9 @@ client: # 'webgl', or 'dom'. # (default: canvas, type: string) webTerminalRenderer: canvas + # Hide AI tasks from the dashboard. + # (default: false, type: bool) + hideAITasks: false # Support links to display in the top right drop down menu. # (default: , type: struct[[]codersdk.LinkConfig]) supportLinks: [] diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 5dc293e2e706e..d564fc8201259 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -12483,6 +12483,9 @@ const docTemplate = `{ "healthcheck": { "$ref": "#/definitions/codersdk.HealthcheckConfig" }, + "hide_ai_tasks": { + "type": "boolean" + }, "http_address": { "description": "HTTPAddress is a string because it may be set to zero to disable.", "type": "string" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index ff48e99d393fc..345b3e25fd9a5 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -11183,6 +11183,9 @@ "healthcheck": { "$ref": "#/definitions/codersdk.HealthcheckConfig" }, + "hide_ai_tasks": { + "type": "boolean" + }, "http_address": { "description": "HTTPAddress is a string because it may be set to zero to disable.", "type": "string" diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md index e0fb97a1513e0..92ee1c60b554b 100644 --- a/docs/reference/api/general.md +++ b/docs/reference/api/general.md @@ -272,6 +272,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "refresh": 0, "threshold_database": 0 }, + "hide_ai_tasks": true, "http_address": "string", "http_cookies": { "same_site": "string", diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index a5b759e5dfb0c..0aa35b173e6ab 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -2443,6 +2443,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "refresh": 0, "threshold_database": 0 }, + "hide_ai_tasks": true, "http_address": "string", "http_cookies": { "same_site": "string", @@ -2943,6 +2944,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "refresh": 0, "threshold_database": 0 }, + "hide_ai_tasks": true, "http_address": "string", "http_cookies": { "same_site": "string", @@ -3243,6 +3245,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `external_auth` | [serpent.Struct-array_codersdk_ExternalAuthConfig](#serpentstruct-array_codersdk_externalauthconfig) | false | | | | `external_token_encryption_keys` | array of string | false | | | | `healthcheck` | [codersdk.HealthcheckConfig](#codersdkhealthcheckconfig) | false | | | +| `hide_ai_tasks` | boolean | false | | | | `http_address` | string | false | | Http address is a string because it may be set to zero to disable. | | `http_cookies` | [codersdk.HTTPCookieConfig](#codersdkhttpcookieconfig) | false | | | | `in_memory_database` | boolean | false | | | diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index 8b47ac00dbc7b..644065d35076f 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1614,3 +1614,14 @@ Enable Coder Inbox. | Default | 5 | The upper limit of attempts to send a notification. + +### --hide-ai-tasks + +| | | +|-------------|-----------------------------------| +| Type | bool | +| Environment | $CODER_HIDE_AI_TASKS | +| YAML | client.hideAITasks | +| Default | false | + +Hide AI tasks from the dashboard. diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index edacc0c43fc0b..3e3868c5ae432 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -86,6 +86,9 @@ Clients include the Coder CLI, Coder Desktop, IDE extensions, and the web UI. is detected. By default it instructs users to update using 'curl -L https://coder.com/install.sh | sh'. + --hide-ai-tasks bool, $CODER_HIDE_AI_TASKS (default: false) + Hide AI tasks from the dashboard. + --ssh-config-options string-array, $CODER_SSH_CONFIG_OPTIONS These SSH config options will override the default SSH config options. Provide options in "key=value" or "key value" format separated by diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index a512305c489d3..f20bf37adbaeb 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -736,6 +736,7 @@ export interface DeploymentValues { readonly additional_csp_policy?: string; readonly workspace_hostname_suffix?: string; readonly workspace_prebuilds?: PrebuildsConfig; + readonly hide_ai_tasks?: boolean; readonly config?: string; readonly write_config?: boolean; readonly address?: string;