|
| 1 | +-- Update task status in view. |
| 2 | +DROP VIEW IF EXISTS tasks_with_status; |
| 3 | + |
| 4 | +CREATE VIEW |
| 5 | + tasks_with_status |
| 6 | +AS |
| 7 | + SELECT |
| 8 | + tasks.*, |
| 9 | + -- Combine component statuses with precedence: build -> agent -> app. |
| 10 | + CASE |
| 11 | + WHEN tasks.workspace_id IS NULL THEN 'pending'::task_status |
| 12 | + WHEN build_status.status != 'active' THEN build_status.status::task_status |
| 13 | + WHEN agent_status.status != 'active' THEN agent_status.status::task_status |
| 14 | + ELSE app_status.status::task_status |
| 15 | + END AS status, |
| 16 | + -- Attach debug information for troubleshooting status. |
| 17 | + jsonb_build_object( |
| 18 | + 'build', jsonb_build_object( |
| 19 | + 'transition', latest_build_raw.transition, |
| 20 | + 'job_status', latest_build_raw.job_status, |
| 21 | + 'computed', build_status.status |
| 22 | + ), |
| 23 | + 'agent', jsonb_build_object( |
| 24 | + 'lifecycle_state', agent_raw.lifecycle_state, |
| 25 | + 'computed', agent_status.status |
| 26 | + ), |
| 27 | + 'app', jsonb_build_object( |
| 28 | + 'health', app_raw.health, |
| 29 | + 'computed', app_status.status |
| 30 | + ) |
| 31 | + ) AS status_debug, |
| 32 | + task_app.*, |
| 33 | + agent_raw.lifecycle_state AS workspace_agent_lifecycle_state, |
| 34 | + app_raw.health AS workspace_app_health, |
| 35 | + task_owner.* |
| 36 | + FROM |
| 37 | + tasks |
| 38 | + CROSS JOIN LATERAL ( |
| 39 | + SELECT |
| 40 | + vu.username AS owner_username, |
| 41 | + vu.name AS owner_name, |
| 42 | + vu.avatar_url AS owner_avatar_url |
| 43 | + FROM |
| 44 | + visible_users vu |
| 45 | + WHERE |
| 46 | + vu.id = tasks.owner_id |
| 47 | + ) task_owner |
| 48 | + LEFT JOIN LATERAL ( |
| 49 | + SELECT |
| 50 | + task_app.workspace_build_number, |
| 51 | + task_app.workspace_agent_id, |
| 52 | + task_app.workspace_app_id |
| 53 | + FROM |
| 54 | + task_workspace_apps task_app |
| 55 | + WHERE |
| 56 | + task_id = tasks.id |
| 57 | + ORDER BY |
| 58 | + task_app.workspace_build_number DESC |
| 59 | + LIMIT 1 |
| 60 | + ) task_app ON TRUE |
| 61 | + |
| 62 | + -- Join the raw data for computing task status. |
| 63 | + LEFT JOIN LATERAL ( |
| 64 | + SELECT |
| 65 | + workspace_build.transition, |
| 66 | + provisioner_job.job_status, |
| 67 | + workspace_build.job_id |
| 68 | + FROM |
| 69 | + workspace_builds workspace_build |
| 70 | + JOIN |
| 71 | + provisioner_jobs provisioner_job |
| 72 | + ON provisioner_job.id = workspace_build.job_id |
| 73 | + WHERE |
| 74 | + workspace_build.workspace_id = tasks.workspace_id |
| 75 | + AND workspace_build.build_number = task_app.workspace_build_number |
| 76 | + ) latest_build_raw ON TRUE |
| 77 | + LEFT JOIN LATERAL ( |
| 78 | + SELECT |
| 79 | + workspace_agent.lifecycle_state |
| 80 | + FROM |
| 81 | + workspace_agents workspace_agent |
| 82 | + WHERE |
| 83 | + workspace_agent.id = task_app.workspace_agent_id |
| 84 | + ) agent_raw ON TRUE |
| 85 | + LEFT JOIN LATERAL ( |
| 86 | + SELECT |
| 87 | + workspace_app.health |
| 88 | + FROM |
| 89 | + workspace_apps workspace_app |
| 90 | + WHERE |
| 91 | + workspace_app.id = task_app.workspace_app_id |
| 92 | + ) app_raw ON TRUE |
| 93 | + |
| 94 | + -- Compute the status for each component. |
| 95 | + CROSS JOIN LATERAL ( |
| 96 | + SELECT |
| 97 | + CASE |
| 98 | + WHEN latest_build_raw.job_status IS NULL THEN 'pending'::task_status |
| 99 | + WHEN latest_build_raw.job_status IN ('failed', 'canceling', 'canceled') THEN 'error'::task_status |
| 100 | + WHEN |
| 101 | + latest_build_raw.transition IN ('stop', 'delete') |
| 102 | + AND latest_build_raw.job_status = 'succeeded' THEN 'paused'::task_status |
| 103 | + WHEN |
| 104 | + latest_build_raw.transition = 'start' |
| 105 | + AND latest_build_raw.job_status = 'pending' THEN 'initializing'::task_status |
| 106 | + -- Build is running or done, defer to agent/app status. |
| 107 | + WHEN |
| 108 | + latest_build_raw.transition = 'start' |
| 109 | + AND latest_build_raw.job_status IN ('running', 'succeeded') THEN 'active'::task_status |
| 110 | + ELSE 'unknown'::task_status |
| 111 | + END AS status |
| 112 | + ) build_status |
| 113 | + CROSS JOIN LATERAL ( |
| 114 | + SELECT |
| 115 | + CASE |
| 116 | + -- No agent or connecting. |
| 117 | + WHEN |
| 118 | + agent_raw.lifecycle_state IS NULL |
| 119 | + OR agent_raw.lifecycle_state IN ('created', 'starting') THEN 'initializing'::task_status |
| 120 | + -- Agent is running, defer to app status. |
| 121 | + -- NOTE(mafredri): The start_error/start_timeout states means connected, but some startup script failed. |
| 122 | + -- This may or may not affect the task status but this has to be caught by app health check. |
| 123 | + WHEN agent_raw.lifecycle_state IN ('ready', 'start_timeout', 'start_error') THEN 'active'::task_status |
| 124 | + -- If the agent is shutting down or turned off, this is an unknown state because we would expect a stop |
| 125 | + -- build to be running. |
| 126 | + -- This is essentially equal to: `IN ('shutting_down', 'shutdown_timeout', 'shutdown_error', 'off')`, |
| 127 | + -- but we cannot use them because the values were added in a migration. |
| 128 | + WHEN agent_raw.lifecycle_state NOT IN ('created', 'starting', 'ready', 'start_timeout', 'start_error') THEN 'unknown'::task_status |
| 129 | + ELSE 'unknown'::task_status |
| 130 | + END AS status |
| 131 | + ) agent_status |
| 132 | + CROSS JOIN LATERAL ( |
| 133 | + SELECT |
| 134 | + CASE |
| 135 | + WHEN app_raw.health = 'initializing' THEN 'initializing'::task_status |
| 136 | + WHEN app_raw.health = 'unhealthy' THEN 'error'::task_status |
| 137 | + WHEN app_raw.health IN ('healthy', 'disabled') THEN 'active'::task_status |
| 138 | + ELSE 'unknown'::task_status |
| 139 | + END AS status |
| 140 | + ) app_status |
| 141 | + WHERE |
| 142 | + tasks.deleted_at IS NULL; |
0 commit comments