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