aven_core/query/
dependencies.rs1use crate::ids::WorkspaceId;
2use anyhow::Result;
3use sqlx::{Row, SqliteConnection};
4
5use crate::choices::TaskStatus;
6use crate::refs::DisplayRefContext;
7use crate::types::Task;
8
9use super::fragments;
10
11#[derive(Debug)]
12pub struct TaskDependencyItem {
13 pub task: Task,
14 pub display_ref: String,
15 pub created_at: String,
16 pub unresolved: bool,
17}
18
19#[derive(Debug)]
20pub struct TaskDependencySummary {
21 pub depends_on: Vec<TaskDependencyItem>,
22 pub blocks: Vec<TaskDependencyItem>,
23}
24
25pub async fn task_dependency_summary(
26 conn: &mut SqliteConnection,
27 workspace_id: &WorkspaceId,
28 task_id: &crate::ids::TaskId,
29) -> Result<TaskDependencySummary> {
30 let display_refs = DisplayRefContext::for_workspace(conn, workspace_id).await?;
31 task_dependency_summary_with_display_refs(conn, workspace_id, task_id, &display_refs).await
32}
33
34pub async fn task_dependency_summary_with_display_refs(
35 conn: &mut SqliteConnection,
36 workspace_id: &WorkspaceId,
37 task_id: &crate::ids::TaskId,
38 display_refs: &DisplayRefContext,
39) -> Result<TaskDependencySummary> {
40 let depends_on = query_dependency_items(&mut *conn, workspace_id, task_id, false, display_refs)
41 .await?
42 .into_iter()
43 .collect::<Vec<_>>();
44 let blocks = query_dependency_items(&mut *conn, workspace_id, task_id, true, display_refs)
45 .await?
46 .into_iter()
47 .collect::<Vec<_>>();
48 Ok(TaskDependencySummary { depends_on, blocks })
49}
50
51async fn query_dependency_items(
52 conn: &mut SqliteConnection,
53 workspace_id: &WorkspaceId,
54 task_id: &crate::ids::TaskId,
55 blocks_only: bool,
56 display_refs: &DisplayRefContext,
57) -> Result<Vec<TaskDependencyItem>> {
58 let rows = if blocks_only {
59 sqlx::query(
60 "SELECT t.id, t.workspace_id, t.title, t.description, t.project_id,
61 p.key AS project_key, p.prefix AS project_prefix, t.status, t.priority, t.created_at, t.updated_at,
62 t.queue_activity_at, t.available_at, t.due_on, t.deleted, t.is_epic, d.created_at AS dependency_created_at
63 FROM task_dependencies d
64 JOIN tasks t ON t.workspace_id = d.workspace_id AND t.id = d.task_id
65 JOIN projects p ON p.workspace_id = t.workspace_id AND p.id = t.project_id
66 WHERE d.workspace_id = ? AND d.depends_on_task_id = ?",
67 )
68 .bind(workspace_id)
69 .bind(task_id)
70 .fetch_all(&mut *conn)
71 .await?
72 } else {
73 sqlx::query(
74 "SELECT t.id, t.workspace_id, t.title, t.description, t.project_id,
75 p.key AS project_key, p.prefix AS project_prefix, t.status, t.priority, t.created_at, t.updated_at,
76 t.queue_activity_at, t.available_at, t.due_on, t.deleted, t.is_epic, d.created_at AS dependency_created_at
77 FROM task_dependencies d
78 JOIN tasks t ON t.workspace_id = d.workspace_id AND t.id = d.depends_on_task_id
79 JOIN projects p ON p.workspace_id = t.workspace_id AND p.id = t.project_id
80 WHERE d.workspace_id = ? AND d.task_id = ?",
81 )
82 .bind(workspace_id)
83 .bind(task_id)
84 .fetch_all(&mut *conn)
85 .await?
86 };
87
88 let subject_is_open = if blocks_only {
89 subject_task_is_open(conn, workspace_id, task_id).await?
90 } else {
91 true
92 };
93 let mut rows_tasks = rows
94 .iter()
95 .map(crate::db::task_from_row)
96 .collect::<Result<Vec<_>>>()?;
97 let mut items = rows
98 .into_iter()
99 .zip(rows_tasks.drain(..))
100 .map(|(row, task)| {
101 let created_at: String = row.get("dependency_created_at");
102 let task_is_open = !task.deleted && task.status.is_open();
103 let unresolved = task_is_open && (!blocks_only || subject_is_open);
104 let display_ref = display_refs.display_ref(&task);
105 TaskDependencyItem {
106 task,
107 display_ref,
108 created_at,
109 unresolved,
110 }
111 })
112 .collect::<Vec<_>>();
113
114 items.sort_by(|a, b| {
115 b.unresolved.cmp(&a.unresolved).then_with(|| {
116 status_order(a.task.status)
117 .cmp(&status_order(b.task.status))
118 .then_with(|| a.task.title.cmp(&b.task.title))
119 .then_with(|| a.created_at.cmp(&b.created_at))
120 .then_with(|| a.task.id.cmp(&b.task.id))
121 })
122 });
123 Ok(items)
124}
125
126async fn subject_task_is_open(
127 conn: &mut SqliteConnection,
128 workspace_id: &WorkspaceId,
129 task_id: &crate::ids::TaskId,
130) -> Result<bool> {
131 let sql = format!(
132 "SELECT count(*) FROM tasks
133 WHERE workspace_id = ? AND id = ? AND {}",
134 fragments::open_task_clause("tasks"),
135 );
136 let open: i64 = sqlx::query_scalar(sqlx::AssertSqlSafe(sql.as_str()))
137 .bind(workspace_id)
138 .bind(task_id)
139 .fetch_one(&mut *conn)
140 .await?;
141 Ok(open > 0)
142}
143
144fn status_order(status: TaskStatus) -> u8 {
145 match status {
146 TaskStatus::Active => 0,
147 TaskStatus::Todo => 1,
148 TaskStatus::Inbox => 2,
149 TaskStatus::Backlog => 3,
150 TaskStatus::Done => 4,
151 TaskStatus::Canceled => 5,
152 }
153}