Visually identify when a task has failed in the kanban
This commit is contained in:
committed by
GitHub
parent
aca769983e
commit
749ddd5ccb
@@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "SELECT \n t.id AS \"id!: Uuid\", \n t.project_id AS \"project_id!: Uuid\", \n t.title, \n t.description, \n t.status AS \"status!: TaskStatus\", \n t.created_at AS \"created_at!: DateTime<Utc>\", \n t.updated_at AS \"updated_at!: DateTime<Utc>\",\n CASE \n WHEN in_progress_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_in_progress_attempt!: i64\",\n CASE \n WHEN merged_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_merged_attempt!\"\n FROM tasks t\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n JOIN execution_processes ep \n ON ta.id = ep.task_attempt_id\n JOIN (\n -- pick exactly one “latest” activity per process,\n -- tiebreaking so that running‐states are lower priority\n SELECT execution_process_id, status\n FROM (\n SELECT\n execution_process_id,\n status,\n ROW_NUMBER() OVER (\n PARTITION BY execution_process_id\n ORDER BY\n created_at DESC,\n CASE \n WHEN status IN ('setuprunning','executorrunning') THEN 1 \n ELSE 0 \n END\n ) AS rn\n FROM task_attempt_activities\n ) sub\n WHERE rn = 1\n ) latest_act \n ON ep.id = latest_act.execution_process_id\n WHERE latest_act.status IN ('setuprunning','executorrunning')\n ) in_progress_attempts \n ON t.id = in_progress_attempts.task_id\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n WHERE ta.merge_commit IS NOT NULL\n ) merged_attempts \n ON t.id = merged_attempts.task_id\n WHERE t.project_id = $1\n ORDER BY t.created_at DESC;\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id!: Uuid",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Blob"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "project_id!: Uuid",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Blob"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "title",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "description",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "status!: TaskStatus",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at!: DateTime<Utc>",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "has_in_progress_attempt!: i64",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Integer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "has_merged_attempt!",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Integer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "5303664388a91c378ade88cad68fcb1bce32a4be1269820bf2031dd6b7e13be8"
|
|
||||||
}
|
|
||||||
74
backend/.sqlx/query-b7d9fc7198a30ad4e88d7cdd7b59a1a73018de16506003622cd7f779028c0fa8.json
generated
Normal file
74
backend/.sqlx/query-b7d9fc7198a30ad4e88d7cdd7b59a1a73018de16506003622cd7f779028c0fa8.json
generated
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT \n t.id AS \"id!: Uuid\", \n t.project_id AS \"project_id!: Uuid\", \n t.title, \n t.description, \n t.status AS \"status!: TaskStatus\", \n t.created_at AS \"created_at!: DateTime<Utc>\", \n t.updated_at AS \"updated_at!: DateTime<Utc>\",\n CASE \n WHEN in_progress_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_in_progress_attempt!: i64\",\n CASE \n WHEN merged_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_merged_attempt!\",\n CASE \n WHEN failed_attempts.task_id IS NOT NULL THEN true \n ELSE false \n END AS \"has_failed_attempt!\"\n FROM tasks t\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n JOIN execution_processes ep \n ON ta.id = ep.task_attempt_id\n JOIN (\n -- pick exactly one “latest” activity per process,\n -- tiebreaking so that running‐states are lower priority\n SELECT execution_process_id, status\n FROM (\n SELECT\n execution_process_id,\n status,\n ROW_NUMBER() OVER (\n PARTITION BY execution_process_id\n ORDER BY\n created_at DESC,\n CASE \n WHEN status IN ('setuprunning','executorrunning') THEN 1 \n ELSE 0 \n END\n ) AS rn\n FROM task_attempt_activities\n ) sub\n WHERE rn = 1\n ) latest_act \n ON ep.id = latest_act.execution_process_id\n WHERE latest_act.status IN ('setuprunning','executorrunning')\n ) in_progress_attempts \n ON t.id = in_progress_attempts.task_id\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n WHERE ta.merge_commit IS NOT NULL\n ) merged_attempts \n ON t.id = merged_attempts.task_id\n LEFT JOIN (\n SELECT DISTINCT ta.task_id\n FROM task_attempts ta\n JOIN execution_processes ep \n ON ta.id = ep.task_attempt_id\n JOIN (\n -- pick exactly one \"latest\" activity per process,\n -- tiebreaking so that running‐states are lower priority\n SELECT execution_process_id, status\n FROM (\n SELECT\n execution_process_id,\n status,\n ROW_NUMBER() OVER (\n PARTITION BY execution_process_id\n ORDER BY\n created_at DESC,\n CASE \n WHEN status IN ('setuprunning','executorrunning') THEN 1 \n ELSE 0 \n END\n ) AS rn\n FROM task_attempt_activities\n ) sub\n WHERE rn = 1\n ) latest_act \n ON ep.id = latest_act.execution_process_id\n WHERE latest_act.status IN ('setupfailed','executorfailed')\n AND ta.merge_commit IS NULL -- Don't show as failed if already merged\n ) failed_attempts \n ON t.id = failed_attempts.task_id\n WHERE t.project_id = $1\n ORDER BY t.created_at DESC;\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id!: Uuid",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "project_id!: Uuid",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status!: TaskStatus",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "has_in_progress_attempt!: i64",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "has_merged_attempt!",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "has_failed_attempt!",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "b7d9fc7198a30ad4e88d7cdd7b59a1a73018de16506003622cd7f779028c0fa8"
|
||||||
|
}
|
||||||
@@ -93,6 +93,8 @@ pub struct TaskSummary {
|
|||||||
pub has_in_progress_attempt: Option<bool>,
|
pub has_in_progress_attempt: Option<bool>,
|
||||||
#[schemars(description = "Whether the task has a merged execution attempt")]
|
#[schemars(description = "Whether the task has a merged execution attempt")]
|
||||||
pub has_merged_attempt: Option<bool>,
|
pub has_merged_attempt: Option<bool>,
|
||||||
|
#[schemars(description = "Whether the task has a failed execution attempt")]
|
||||||
|
pub has_failed_attempt: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, schemars::JsonSchema)]
|
#[derive(Debug, Serialize, schemars::JsonSchema)]
|
||||||
@@ -513,6 +515,7 @@ impl TaskServer {
|
|||||||
updated_at: task.updated_at.to_rfc3339(),
|
updated_at: task.updated_at.to_rfc3339(),
|
||||||
has_in_progress_attempt: Some(task.has_in_progress_attempt),
|
has_in_progress_attempt: Some(task.has_in_progress_attempt),
|
||||||
has_merged_attempt: Some(task.has_merged_attempt),
|
has_merged_attempt: Some(task.has_merged_attempt),
|
||||||
|
has_failed_attempt: Some(task.has_failed_attempt),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -656,6 +659,7 @@ impl TaskServer {
|
|||||||
updated_at: updated_task.updated_at.to_rfc3339(),
|
updated_at: updated_task.updated_at.to_rfc3339(),
|
||||||
has_in_progress_attempt: None,
|
has_in_progress_attempt: None,
|
||||||
has_merged_attempt: None,
|
has_merged_attempt: None,
|
||||||
|
has_failed_attempt: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = UpdateTaskResponse {
|
let response = UpdateTaskResponse {
|
||||||
@@ -1220,6 +1224,7 @@ impl TaskServer {
|
|||||||
updated_at: task.updated_at.to_rfc3339(),
|
updated_at: task.updated_at.to_rfc3339(),
|
||||||
has_in_progress_attempt: None,
|
has_in_progress_attempt: None,
|
||||||
has_merged_attempt: None,
|
has_merged_attempt: None,
|
||||||
|
has_failed_attempt: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = GetTaskResponse {
|
let response = GetTaskResponse {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub struct TaskWithAttemptStatus {
|
|||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
pub has_in_progress_attempt: bool,
|
pub has_in_progress_attempt: bool,
|
||||||
pub has_merged_attempt: bool,
|
pub has_merged_attempt: bool,
|
||||||
|
pub has_failed_attempt: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, TS)]
|
#[derive(Debug, Deserialize, TS)]
|
||||||
@@ -88,7 +89,11 @@ impl Task {
|
|||||||
CASE
|
CASE
|
||||||
WHEN merged_attempts.task_id IS NOT NULL THEN true
|
WHEN merged_attempts.task_id IS NOT NULL THEN true
|
||||||
ELSE false
|
ELSE false
|
||||||
END AS "has_merged_attempt!"
|
END AS "has_merged_attempt!",
|
||||||
|
CASE
|
||||||
|
WHEN failed_attempts.task_id IS NOT NULL THEN true
|
||||||
|
ELSE false
|
||||||
|
END AS "has_failed_attempt!"
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT DISTINCT ta.task_id
|
SELECT DISTINCT ta.task_id
|
||||||
@@ -126,6 +131,37 @@ impl Task {
|
|||||||
WHERE ta.merge_commit IS NOT NULL
|
WHERE ta.merge_commit IS NOT NULL
|
||||||
) merged_attempts
|
) merged_attempts
|
||||||
ON t.id = merged_attempts.task_id
|
ON t.id = merged_attempts.task_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT DISTINCT ta.task_id
|
||||||
|
FROM task_attempts ta
|
||||||
|
JOIN execution_processes ep
|
||||||
|
ON ta.id = ep.task_attempt_id
|
||||||
|
JOIN (
|
||||||
|
-- pick exactly one "latest" activity per process,
|
||||||
|
-- tiebreaking so that running‐states are lower priority
|
||||||
|
SELECT execution_process_id, status
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
execution_process_id,
|
||||||
|
status,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY execution_process_id
|
||||||
|
ORDER BY
|
||||||
|
created_at DESC,
|
||||||
|
CASE
|
||||||
|
WHEN status IN ('setuprunning','executorrunning') THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
) AS rn
|
||||||
|
FROM task_attempt_activities
|
||||||
|
) sub
|
||||||
|
WHERE rn = 1
|
||||||
|
) latest_act
|
||||||
|
ON ep.id = latest_act.execution_process_id
|
||||||
|
WHERE latest_act.status IN ('setupfailed','executorfailed')
|
||||||
|
AND ta.merge_commit IS NULL -- Don't show as failed if already merged
|
||||||
|
) failed_attempts
|
||||||
|
ON t.id = failed_attempts.task_id
|
||||||
WHERE t.project_id = $1
|
WHERE t.project_id = $1
|
||||||
ORDER BY t.created_at DESC;
|
ORDER BY t.created_at DESC;
|
||||||
"#,
|
"#,
|
||||||
@@ -146,6 +182,7 @@ impl Task {
|
|||||||
updated_at: record.updated_at,
|
updated_at: record.updated_at,
|
||||||
has_in_progress_attempt: record.has_in_progress_attempt != 0,
|
has_in_progress_attempt: record.has_in_progress_attempt != 0,
|
||||||
has_merged_attempt: record.has_merged_attempt != 0,
|
has_merged_attempt: record.has_merged_attempt != 0,
|
||||||
|
has_failed_attempt: record.has_failed_attempt != 0,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
Loader2,
|
Loader2,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
|
XCircle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { TaskWithAttemptStatus } from 'shared/types';
|
import type { TaskWithAttemptStatus } from 'shared/types';
|
||||||
|
|
||||||
@@ -57,6 +58,10 @@ export function TaskCard({
|
|||||||
{task.has_merged_attempt && (
|
{task.has_merged_attempt && (
|
||||||
<CheckCircle className="h-3 w-3 text-green-500" />
|
<CheckCircle className="h-3 w-3 text-green-500" />
|
||||||
)}
|
)}
|
||||||
|
{/* Failed Indicator */}
|
||||||
|
{task.has_failed_attempt && !task.has_merged_attempt && (
|
||||||
|
<XCircle className="h-3 w-3 text-red-500" />
|
||||||
|
)}
|
||||||
{/* Actions Menu */}
|
{/* Actions Menu */}
|
||||||
<div
|
<div
|
||||||
onPointerDown={(e) => e.stopPropagation()}
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export type TaskStatus = "todo" | "inprogress" | "inreview" | "done" | "cancelle
|
|||||||
|
|
||||||
export type Task = { id: string, project_id: string, title: string, description: string | null, status: TaskStatus, created_at: string, updated_at: string, };
|
export type Task = { id: string, project_id: string, title: string, description: string | null, status: TaskStatus, created_at: string, updated_at: string, };
|
||||||
|
|
||||||
export type TaskWithAttemptStatus = { id: string, project_id: string, title: string, description: string | null, status: TaskStatus, created_at: string, updated_at: string, has_in_progress_attempt: boolean, has_merged_attempt: boolean, };
|
export type TaskWithAttemptStatus = { id: string, project_id: string, title: string, description: string | null, status: TaskStatus, created_at: string, updated_at: string, has_in_progress_attempt: boolean, has_merged_attempt: boolean, has_failed_attempt: boolean, };
|
||||||
|
|
||||||
export type UpdateTask = { title: string | null, description: string | null, status: TaskStatus | null, };
|
export type UpdateTask = { title: string | null, description: string | null, status: TaskStatus | null, };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user