diff --git a/coderd/aitasks.go b/coderd/aitasks.go index 8601b50196..8415a01454 100644 --- a/coderd/aitasks.go +++ b/coderd/aitasks.go @@ -339,8 +339,8 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod ID: dbTask.ID, OrganizationID: dbTask.OrganizationID, OwnerID: dbTask.OwnerID, - OwnerName: ws.OwnerName, - OwnerAvatarURL: ws.OwnerAvatarURL, + OwnerName: dbTask.OwnerUsername, + OwnerAvatarURL: dbTask.OwnerAvatarUrl, Name: dbTask.Name, TemplateID: ws.TemplateID, TemplateVersionID: dbTask.TemplateVersionID, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index b61b678bba..837c657402 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1828,6 +1828,15 @@ CREATE TABLE tasks ( deleted_at timestamp with time zone ); +CREATE VIEW visible_users AS + SELECT users.id, + users.username, + users.name, + users.avatar_url + FROM users; + +COMMENT ON VIEW visible_users IS 'Visible fields of users are allowed to be joined with other tables for including context of other resources.'; + CREATE TABLE workspace_agents ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -1978,8 +1987,16 @@ CREATE VIEW tasks_with_status AS END AS status, task_app.workspace_build_number, task_app.workspace_agent_id, - task_app.workspace_app_id - FROM ((((tasks + task_app.workspace_app_id, + task_owner.owner_username, + task_owner.owner_name, + task_owner.owner_avatar_url + FROM (((((tasks + CROSS JOIN LATERAL ( SELECT vu.username AS owner_username, + vu.name AS owner_name, + vu.avatar_url AS owner_avatar_url + FROM visible_users vu + WHERE (vu.id = tasks.owner_id)) task_owner) LEFT JOIN LATERAL ( SELECT task_app_1.workspace_build_number, task_app_1.workspace_agent_id, task_app_1.workspace_app_id @@ -2210,15 +2227,6 @@ COMMENT ON COLUMN template_versions.external_auth_providers IS 'IDs of External COMMENT ON COLUMN template_versions.message IS 'Message describing the changes in this version of the template, similar to a Git commit message. Like a commit message, this should be a short, high-level description of the changes in this version of the template. This message is immutable and should not be updated after the fact.'; -CREATE VIEW visible_users AS - SELECT users.id, - users.username, - users.name, - users.avatar_url - FROM users; - -COMMENT ON VIEW visible_users IS 'Visible fields of users are allowed to be joined with other tables for including context of other resources.'; - CREATE VIEW template_version_with_user AS SELECT template_versions.id, template_versions.template_id, diff --git a/coderd/database/migrations/000391_tasks_with_status_user_fields.down.sql b/coderd/database/migrations/000391_tasks_with_status_user_fields.down.sql new file mode 100644 index 0000000000..ff103d47e0 --- /dev/null +++ b/coderd/database/migrations/000391_tasks_with_status_user_fields.down.sql @@ -0,0 +1,74 @@ +-- Drop view from 000390_tasks_with_status_user_fields.up.sql. +DROP VIEW IF EXISTS tasks_with_status; + +-- Restore from 000382_add_columns_to_tasks_with_status.up.sql. +CREATE VIEW + tasks_with_status +AS + SELECT + tasks.*, + CASE + WHEN tasks.workspace_id IS NULL OR latest_build.job_status IS NULL THEN 'pending'::task_status + + WHEN latest_build.job_status = 'failed' THEN 'error'::task_status + + WHEN latest_build.transition IN ('stop', 'delete') + AND latest_build.job_status = 'succeeded' THEN 'paused'::task_status + + WHEN latest_build.transition = 'start' + AND latest_build.job_status = 'pending' THEN 'initializing'::task_status + + WHEN latest_build.transition = 'start' AND latest_build.job_status IN ('running', 'succeeded') THEN + CASE + WHEN agent_status.none THEN 'initializing'::task_status + WHEN agent_status.connecting THEN 'initializing'::task_status + WHEN agent_status.connected THEN + CASE + WHEN app_status.any_unhealthy THEN 'error'::task_status + WHEN app_status.any_initializing THEN 'initializing'::task_status + WHEN app_status.all_healthy_or_disabled THEN 'active'::task_status + ELSE 'unknown'::task_status + END + ELSE 'unknown'::task_status + END + + ELSE 'unknown'::task_status + END AS status, + task_app.* + FROM + tasks + LEFT JOIN LATERAL ( + SELECT workspace_build_number, workspace_agent_id, workspace_app_id + FROM task_workspace_apps task_app + WHERE task_id = tasks.id + ORDER BY workspace_build_number DESC + LIMIT 1 + ) task_app ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_build.transition, + provisioner_job.job_status, + workspace_build.job_id + FROM workspace_builds workspace_build + JOIN provisioner_jobs provisioner_job ON provisioner_job.id = workspace_build.job_id + WHERE workspace_build.workspace_id = tasks.workspace_id + AND workspace_build.build_number = task_app.workspace_build_number + ) latest_build ON TRUE + CROSS JOIN LATERAL ( + SELECT + COUNT(*) = 0 AS none, + bool_or(workspace_agent.lifecycle_state IN ('created', 'starting')) AS connecting, + bool_and(workspace_agent.lifecycle_state = 'ready') AS connected + FROM workspace_agents workspace_agent + WHERE workspace_agent.id = task_app.workspace_agent_id + ) agent_status + CROSS JOIN LATERAL ( + SELECT + bool_or(workspace_app.health = 'unhealthy') AS any_unhealthy, + bool_or(workspace_app.health = 'initializing') AS any_initializing, + bool_and(workspace_app.health IN ('healthy', 'disabled')) AS all_healthy_or_disabled + FROM workspace_apps workspace_app + WHERE workspace_app.id = task_app.workspace_app_id + ) app_status + WHERE + tasks.deleted_at IS NULL; diff --git a/coderd/database/migrations/000391_tasks_with_status_user_fields.up.sql b/coderd/database/migrations/000391_tasks_with_status_user_fields.up.sql new file mode 100644 index 0000000000..243772c241 --- /dev/null +++ b/coderd/database/migrations/000391_tasks_with_status_user_fields.up.sql @@ -0,0 +1,84 @@ +-- Drop view from 00037_add_columns_to_tasks_with_status.up.sql. +DROP VIEW IF EXISTS tasks_with_status; + +-- Add owner_name, owner_avatar_url columns. +CREATE VIEW + tasks_with_status +AS + SELECT + tasks.*, + CASE + WHEN tasks.workspace_id IS NULL OR latest_build.job_status IS NULL THEN 'pending'::task_status + + WHEN latest_build.job_status = 'failed' THEN 'error'::task_status + + WHEN latest_build.transition IN ('stop', 'delete') + AND latest_build.job_status = 'succeeded' THEN 'paused'::task_status + + WHEN latest_build.transition = 'start' + AND latest_build.job_status = 'pending' THEN 'initializing'::task_status + + WHEN latest_build.transition = 'start' AND latest_build.job_status IN ('running', 'succeeded') THEN + CASE + WHEN agent_status.none THEN 'initializing'::task_status + WHEN agent_status.connecting THEN 'initializing'::task_status + WHEN agent_status.connected THEN + CASE + WHEN app_status.any_unhealthy THEN 'error'::task_status + WHEN app_status.any_initializing THEN 'initializing'::task_status + WHEN app_status.all_healthy_or_disabled THEN 'active'::task_status + ELSE 'unknown'::task_status + END + ELSE 'unknown'::task_status + END + + ELSE 'unknown'::task_status + END AS status, + task_app.*, + task_owner.* + FROM + tasks + CROSS JOIN LATERAL ( + SELECT + vu.username AS owner_username, + vu.name AS owner_name, + vu.avatar_url AS owner_avatar_url + FROM visible_users vu + WHERE vu.id = tasks.owner_id + ) task_owner + LEFT JOIN LATERAL ( + SELECT workspace_build_number, workspace_agent_id, workspace_app_id + FROM task_workspace_apps task_app + WHERE task_id = tasks.id + ORDER BY workspace_build_number DESC + LIMIT 1 + ) task_app ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_build.transition, + provisioner_job.job_status, + workspace_build.job_id + FROM workspace_builds workspace_build + JOIN provisioner_jobs provisioner_job ON provisioner_job.id = workspace_build.job_id + WHERE workspace_build.workspace_id = tasks.workspace_id + AND workspace_build.build_number = task_app.workspace_build_number + ) latest_build ON TRUE + CROSS JOIN LATERAL ( + SELECT + COUNT(*) = 0 AS none, + bool_or(workspace_agent.lifecycle_state IN ('created', 'starting')) AS connecting, + bool_and(workspace_agent.lifecycle_state = 'ready') AS connected + FROM workspace_agents workspace_agent + WHERE workspace_agent.id = task_app.workspace_agent_id + ) agent_status + CROSS JOIN LATERAL ( + SELECT + bool_or(workspace_app.health = 'unhealthy') AS any_unhealthy, + bool_or(workspace_app.health = 'initializing') AS any_initializing, + bool_and(workspace_app.health IN ('healthy', 'disabled')) AS all_healthy_or_disabled + FROM workspace_apps workspace_app + WHERE workspace_app.id = task_app.workspace_app_id + ) app_status + WHERE + tasks.deleted_at IS NULL; + diff --git a/coderd/database/models.go b/coderd/database/models.go index b3d41b25f9..e55f3a5537 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -4221,6 +4221,9 @@ type Task struct { WorkspaceBuildNumber sql.NullInt32 `db:"workspace_build_number" json:"workspace_build_number"` WorkspaceAgentID uuid.NullUUID `db:"workspace_agent_id" json:"workspace_agent_id"` WorkspaceAppID uuid.NullUUID `db:"workspace_app_id" json:"workspace_app_id"` + OwnerUsername string `db:"owner_username" json:"owner_username"` + OwnerName string `db:"owner_name" json:"owner_name"` + OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"` } type TaskTable struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 8e24ab04c9..2005f98347 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -12964,7 +12964,7 @@ func (q *sqlQuerier) DeleteTask(ctx context.Context, arg DeleteTaskParams) (Task } const getTaskByID = `-- name: GetTaskByID :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id FROM tasks_with_status WHERE id = $1::uuid +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE id = $1::uuid ` func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error) { @@ -12985,12 +12985,15 @@ func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error &i.WorkspaceBuildNumber, &i.WorkspaceAgentID, &i.WorkspaceAppID, + &i.OwnerUsername, + &i.OwnerName, + &i.OwnerAvatarUrl, ) return i, err } const getTaskByWorkspaceID = `-- name: GetTaskByWorkspaceID :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id FROM tasks_with_status WHERE workspace_id = $1::uuid +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE workspace_id = $1::uuid ` func (q *sqlQuerier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (Task, error) { @@ -13011,6 +13014,9 @@ func (q *sqlQuerier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid. &i.WorkspaceBuildNumber, &i.WorkspaceAgentID, &i.WorkspaceAppID, + &i.OwnerUsername, + &i.OwnerName, + &i.OwnerAvatarUrl, ) return i, err } @@ -13064,7 +13070,7 @@ func (q *sqlQuerier) InsertTask(ctx context.Context, arg InsertTaskParams) (Task } const listTasks = `-- name: ListTasks :many -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id FROM tasks_with_status tws +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id, owner_username, owner_name, owner_avatar_url FROM tasks_with_status tws WHERE tws.deleted_at IS NULL AND CASE WHEN $1::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.owner_id = $1::UUID ELSE TRUE END AND CASE WHEN $2::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.organization_id = $2::UUID ELSE TRUE END @@ -13102,6 +13108,9 @@ func (q *sqlQuerier) ListTasks(ctx context.Context, arg ListTasksParams) ([]Task &i.WorkspaceBuildNumber, &i.WorkspaceAgentID, &i.WorkspaceAppID, + &i.OwnerUsername, + &i.OwnerName, + &i.OwnerAvatarUrl, ); err != nil { return nil, err }