feat(bd-2g50): fill data gaps in issue detail view

Add references_full, user_notes_count, merge_requests_count computed
fields to show issue. Add closed_at and confidential columns via
migration 023.

Closes: bd-2g50
This commit is contained in:
teernisse
2026-02-12 11:34:10 -05:00
parent e26816333f
commit b29c382583
3 changed files with 67 additions and 11 deletions

View File

@@ -75,12 +75,17 @@ pub struct IssueDetail {
pub author_username: String,
pub created_at: i64,
pub updated_at: i64,
pub closed_at: Option<String>,
pub confidential: bool,
pub web_url: Option<String>,
pub project_path: String,
pub references_full: String,
pub labels: Vec<String>,
pub assignees: Vec<String>,
pub due_date: Option<String>,
pub milestone: Option<String>,
pub user_notes_count: i64,
pub merge_requests_count: usize,
pub closing_merge_requests: Vec<ClosingMrRef>,
pub discussions: Vec<DiscussionDetail>,
pub status_name: Option<String>,
@@ -122,6 +127,9 @@ pub fn run_show_issue(
let discussions = get_issue_discussions(&conn, issue.id)?;
let references_full = format!("{}#{}", issue.project_path, issue.iid);
let merge_requests_count = closing_mrs.len();
Ok(IssueDetail {
id: issue.id,
iid: issue.iid,
@@ -131,12 +139,17 @@ pub fn run_show_issue(
author_username: issue.author_username,
created_at: issue.created_at,
updated_at: issue.updated_at,
closed_at: issue.closed_at,
confidential: issue.confidential,
web_url: issue.web_url,
project_path: issue.project_path,
references_full,
labels,
assignees,
due_date: issue.due_date,
milestone: issue.milestone_title,
user_notes_count: issue.user_notes_count,
merge_requests_count,
closing_merge_requests: closing_mrs,
discussions,
status_name: issue.status_name,
@@ -156,10 +169,13 @@ struct IssueRow {
author_username: String,
created_at: i64,
updated_at: i64,
closed_at: Option<String>,
confidential: bool,
web_url: Option<String>,
project_path: String,
due_date: Option<String>,
milestone_title: Option<String>,
user_notes_count: i64,
status_name: Option<String>,
status_category: Option<String>,
status_color: Option<String>,
@@ -173,8 +189,12 @@ fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Resu
let project_id = resolve_project(conn, project)?;
(
"SELECT i.id, i.iid, i.title, i.description, i.state, i.author_username,
i.created_at, i.updated_at, i.web_url, p.path_with_namespace,
i.created_at, i.updated_at, i.closed_at, i.confidential,
i.web_url, p.path_with_namespace,
i.due_date, i.milestone_title,
(SELECT COUNT(*) FROM notes n
JOIN discussions d ON n.discussion_id = d.id
WHERE d.noteable_type = 'Issue' AND d.noteable_id = i.id AND n.is_system = 0) AS user_notes_count,
i.status_name, i.status_category, i.status_color,
i.status_icon_name, i.status_synced_at
FROM issues i
@@ -185,8 +205,12 @@ fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Resu
}
None => (
"SELECT i.id, i.iid, i.title, i.description, i.state, i.author_username,
i.created_at, i.updated_at, i.web_url, p.path_with_namespace,
i.created_at, i.updated_at, i.closed_at, i.confidential,
i.web_url, p.path_with_namespace,
i.due_date, i.milestone_title,
(SELECT COUNT(*) FROM notes n
JOIN discussions d ON n.discussion_id = d.id
WHERE d.noteable_type = 'Issue' AND d.noteable_id = i.id AND n.is_system = 0) AS user_notes_count,
i.status_name, i.status_category, i.status_color,
i.status_icon_name, i.status_synced_at
FROM issues i
@@ -201,6 +225,7 @@ fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Resu
let mut stmt = conn.prepare(sql)?;
let issues: Vec<IssueRow> = stmt
.query_map(param_refs.as_slice(), |row| {
let confidential_val: i64 = row.get(9)?;
Ok(IssueRow {
id: row.get(0)?,
iid: row.get(1)?,
@@ -210,15 +235,18 @@ fn find_issue(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Resu
author_username: row.get(5)?,
created_at: row.get(6)?,
updated_at: row.get(7)?,
web_url: row.get(8)?,
project_path: row.get(9)?,
due_date: row.get(10)?,
milestone_title: row.get(11)?,
status_name: row.get(12)?,
status_category: row.get(13)?,
status_color: row.get(14)?,
status_icon_name: row.get(15)?,
status_synced_at: row.get(16)?,
closed_at: row.get(8)?,
confidential: confidential_val != 0,
web_url: row.get(10)?,
project_path: row.get(11)?,
due_date: row.get(12)?,
milestone_title: row.get(13)?,
user_notes_count: row.get(14)?,
status_name: row.get(15)?,
status_category: row.get(16)?,
status_color: row.get(17)?,
status_icon_name: row.get(18)?,
status_synced_at: row.get(19)?,
})
})?
.collect::<std::result::Result<Vec<_>, _>>()?;
@@ -618,6 +646,7 @@ pub fn print_show_issue(issue: &IssueDetail) {
println!("{}", "".repeat(header.len().min(80)));
println!();
println!("Ref: {}", style(&issue.references_full).dim());
println!("Project: {}", style(&issue.project_path).cyan());
let state_styled = if issue.state == "opened" {
@@ -627,6 +656,10 @@ pub fn print_show_issue(issue: &IssueDetail) {
};
println!("State: {}", state_styled);
if issue.confidential {
println!(" {}", style("CONFIDENTIAL").red().bold());
}
if let Some(status) = &issue.status_name {
println!(
"Status: {}",
@@ -658,6 +691,10 @@ pub fn print_show_issue(issue: &IssueDetail) {
println!("Created: {}", format_date(issue.created_at));
println!("Updated: {}", format_date(issue.updated_at));
if let Some(closed_at) = &issue.closed_at {
println!("Closed: {}", closed_at);
}
if let Some(due) = &issue.due_date {
println!("Due: {}", due);
}
@@ -931,12 +968,17 @@ pub struct IssueDetailJson {
pub author_username: String,
pub created_at: String,
pub updated_at: String,
pub closed_at: Option<String>,
pub confidential: bool,
pub web_url: Option<String>,
pub project_path: String,
pub references_full: String,
pub labels: Vec<String>,
pub assignees: Vec<String>,
pub due_date: Option<String>,
pub milestone: Option<String>,
pub user_notes_count: i64,
pub merge_requests_count: usize,
pub closing_merge_requests: Vec<ClosingMrRefJson>,
pub discussions: Vec<DiscussionDetailJson>,
pub status_name: Option<String>,
@@ -980,12 +1022,17 @@ impl From<&IssueDetail> for IssueDetailJson {
author_username: issue.author_username.clone(),
created_at: ms_to_iso(issue.created_at),
updated_at: ms_to_iso(issue.updated_at),
closed_at: issue.closed_at.clone(),
confidential: issue.confidential,
web_url: issue.web_url.clone(),
project_path: issue.project_path.clone(),
references_full: issue.references_full.clone(),
labels: issue.labels.clone(),
assignees: issue.assignees.clone(),
due_date: issue.due_date.clone(),
milestone: issue.milestone.clone(),
user_notes_count: issue.user_notes_count,
merge_requests_count: issue.merge_requests_count,
closing_merge_requests: issue
.closing_merge_requests
.iter()

View File

@@ -69,6 +69,10 @@ const MIGRATIONS: &[(&str, &str)] = &[
"021",
include_str!("../../migrations/021_work_item_status.sql"),
),
(
"023",
include_str!("../../migrations/023_issue_detail_fields.sql"),
),
];
pub fn create_connection(db_path: &Path) -> Result<Connection> {