From 92b35ffe0c25288b1f373d6df847356c91535dcf Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 10:08:23 +0000 Subject: [PATCH 01/11] feat: add `parent_id` column to `workspace_agents` table Adds a new nullable column `parent_id` to `workspace_agents` table. This lays the groundwork for having child agents. --- coderd/apidoc/docs.go | 20 +++++++ coderd/apidoc/swagger.json | 20 +++++++ coderd/database/dump.sql | 4 ++ coderd/database/foreign_key_constraint.go | 1 + ...add_parent_id_to_workspace_agents.down.sql | 2 + ...0_add_parent_id_to_workspace_agents.up.sql | 2 + coderd/database/models.go | 3 +- coderd/database/queries.sql.go | 21 ++++--- codersdk/workspaceagents.go | 1 + docs/admin/security/audit-logs.md | 2 +- docs/reference/api/agents.md | 4 ++ docs/reference/api/builds.md | 30 ++++++++++ docs/reference/api/schemas.md | 37 +++++++++++++ docs/reference/api/templates.md | 14 +++++ docs/reference/api/workspaces.md | 24 ++++++++ enterprise/audit/table.go | 1 + site/src/api/typesGenerated.ts | 1 + .../pages/WorkspacePage/Workspace.stories.tsx | 27 +++++++++ site/src/pages/WorkspacePage/Workspace.tsx | 55 ++++++++++--------- site/src/testHelpers/entities.ts | 41 ++++++++++++++ 20 files changed, 276 insertions(+), 34 deletions(-) create mode 100644 coderd/database/migrations/000320_add_parent_id_to_workspace_agents.down.sql create mode 100644 coderd/database/migrations/000320_add_parent_id_to_workspace_agents.up.sql diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index fb5ae20e448c8..dc5724f77b020 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -17021,6 +17021,14 @@ const docTemplate = `{ "operating_system": { "type": "string" }, + "parent_id": { + "format": "uuid", + "allOf": [ + { + "$ref": "#/definitions/uuid.NullUUID" + } + ] + }, "ready_at": { "type": "string", "format": "date-time" @@ -19033,6 +19041,18 @@ const docTemplate = `{ "url.Userinfo": { "type": "object" }, + "uuid.NullUUID": { + "type": "object", + "properties": { + "uuid": { + "type": "string" + }, + "valid": { + "description": "Valid is true if UUID is not NULL", + "type": "boolean" + } + } + }, "workspaceapps.AccessMethod": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8420c9ea0f812..1628797d85ffc 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -15538,6 +15538,14 @@ "operating_system": { "type": "string" }, + "parent_id": { + "format": "uuid", + "allOf": [ + { + "$ref": "#/definitions/uuid.NullUUID" + } + ] + }, "ready_at": { "type": "string", "format": "date-time" @@ -17442,6 +17450,18 @@ "url.Userinfo": { "type": "object" }, + "uuid.NullUUID": { + "type": "object", + "properties": { + "uuid": { + "type": "string" + }, + "valid": { + "description": "Valid is true if UUID is not NULL", + "type": "boolean" + } + } + }, "workspaceapps.AccessMethod": { "type": "string", "enum": ["path", "subdomain", "terminal"], diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 9ce3b0171d2d4..b8cbc6ee18456 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1832,6 +1832,7 @@ CREATE TABLE workspace_agents ( display_apps display_app[] DEFAULT '{vscode,vscode_insiders,web_terminal,ssh_helper,port_forwarding_helper}'::display_app[], api_version text DEFAULT ''::text NOT NULL, display_order integer DEFAULT 0 NOT NULL, + parent_id uuid, CONSTRAINT max_logs_length CHECK ((logs_length <= 1048576)), CONSTRAINT subsystems_not_none CHECK ((NOT ('none'::workspace_agent_subsystem = ANY (subsystems)))) ); @@ -2922,6 +2923,9 @@ ALTER TABLE ONLY workspace_agent_logs ALTER TABLE ONLY workspace_agent_volume_resource_monitors ADD CONSTRAINT workspace_agent_volume_resource_monitors_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; +ALTER TABLE ONLY workspace_agents + ADD CONSTRAINT workspace_agents_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index 0db3e9522547e..3ccf093d85ce6 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -70,6 +70,7 @@ const ( ForeignKeyWorkspaceAgentScriptsWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_scripts_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_scripts ADD CONSTRAINT workspace_agent_scripts_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyWorkspaceAgentStartupLogsAgentID ForeignKeyConstraint = "workspace_agent_startup_logs_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyWorkspaceAgentVolumeResourceMonitorsAgentID ForeignKeyConstraint = "workspace_agent_volume_resource_monitors_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_volume_resource_monitors ADD CONSTRAINT workspace_agent_volume_resource_monitors_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + ForeignKeyWorkspaceAgentsParentID ForeignKeyConstraint = "workspace_agents_parent_id_fkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyWorkspaceAgentsResourceID ForeignKeyConstraint = "workspace_agents_resource_id_fkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE; ForeignKeyWorkspaceAppAuditSessionsAgentID ForeignKeyConstraint = "workspace_app_audit_sessions_agent_id_fkey" // ALTER TABLE ONLY workspace_app_audit_sessions ADD CONSTRAINT workspace_app_audit_sessions_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyWorkspaceAppStatsAgentID ForeignKeyConstraint = "workspace_app_stats_agent_id_fkey" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id); diff --git a/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.down.sql b/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.down.sql new file mode 100644 index 0000000000000..ab810126ad60e --- /dev/null +++ b/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE workspace_agents +DROP COLUMN IF EXISTS parent_id; diff --git a/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.up.sql b/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.up.sql new file mode 100644 index 0000000000000..f2fd7a8c1cd10 --- /dev/null +++ b/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE workspace_agents +ADD COLUMN parent_id UUID REFERENCES workspace_agents (id) ON DELETE CASCADE; diff --git a/coderd/database/models.go b/coderd/database/models.go index c8ac71e8b9398..b3c3e2c7e6eaa 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -3401,7 +3401,8 @@ type WorkspaceAgent struct { DisplayApps []DisplayApp `db:"display_apps" json:"display_apps"` APIVersion string `db:"api_version" json:"api_version"` // Specifies the order in which to display agents in user interfaces. - DisplayOrder int32 `db:"display_order" json:"display_order"` + DisplayOrder int32 `db:"display_order" json:"display_order"` + ParentID uuid.NullUUID `db:"parent_id" json:"parent_id"` } // Workspace agent devcontainer configuration diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index cd5b297c85e07..9d46821cfe7d8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -13913,7 +13913,7 @@ func (q *sqlQuerier) DeleteOldWorkspaceAgentLogs(ctx context.Context, threshold const getWorkspaceAgentAndLatestBuildByAuthToken = `-- name: GetWorkspaceAgentAndLatestBuildByAuthToken :one 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, - 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.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_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.provisioner_state, 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.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username FROM workspace_agents @@ -14003,6 +14003,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont pq.Array(&i.WorkspaceAgent.DisplayApps), &i.WorkspaceAgent.APIVersion, &i.WorkspaceAgent.DisplayOrder, + &i.WorkspaceAgent.ParentID, &i.WorkspaceBuild.ID, &i.WorkspaceBuild.CreatedAt, &i.WorkspaceBuild.UpdatedAt, @@ -14026,7 +14027,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont const getWorkspaceAgentByID = `-- name: GetWorkspaceAgentByID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id FROM workspace_agents WHERE @@ -14068,13 +14069,14 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W pq.Array(&i.DisplayApps), &i.APIVersion, &i.DisplayOrder, + &i.ParentID, ) return i, err } const getWorkspaceAgentByInstanceID = `-- name: GetWorkspaceAgentByInstanceID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id FROM workspace_agents WHERE @@ -14118,6 +14120,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst pq.Array(&i.DisplayApps), &i.APIVersion, &i.DisplayOrder, + &i.ParentID, ) return i, err } @@ -14337,7 +14340,7 @@ func (q *sqlQuerier) GetWorkspaceAgentScriptTimingsByBuildID(ctx context.Context const getWorkspaceAgentsByResourceIDs = `-- name: GetWorkspaceAgentsByResourceIDs :many SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id FROM workspace_agents WHERE @@ -14385,6 +14388,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] pq.Array(&i.DisplayApps), &i.APIVersion, &i.DisplayOrder, + &i.ParentID, ); err != nil { return nil, err } @@ -14400,7 +14404,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] } const getWorkspaceAgentsCreatedAfter = `-- name: GetWorkspaceAgentsCreatedAfter :many -SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order FROM workspace_agents WHERE created_at > $1 +SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id FROM workspace_agents WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) { @@ -14444,6 +14448,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created pq.Array(&i.DisplayApps), &i.APIVersion, &i.DisplayOrder, + &i.ParentID, ); err != nil { return nil, err } @@ -14460,7 +14465,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created const getWorkspaceAgentsInLatestBuildByWorkspaceID = `-- name: GetWorkspaceAgentsInLatestBuildByWorkspaceID :many SELECT - 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.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 FROM workspace_agents JOIN @@ -14520,6 +14525,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Co pq.Array(&i.DisplayApps), &i.APIVersion, &i.DisplayOrder, + &i.ParentID, ); err != nil { return nil, err } @@ -14557,7 +14563,7 @@ INSERT INTO display_order ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id ` type InsertWorkspaceAgentParams struct { @@ -14635,6 +14641,7 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa pq.Array(&i.DisplayApps), &i.APIVersion, &i.DisplayOrder, + &i.ParentID, ) return i, err } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 5c7171f70a627..f58338a209901 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -139,6 +139,7 @@ const ( type WorkspaceAgent struct { ID uuid.UUID `json:"id" format:"uuid"` + ParentID uuid.NullUUID `json:"parent_id" format:"uuid"` CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` FirstConnectedAt *time.Time `json:"first_connected_at,omitempty" format:"date-time"` diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index c9124efa14bf0..2ab7d454ca71b 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -29,7 +29,7 @@ We track the following resources: | Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
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_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| | TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| | User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| WorkspaceAgent
connect, disconnect | |
FieldTracked
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
directoryfalse
disconnected_atfalse
display_appsfalse
display_orderfalse
environment_variablesfalse
expanded_directoryfalse
first_connected_atfalse
idfalse
instance_metadatafalse
last_connected_atfalse
last_connected_replica_idfalse
lifecycle_statefalse
logs_lengthfalse
logs_overflowedfalse
motd_filefalse
namefalse
operating_systemfalse
ready_atfalse
resource_idfalse
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| +| WorkspaceAgent
connect, disconnect | |
FieldTracked
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
directoryfalse
disconnected_atfalse
display_appsfalse
display_orderfalse
environment_variablesfalse
expanded_directoryfalse
first_connected_atfalse
idfalse
instance_metadatafalse
last_connected_atfalse
last_connected_replica_idfalse
lifecycle_statefalse
logs_lengthfalse
logs_overflowedfalse
motd_filefalse
namefalse
operating_systemfalse
parent_idfalse
ready_atfalse
resource_idfalse
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| | WorkspaceApp
open, close | |
FieldTracked
agent_idfalse
commandfalse
created_atfalse
display_namefalse
display_orderfalse
externalfalse
healthfalse
healthcheck_intervalfalse
healthcheck_thresholdfalse
healthcheck_urlfalse
hiddenfalse
iconfalse
idfalse
open_infalse
sharing_levelfalse
slugfalse
subdomainfalse
urlfalse
| | WorkspaceBuild
start, stop | |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
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
| diff --git a/docs/reference/api/agents.md b/docs/reference/api/agents.md index 853cb67e38bfd..c0ddd79cd2052 100644 --- a/docs/reference/api/agents.md +++ b/docs/reference/api/agents.md @@ -577,6 +577,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index 1f795c3d7d313..8e88df96c1d29 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -164,6 +164,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -393,6 +397,10 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -737,6 +745,10 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -859,6 +871,9 @@ Status Code **200** | `»» logs_overflowed` | boolean | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | +| `»» parent_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | | +| `»»» uuid` | string | false | | | +| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL | | `»» ready_at` | string(date-time) | false | | | | `»» resource_id` | string(uuid) | false | | | | `»» scripts` | array | false | | | @@ -1092,6 +1107,10 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -1394,6 +1413,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -1573,6 +1596,9 @@ Status Code **200** | `»»» logs_overflowed` | boolean | false | | | | `»»» name` | string | false | | | | `»»» operating_system` | string | false | | | +| `»»» parent_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | | +| `»»»» uuid` | string | false | | | +| `»»»» valid` | boolean | false | | Valid is true if UUID is not NULL | | `»»» ready_at` | string(date-time) | false | | | | `»»» resource_id` | string(uuid) | false | | | | `»»» scripts` | array | false | | | @@ -1867,6 +1893,10 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 6ca005b4ec69c..8c1c86167b9d4 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -8304,6 +8304,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -8508,6 +8512,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -8564,6 +8572,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `logs_overflowed` | boolean | false | | | | `name` | string | false | | | | `operating_system` | string | false | | | +| `parent_id` | [uuid.NullUUID](#uuidnulluuid) | false | | | | `ready_at` | string | false | | | | `resource_id` | string | false | | | | `scripts` | array of [codersdk.WorkspaceAgentScript](#codersdkworkspaceagentscript) | false | | | @@ -9256,6 +9265,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -9672,6 +9685,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -9954,6 +9971,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -11941,6 +11962,22 @@ RegionIDs in range 900-999 are reserved for end users to run their own DERP node None +## uuid.NullUUID + +```json +{ + "uuid": "string", + "valid": true +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|---------|---------|----------|--------------|-----------------------------------| +| `uuid` | string | false | | | +| `valid` | boolean | false | | Valid is true if UUID is not NULL | + ## workspaceapps.AccessMethod ```json diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index ef136764bf2c5..b1beeb64a7116 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -2348,6 +2348,10 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -2470,6 +2474,9 @@ Status Code **200** | `»» logs_overflowed` | boolean | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | +| `»» parent_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | | +| `»»» uuid` | string | false | | | +| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL | | `»» ready_at` | string(date-time) | false | | | | `»» resource_id` | string(uuid) | false | | | | `»» scripts` | array | false | | | @@ -2869,6 +2876,10 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -2991,6 +3002,9 @@ Status Code **200** | `»» logs_overflowed` | boolean | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | +| `»» parent_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | | +| `»»» uuid` | string | false | | | +| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL | | `»» ready_at` | string(date-time) | false | | | | `»» resource_id` | string(uuid) | false | | | | `»» scripts` | array | false | | | diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index 5d09c46a01d30..8e25cd0bd58e6 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -219,6 +219,10 @@ of the template will be used. "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -496,6 +500,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -799,6 +807,10 @@ of the template will be used. "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -1062,6 +1074,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -1340,6 +1356,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ @@ -1733,6 +1753,10 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ "logs_overflowed": true, "name": "string", "operating_system": "string", + "parent_id": { + "uuid": "string", + "valid": true + }, "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "scripts": [ diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 84cc7d451b4f1..d5bf22df9ff5e 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -342,6 +342,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "display_apps": ActionIgnore, "api_version": ActionIgnore, "display_order": ActionIgnore, + "parent_id": ActionIgnore, }, &database.WorkspaceApp{}: { "id": ActionIgnore, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index e471e12b240b6..63dabab5bd793 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -3254,6 +3254,7 @@ export interface Workspace { // From codersdk/workspaceagents.go export interface WorkspaceAgent { readonly id: string; + readonly parent_id: string | null; readonly created_at: string; readonly updated_at: string; readonly first_connected_at?: string; diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index 957a651788d2c..ca782d73b68a0 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -103,6 +103,33 @@ export const Running: Story = { }, }; +export const RunningWithChildAgent: Story = { + args: { + ...Running.args, + workspace: { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspace.latest_build, + resources: [ + { + ...Mocks.MockWorkspaceResource, + agents: [ + { + ...Mocks.MockWorkspaceAgent, + lifecycle_state: "ready", + }, + { + ...Mocks.MockWorkspaceChildAgent, + lifecycle_state: "ready", + }, + ], + }, + ], + }, + }, + }, +}; + export const RunningWithAppStatuses: Story = { args: { workspace: { diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 69ce29ed0e7d1..85c749596176a 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -269,22 +269,29 @@ export const Workspace: FC = ({ minWidth: 0 /* Prevent overflow */, }} > - {selectedResource.agents?.map((agent) => ( - - ))} + {selectedResource.agents?.map((agent) => { + // We do not want to display child agents at the top-level. + if (agent.parent_id !== null) { + return; + } + + return ( + + ); + })} {(!selectedResource.agents || selectedResource.agents?.length === 0) && ( @@ -348,15 +355,13 @@ export const Workspace: FC = ({ color: theme.palette.text.secondary, }} > - { - // Calculate total status count - selectedResource.agents - ?.flatMap((agent) => agent.apps ?? []) - .reduce( - (count, app) => count + (app.statuses?.length ?? 0), - 0, - ) - }{" "} + {// Calculate total status count + selectedResource.agents + ?.flatMap((agent) => agent.apps ?? []) + .reduce( + (count, app) => count + (app.statuses?.length ?? 0), + 0, + )}{" "} Total diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index a8db5f55879c4..dc0883bdbbdd5 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -966,6 +966,47 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { ], }; +export const MockWorkspaceChildAgent: TypesGen.WorkspaceAgent = { + apps: [MockWorkspaceApp], + architecture: "amd64", + created_at: "", + environment_variables: {}, + id: "test-workspace-child-agent", + parent_id: "test-workspace-agent", + name: "a-workspace-child-agent", + operating_system: "linux", + resource_id: "", + status: "connected", + updated_at: "", + version: MockBuildInfo.version, + api_version: MockBuildInfo.agent_api_version, + latency: { + "Coder Embedded DERP": { + latency_ms: 32.55, + preferred: true, + }, + }, + connection_timeout_seconds: 120, + troubleshooting_url: "https://coder.com/troubleshoot", + lifecycle_state: "starting", + logs_length: 0, + logs_overflowed: false, + log_sources: [MockWorkspaceAgentLogSource], + scripts: [], + startup_script_behavior: "non-blocking", + subsystems: ["envbox", "exectrace"], + health: { + healthy: true, + }, + display_apps: [ + "ssh_helper", + "port_forwarding_helper", + "vscode", + "vscode_insiders", + "web_terminal", + ], +}; + export const MockWorkspaceAppStatus: TypesGen.WorkspaceAppStatus = { id: "test-app-status", created_at: "2022-05-17T17:39:01.382927298Z", From 79de869b8c25b4ead300b60a010aeed16fe7ea63 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 10:16:11 +0000 Subject: [PATCH 02/11] fix: add missing parent_id --- site/src/testHelpers/entities.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index dc0883bdbbdd5..182e03c6e7e72 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -932,6 +932,7 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { created_at: "", environment_variables: {}, id: "test-workspace-agent", + parent_id: null, name: "a-workspace-agent", operating_system: "linux", resource_id: "", From 548deaa7095046f38d064ec72f2ab8cbc9d56611 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 10:51:14 +0000 Subject: [PATCH 03/11] chore: `make fmt` --- site/src/pages/WorkspacePage/Workspace.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 85c749596176a..68ba271898204 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -355,13 +355,15 @@ export const Workspace: FC = ({ color: theme.palette.text.secondary, }} > - {// Calculate total status count - selectedResource.agents - ?.flatMap((agent) => agent.apps ?? []) - .reduce( - (count, app) => count + (app.statuses?.length ?? 0), - 0, - )}{" "} + { + // Calculate total status count + selectedResource.agents + ?.flatMap((agent) => agent.apps ?? []) + .reduce( + (count, app) => count + (app.statuses?.length ?? 0), + 0, + ) + }{" "} Total From 742b7f21bc77b4739b28f624715ccf06b788360f Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 11:11:10 +0000 Subject: [PATCH 04/11] chore: update `InsertWorkspaceAgent` query Update the `InsertWorkspaceAgent` query to insert the `parent_id` field. Also updates the `dbmem` implementation, as well as updates the `dbgen` package to handle this new field as well. --- coderd/database/dbgen/dbgen.go | 1 + coderd/database/dbmem/dbmem.go | 1 + coderd/database/queries.sql.go | 5 ++++- coderd/database/queries/workspaceagents.sql | 3 ++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 55c2fe4cf6965..966e3365839ca 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -181,6 +181,7 @@ func WorkspaceAgentPortShare(t testing.TB, db database.Store, orig database.Work func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgent) database.WorkspaceAgent { agt, err := db.InsertWorkspaceAgent(genCtx, database.InsertWorkspaceAgentParams{ ID: takeFirst(orig.ID, uuid.New()), + ParentID: takeFirst(orig.ParentID, uuid.NullUUID{}), CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()), UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()), Name: takeFirst(orig.Name, testutil.GetRandomName(t)), diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 6bae4455a89ef..eecf5f8b26329 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9569,6 +9569,7 @@ func (q *FakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.Inser agent := database.WorkspaceAgent{ ID: arg.ID, + ParentID: arg.ParentID, CreatedAt: arg.CreatedAt, UpdatedAt: arg.UpdatedAt, ResourceID: arg.ResourceID, diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 9d46821cfe7d8..6422005da46e9 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -14544,6 +14544,7 @@ const insertWorkspaceAgent = `-- name: InsertWorkspaceAgent :one INSERT INTO workspace_agents ( id, + parent_id, created_at, updated_at, name, @@ -14563,11 +14564,12 @@ INSERT INTO display_order ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id ` type InsertWorkspaceAgentParams struct { ID uuid.UUID `db:"id" json:"id"` + ParentID uuid.NullUUID `db:"parent_id" json:"parent_id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Name string `db:"name" json:"name"` @@ -14590,6 +14592,7 @@ type InsertWorkspaceAgentParams struct { func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) { row := q.db.QueryRowContext(ctx, insertWorkspaceAgent, arg.ID, + arg.ParentID, arg.CreatedAt, arg.UpdatedAt, arg.Name, diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 52d8b5275fc97..2e186461931b2 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -31,6 +31,7 @@ SELECT * FROM workspace_agents WHERE created_at > $1; INSERT INTO workspace_agents ( id, + parent_id, created_at, updated_at, name, @@ -50,7 +51,7 @@ INSERT INTO display_order ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING *; -- name: UpdateWorkspaceAgentConnectionByID :exec UPDATE From 2f9844a3f55ae21329780e4461cdfbd28f428919 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 11:18:11 +0000 Subject: [PATCH 05/11] fix: explicitly set `ParentID` to `NullUUID{}` --- coderd/provisionerdserver/provisionerdserver.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 9362d2f3e5a85..df3bdc6dc5457 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2006,6 +2006,7 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. agentID := uuid.New() dbAgent, err := db.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{ ID: agentID, + ParentID: uuid.NullUUID{}, CreatedAt: dbtime.Now(), UpdatedAt: dbtime.Now(), ResourceID: resource.ID, From cb4e5b23a147278b481e44486d5e1c8847e89326 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 15:12:43 +0000 Subject: [PATCH 06/11] chore: test for `ParentID` being `uuid.NullUUID{}` --- coderd/provisionerdserver/provisionerdserver_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index caeef8a9793b7..7a4b7640ed60a 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -2153,6 +2153,7 @@ func TestInsertWorkspaceResource(t *testing.T) { require.NoError(t, err) require.Len(t, agents, 1) agent := agents[0] + require.Equal(t, uuid.NullUUID{}, agent.ParentID) require.Equal(t, "amd64", agent.Architecture) require.Equal(t, "linux", agent.OperatingSystem) want, err := json.Marshal(map[string]string{ From 20da6e47368c2cb1cf3007d2e1610e95e6397e8d Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 15:12:59 +0000 Subject: [PATCH 07/11] chore: improve comment --- site/src/pages/WorkspacePage/Workspace.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 68ba271898204..87a2b7f0ecf39 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -269,13 +269,13 @@ export const Workspace: FC = ({ minWidth: 0 /* Prevent overflow */, }} > - {selectedResource.agents?.map((agent) => { - // We do not want to display child agents at the top-level. - if (agent.parent_id !== null) { - return; - } - - return ( + {selectedResource.agents + // If an agent has a `parent_id`, that means it is + // child of another agent. We do not want these agents + // to be displayed at the top-level on this page. We + // want them to display _as children_ of their parents. + ?.filter((agent) => agent.parent_id !== null) + .map((agent) => ( = ({ serverAPIVersion={buildInfo?.agent_api_version || ""} onUpdateAgent={handleUpdate} // On updating the workspace the agent version is also updated /> - ); - })} + ))} {(!selectedResource.agents || selectedResource.agents?.length === 0) && ( From 579b9c0bcf711588d1b0e716fdc7df4ea1ea07bc Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 15:13:08 +0000 Subject: [PATCH 08/11] chore: remove app from `MockWorkspaceChildAgent` --- site/src/testHelpers/entities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 182e03c6e7e72..7fa69c006b8e7 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -968,7 +968,7 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { }; export const MockWorkspaceChildAgent: TypesGen.WorkspaceAgent = { - apps: [MockWorkspaceApp], + apps: [], architecture: "amd64", created_at: "", environment_variables: {}, From 91c22e1e5b6eb409dd9b0924192a860da243d78b Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 22:24:11 +0000 Subject: [PATCH 09/11] fix: invert condition --- site/src/pages/WorkspacePage/Workspace.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 87a2b7f0ecf39..38a41051d3dee 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -274,7 +274,7 @@ export const Workspace: FC = ({ // child of another agent. We do not want these agents // to be displayed at the top-level on this page. We // want them to display _as children_ of their parents. - ?.filter((agent) => agent.parent_id !== null) + ?.filter((agent) => agent.parent_id === null) .map((agent) => ( = ({ color: theme.palette.text.secondary, }} > - { - // Calculate total status count - selectedResource.agents - ?.flatMap((agent) => agent.apps ?? []) - .reduce( - (count, app) => count + (app.statuses?.length ?? 0), - 0, - ) - }{" "} + {// Calculate total status count + selectedResource.agents + ?.flatMap((agent) => agent.apps ?? []) + .reduce( + (count, app) => count + (app.statuses?.length ?? 0), + 0, + )}{" "} Total From a8e9c7b65be72c94557bcd3b155ede6f7340b920 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 22:28:32 +0000 Subject: [PATCH 10/11] fix: bump migration number --- ...down.sql => 000321_add_parent_id_to_workspace_agents.down.sql} | 0 ...nts.up.sql => 000321_add_parent_id_to_workspace_agents.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000320_add_parent_id_to_workspace_agents.down.sql => 000321_add_parent_id_to_workspace_agents.down.sql} (100%) rename coderd/database/migrations/{000320_add_parent_id_to_workspace_agents.up.sql => 000321_add_parent_id_to_workspace_agents.up.sql} (100%) diff --git a/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.down.sql b/coderd/database/migrations/000321_add_parent_id_to_workspace_agents.down.sql similarity index 100% rename from coderd/database/migrations/000320_add_parent_id_to_workspace_agents.down.sql rename to coderd/database/migrations/000321_add_parent_id_to_workspace_agents.down.sql diff --git a/coderd/database/migrations/000320_add_parent_id_to_workspace_agents.up.sql b/coderd/database/migrations/000321_add_parent_id_to_workspace_agents.up.sql similarity index 100% rename from coderd/database/migrations/000320_add_parent_id_to_workspace_agents.up.sql rename to coderd/database/migrations/000321_add_parent_id_to_workspace_agents.up.sql From 8df43c4a2182a83a5923d5d0f5376ff32939191b Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 12 May 2025 22:32:52 +0000 Subject: [PATCH 11/11] chore: `make fmt` --- site/src/pages/WorkspacePage/Workspace.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 38a41051d3dee..1c60b8b86b50b 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -354,13 +354,15 @@ export const Workspace: FC = ({ color: theme.palette.text.secondary, }} > - {// Calculate total status count - selectedResource.agents - ?.flatMap((agent) => agent.apps ?? []) - .reduce( - (count, app) => count + (app.statuses?.length ?? 0), - 0, - )}{" "} + { + // Calculate total status count + selectedResource.agents + ?.flatMap((agent) => agent.apps ?? []) + .reduce( + (count, app) => count + (app.statuses?.length ?? 0), + 0, + ) + }{" "} Total