vissue_core/views.rs
1//! Typed issue views shared by JSON output and later control clients.
2
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::path::PathBuf;
6
7use crate::model::IssueHeading;
8
9/// One parsed heading plus the `issues.org` it came from.
10#[derive(Debug, Clone)]
11pub struct IssueRec {
12 /// Project directory name the heading lives under.
13 pub project: String,
14 /// Parsed heading, including body and logbook.
15 pub heading: IssueHeading,
16 /// Absolute path of the project's `issues.org`.
17 pub path: PathBuf,
18 /// File-level tags and `#+TAGS:` groups from the preamble.
19 pub tag_settings: crate::org::TagSettings,
20}
21
22/// Filters for [`crate::catalog::CatalogService::issues_rows`].
23#[derive(Debug, Clone, Default, PartialEq, Eq)]
24pub struct ListQuery {
25 /// Restrict to this project name (case-insensitive).
26 pub project: Option<String>,
27 /// Restrict to this TODO keyword.
28 pub state: Option<String>,
29 /// Keep only TODO or STARTED issues with no open blocker.
30 pub ready: bool,
31 /// Case-insensitive substring over id, title, tags, and properties.
32 pub query: Option<String>,
33 /// Cap the result after sorting.
34 pub limit: Option<usize>,
35 /// Drop this many leading rows after sorting.
36 pub offset: Option<usize>,
37}
38
39/// One list/ready row: the fields a board or JSON client paints.
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41pub struct IssueRow {
42 /// Issue id, `<project>-<suffix>`.
43 pub id: String,
44 /// TODO keyword on the heading.
45 pub state: String,
46 /// Priority cookie as a one-character string.
47 pub priority: String,
48 /// Heading title, without tags.
49 pub title: String,
50 /// Project the heading lives in.
51 pub project: String,
52 /// Ids listed in `:BLOCKED_BY:`.
53 pub blocked_by: Vec<String>,
54 /// Identity holding the issue, when claimed.
55 pub claimed_by: Option<String>,
56 /// Org timestamp of the claim.
57 pub claimed_at: Option<String>,
58 /// `:PARENT:` id, when set.
59 #[serde(default)]
60 pub parent: Option<String>,
61}
62
63/// One issue as a detail card: properties, tags, file range, body, and logbook.
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65pub struct IssueDetail {
66 /// Issue id, `<project>-<suffix>`.
67 pub id: String,
68 /// Project the heading lives in.
69 pub project: String,
70 /// Heading title, without tags.
71 pub title: String,
72 /// TODO keyword on the heading.
73 pub state: String,
74 /// Priority cookie as a one-character string.
75 pub priority: String,
76 /// Property drawer, including planning keys held in the map.
77 pub properties: BTreeMap<String, String>,
78 /// Tags written on the heading itself.
79 pub org_tags: Vec<String>,
80 /// Combined heading tags and `:VISSUE_TAGS:`.
81 pub tags: Vec<String>,
82 /// Ids listed in `:BLOCKED_BY:`.
83 pub blocked_by: Vec<String>,
84 /// `:PARENT:` id, when set.
85 pub parent: Option<String>,
86 /// Identity holding the issue, when claimed.
87 pub claimed_by: Option<String>,
88 /// Org timestamp of the claim.
89 pub claimed_at: Option<String>,
90 /// `path:line_start-line_end` of the heading in its `issues.org`.
91 pub file: String,
92 /// 1-based first line of the heading in the file.
93 pub line_start: usize,
94 /// 1-based last line of the heading in the file.
95 pub line_end: usize,
96 /// Prose under the heading, without the property drawer or logbook.
97 ///
98 /// Carried here so a caller that fetched the detail has what the issue
99 /// asks for, rather than a file path and a line range to go read.
100 #[serde(default)]
101 pub body: String,
102 /// Logbook lines on the heading, newest first.
103 #[serde(default)]
104 pub logbook: Vec<LogbookLine>,
105}
106
107/// One logbook line on a detail card: note, state flip, or raw CLOCK.
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
109pub struct LogbookLine {
110 /// Inactive org timestamp on the line, or empty for a raw CLOCK row.
111 #[serde(default)]
112 pub timestamp: String,
113 /// Previous TODO keyword on a state flip.
114 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub from_state: Option<String>,
116 /// New TODO keyword on a state flip.
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub to_state: Option<String>,
119 /// Folded note text, when the line is a note rather than a state flip.
120 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub note: Option<String>,
122 /// Opaque drawer line preserved verbatim (a `CLOCK:` entry, say).
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub raw: Option<String>,
125}
126
127/// One live claim: who holds the issue and for how long.
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129pub struct ClaimRow {
130 /// Issue id.
131 pub id: String,
132 /// Project the heading lives in.
133 pub project: String,
134 /// TODO keyword on the heading.
135 pub state: String,
136 /// Priority cookie as a one-character string.
137 pub priority: String,
138 /// Identity holding the issue.
139 pub holder: Option<String>,
140 /// Org timestamp of the claim.
141 pub claimed_at: Option<String>,
142 /// Whole days since the claim; `-1` when the stamp does not parse.
143 pub age_days: i64,
144 /// Heading title.
145 pub title: String,
146}
147
148/// A capped, secret-screened slice of a heading's on-disk range.
149#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
150pub struct Excerpt {
151 /// Issue id.
152 pub id: String,
153 /// Path of the `issues.org` the heading lives in.
154 pub file: String,
155 /// 1-based first line of the heading.
156 pub line_start: usize,
157 /// 1-based last line of the heading.
158 pub line_end: usize,
159 /// Excerpt text, or a suppression notice when credential-shaped.
160 pub text: String,
161 /// Whether `text` is a suppression notice rather than the heading.
162 pub suppressed: bool,
163}
164
165/// One search match: the heading plus a short snippet of the hit.
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
167pub struct SearchHit {
168 /// Issue id.
169 pub id: String,
170 /// Project the heading lives in.
171 pub project: String,
172 /// TODO keyword on the heading.
173 pub state: String,
174 /// Priority cookie as a one-character string.
175 pub priority: String,
176 /// Heading title.
177 pub title: String,
178 /// First matching line, capped.
179 pub snippet: String,
180}
181
182/// One dated row: a deadline or scheduled date on an open issue.
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
184pub struct AgendaRow {
185 /// Calendar date as `YYYY-MM-DD`.
186 pub date: String,
187 /// `deadline` or `scheduled`.
188 pub kind: String,
189 /// Days past the date; `0` when it is today or still upcoming.
190 pub overdue_days: i64,
191 /// Issue id.
192 pub id: String,
193 /// Project the heading lives in.
194 pub project: String,
195 /// TODO keyword on the heading.
196 pub state: String,
197 /// Priority cookie as a one-character string.
198 pub priority: String,
199 /// Heading title.
200 pub title: String,
201}
202
203/// A parent/child subtree node, with the issue's own blockers.
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
205pub struct TreeNode {
206 /// Issue id.
207 pub id: String,
208 /// TODO keyword on the heading.
209 pub state: String,
210 /// Heading title.
211 pub title: String,
212 /// Direct children by `:PARENT:`.
213 pub children: Vec<TreeNode>,
214 /// Ids listed in `:BLOCKED_BY:`.
215 pub blocked_by: Vec<String>,
216}
217
218/// One ranked related-issue hit, with the evidence that produced the score.
219#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
220pub struct RelatedHit {
221 /// Issue id.
222 pub id: String,
223 /// Project the heading lives in.
224 pub project: String,
225 /// TODO keyword on the heading.
226 pub state: String,
227 /// Heading title.
228 pub title: String,
229 /// Combined evidence score; higher is a closer match.
230 pub score: f64,
231 /// Named reasons (`blocked_by`, `term:foo`, `org_distance:1`, ...).
232 pub evidence: Vec<String>,
233}
234
235/// One related heading from a walk: children, ancestors, impact, or backlinks.
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
237pub struct WalkHit {
238 /// Issue id.
239 pub id: String,
240 /// Project the heading lives in.
241 pub project: String,
242 /// TODO keyword on the heading.
243 pub state: String,
244 /// Heading title.
245 pub title: String,
246 /// How this heading relates to the walk root (`child`, `ancestor`, ...).
247 pub relation: String,
248}