Skip to main content

aven_core/query/
related.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use sqlx::{QueryBuilder, Row, Sqlite, SqliteConnection};
5
6use crate::choices::{TaskPriority, TaskStatus};
7use crate::ids::{TaskId, WorkspaceId};
8use crate::refs::DisplayRefContext;
9
10const RELATED_BIND_CHUNK_SIZE: usize = 449;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct TaskRelatedLink {
14    pub project_key: String,
15    pub task_id: TaskId,
16    pub display_ref: String,
17    pub title: String,
18    pub status: TaskStatus,
19    pub priority: TaskPriority,
20    pub deleted: bool,
21    pub linked_at: String,
22}
23
24pub(crate) async fn related_links_for_tasks(
25    conn: &mut SqliteConnection,
26    workspace_id: &WorkspaceId,
27    task_ids: &[TaskId],
28    display_refs: &DisplayRefContext,
29) -> Result<HashMap<TaskId, Vec<TaskRelatedLink>>> {
30    let mut links_by_task = HashMap::new();
31    for chunk in task_ids.chunks(RELATED_BIND_CHUNK_SIZE) {
32        let mut query = QueryBuilder::<Sqlite>::new(
33            "SELECT r.task_a_id AS source_task_id,
34                    t.id, t.title, t.status, t.priority, t.deleted,
35                    p.key AS project_key, p.prefix AS project_prefix, c.created_at AS linked_at
36             FROM task_related_links r
37             JOIN tasks t ON t.workspace_id = r.workspace_id AND t.id = r.task_b_id
38             JOIN projects p ON p.workspace_id = t.workspace_id AND p.id = t.project_id
39             JOIN changes c ON c.change_id = r.last_change_id
40             WHERE r.workspace_id = ",
41        );
42        query.push_bind(workspace_id);
43        query.push(" AND r.task_a_id IN (");
44        {
45            let mut separated = query.separated(", ");
46            for task_id in chunk {
47                separated.push_bind(task_id);
48            }
49        }
50        query.push(
51            ") AND r.linked = 1 UNION ALL SELECT r.task_b_id AS source_task_id,
52                    t.id, t.title, t.status, t.priority, t.deleted,
53                    p.key AS project_key, p.prefix AS project_prefix, c.created_at AS linked_at
54             FROM task_related_links r
55             JOIN tasks t ON t.workspace_id = r.workspace_id AND t.id = r.task_a_id
56             JOIN projects p ON p.workspace_id = t.workspace_id AND p.id = t.project_id
57             JOIN changes c ON c.change_id = r.last_change_id
58             WHERE r.workspace_id = ",
59        );
60        query.push_bind(workspace_id);
61        query.push(" AND r.task_b_id IN (");
62        {
63            let mut separated = query.separated(", ");
64            for task_id in chunk {
65                separated.push_bind(task_id);
66            }
67        }
68        query.push(") AND r.linked = 1");
69
70        for row in query.build().fetch_all(&mut *conn).await? {
71            let source_task_id: TaskId = row.get("source_task_id");
72            let related_id: TaskId = row.get("id");
73            let project_prefix: String = row.get("project_prefix");
74            links_by_task
75                .entry(source_task_id)
76                .or_insert_with(Vec::new)
77                .push(TaskRelatedLink {
78                    project_key: row.get("project_key"),
79                    display_ref: display_refs.display_ref_for_id(
80                        workspace_id,
81                        &project_prefix,
82                        &related_id,
83                    ),
84                    task_id: related_id,
85                    title: row.get("title"),
86                    status: TaskStatus::parse(&row.get::<String, _>("status"))?,
87                    priority: TaskPriority::parse(&row.get::<String, _>("priority"))?,
88                    deleted: row.get::<i64, _>("deleted") != 0,
89                    linked_at: row.get("linked_at"),
90                });
91        }
92    }
93
94    for links in links_by_task.values_mut() {
95        links.sort_by(|left, right| {
96            left.deleted
97                .cmp(&right.deleted)
98                .then_with(|| {
99                    super::status_order(left.status).cmp(&super::status_order(right.status))
100                })
101                .then_with(|| left.title.cmp(&right.title))
102                .then_with(|| left.linked_at.cmp(&right.linked_at))
103                .then_with(|| left.task_id.cmp(&right.task_id))
104        });
105    }
106    Ok(links_by_task)
107}
108
109pub(crate) async fn task_related_links(
110    conn: &mut SqliteConnection,
111    workspace_id: &WorkspaceId,
112    task_id: &TaskId,
113    display_refs: Option<&DisplayRefContext>,
114) -> Result<Vec<TaskRelatedLink>> {
115    let owned_refs;
116    let display_refs = match display_refs {
117        Some(refs) => refs,
118        None => {
119            owned_refs = DisplayRefContext::for_workspace(conn, workspace_id).await?;
120            &owned_refs
121        }
122    };
123    Ok(related_links_for_tasks(
124        conn,
125        workspace_id,
126        std::slice::from_ref(task_id),
127        display_refs,
128    )
129    .await?
130    .remove(task_id)
131    .unwrap_or_default())
132}