Skip to main content

aven_core/query/
dependencies.rs

1use 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}