codeberg_cli/actions/issue/
view.rs1use crate::render::comment::render_comment;
2use crate::render::datetime::render_datetime_and_info;
3use crate::render::option::option_display;
4use crate::render::spinner::spin_until_ready;
5use crate::render::ui::fuzzy_select_with_key;
6use crate::types::api::state_type::ViewStateType;
7use crate::types::context::BergContext;
8use crate::{actions::GeneralArgs, types::git::OwnerRepo};
9use anyhow::Context;
10use forgejo_api::structs::{
11 Issue, IssueGetCommentsQuery, IssueListIssuesQuery, IssueListIssuesQueryState,
12};
13
14use crate::actions::text_manipulation::select_prompt_for;
15
16use super::display_issue;
17
18use clap::Parser;
19
20#[derive(Parser, Debug)]
22pub struct ViewIssueArgs {
23 #[arg(short, long, value_enum, default_value_t = ViewStateType::All)]
25 pub state: ViewStateType,
26
27 #[arg(short, long)]
29 pub comments: bool,
30}
31
32impl ViewIssueArgs {
33 pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
34 let _ = general_args;
35 let ctx = BergContext::new(self, general_args).await?;
36 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
37 let state = match ctx.args.state {
38 ViewStateType::Closed => IssueListIssuesQueryState::Closed,
39 ViewStateType::Open => IssueListIssuesQueryState::Open,
40 ViewStateType::All => IssueListIssuesQueryState::All,
41 };
42 let issues_list = spin_until_ready(ctx.client.issue_list_issues(
43 owner.as_str(),
44 repo.as_str(),
45 IssueListIssuesQuery {
46 state: Some(state),
47 ..Default::default()
48 },
49 ))
50 .await?;
51
52 let selected_issue =
53 fuzzy_select_with_key(&issues_list, select_prompt_for("issue"), display_issue)?;
54
55 if ctx.args.comments {
56 spin_until_ready(present_issue_comments(&ctx, selected_issue)).await?;
57 } else {
58 present_issue_overview(&ctx, selected_issue);
59 }
60
61 Ok(())
62 }
63}
64
65fn present_issue_overview(ctx: &BergContext<ViewIssueArgs>, issue: &Issue) {
66 let days_passed_since_creation =
67 option_display(&issue.created_at.as_ref().map(render_datetime_and_info));
68
69 let mut table = ctx.make_table();
70
71 table
72 .set_header(vec![option_display(
73 &issue.id.as_ref().map(|id| format!("Issue #{}", id)),
74 )])
75 .add_row(vec![String::from("Title"), option_display(&issue.title)])
76 .add_row(vec![String::from("Created"), days_passed_since_creation])
77 .add_row(vec![
78 String::from("Labels"),
79 option_display(&issue.labels.as_ref().map(|labels| {
80 labels
81 .iter()
82 .map(|label| option_display(&label.name))
83 .collect::<Vec<_>>()
84 .join(", ")
85 })),
86 ])
87 .add_row(vec![
88 String::from("Description"),
89 option_display(&issue.body),
90 ]);
91
92 println!("{table}", table = table.show());
93}
94
95async fn present_issue_comments(
96 ctx: &BergContext<ViewIssueArgs>,
97 issue: &Issue,
98) -> anyhow::Result<()> {
99 let id = issue.id.context("Issue has no Id")?;
100 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
101 let (header, comments) = {
102 let comments_list = ctx
103 .client
104 .issue_get_comments(
105 owner.as_str(),
106 repo.as_str(),
107 id as u64,
108 IssueGetCommentsQuery::default(),
109 )
110 .await?;
111 let header = format!(
112 "Issue #{} {}",
113 id,
114 if comments_list.is_empty() {
115 "(no comments)"
116 } else {
117 "comments"
118 }
119 );
120 (header, comments_list)
121 };
122
123 let mut table = ctx.make_table();
124
125 table
126 .add_row(vec![header])
127 .add_rows(comments.into_iter().filter_map(|comment| {
128 let username = comment.user.as_ref()?.login.as_ref()?.as_str();
129 let creation_time = comment.created_at.as_ref()?;
130 let comment = comment.body.as_ref()?;
131 let comment = render_comment(&ctx.config, username, creation_time, comment);
132 Some(vec![comment])
133 }));
134
135 println!("{table}", table = table.show());
136
137 Ok(())
138}