codeberg_cli/actions/issue/
view.rs1use crate::render::comment::render_comment;
2use crate::render::datetime::render_datetime_and_info;
3use crate::render::json::JsonToStdout;
4use crate::render::option::{option_debug_display, option_display};
5use crate::render::spinner::spin_until_ready;
6use crate::render::ui::fuzzy_select_with_key;
7use crate::types::api::state_type::ViewStateType;
8use crate::types::context::BergContext;
9use crate::types::output::OutputMode;
10use crate::{actions::GlobalArgs, types::git::OwnerRepo};
11use forgejo_api::structs::{
12 Issue, IssueGetCommentsQuery, IssueListIssuesQuery, IssueListIssuesQueryState,
13};
14use miette::{Context, IntoDiagnostic};
15
16use crate::actions::text_manipulation::select_prompt_for;
17
18use super::display_issue;
19
20use clap::Parser;
21
22#[derive(Parser, Debug)]
24pub struct ViewIssueArgs {
25 pub index: Option<i64>,
27
28 #[arg(short, long, value_enum, default_value_t = ViewStateType::All)]
30 pub state: ViewStateType,
31
32 #[arg(short, long)]
34 pub comments: bool,
35}
36
37impl ViewIssueArgs {
38 pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
39 let ctx = BergContext::new(self, global_args).await?;
40 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
41 let state = match ctx.args.state {
42 ViewStateType::Closed => IssueListIssuesQueryState::Closed,
43 ViewStateType::Open => IssueListIssuesQueryState::Open,
44 ViewStateType::All => IssueListIssuesQueryState::All,
45 };
46 let selected_issue = if let Some(index) = ctx.args.index {
47 ctx.client
48 .issue_get_issue(owner.as_str(), repo.as_str(), index)
49 .await
50 .into_diagnostic()?
51 } else {
52 if ctx.global_args.non_interactive {
53 miette::bail!("non-interactive mode enabled. You have to specify an issue #number");
54 };
55 let (_, issues_list) = spin_until_ready(
56 ctx.client
57 .issue_list_issues(
58 owner.as_str(),
59 repo.as_str(),
60 IssueListIssuesQuery {
61 state: Some(state),
62 ..Default::default()
63 },
64 )
65 .send(),
66 )
67 .await
68 .into_diagnostic()?;
69
70 fuzzy_select_with_key(&issues_list, select_prompt_for("issue"), display_issue)
71 .cloned()?
72 };
73
74 match ctx.global_args.output_mode {
75 OutputMode::Pretty => {
76 if ctx.args.comments {
77 spin_until_ready(present_issue_comments(&ctx, &selected_issue)).await?;
78 } else {
79 present_issue_overview(&ctx, &selected_issue);
80 }
81 }
82 OutputMode::Json => {
83 if ctx.args.comments {
84 let index = selected_issue
85 .number
86 .context("Selected issue has to have a valid issue number")?;
87 let (_, comments) = ctx
88 .client
89 .issue_get_comments(
90 owner.as_str(),
91 repo.as_str(),
92 index,
93 IssueGetCommentsQuery::default(),
94 )
95 .send()
96 .await
97 .into_diagnostic()?;
98 comments.print_json()?;
99 } else {
100 selected_issue.print_json()?;
101 }
102 }
103 }
104
105 Ok(())
106 }
107}
108
109fn present_issue_overview(ctx: &BergContext<ViewIssueArgs>, issue: &Issue) {
110 let days_passed_since_creation =
111 option_display(&issue.created_at.as_ref().map(render_datetime_and_info));
112
113 let table = ctx
114 .make_table()
115 .set_header(vec![option_display(
116 &issue.id.as_ref().map(|id| format!("Issue #{}", id)),
117 )])
118 .add_row(vec![String::from("Title"), option_display(&issue.title)])
119 .add_row(vec![String::from("Created"), days_passed_since_creation])
120 .add_row(vec![
121 String::from("Labels"),
122 option_display(&issue.labels.as_ref().map(|labels| {
123 labels
124 .iter()
125 .map(|label| option_display(&label.name))
126 .collect::<Vec<_>>()
127 .join(", ")
128 })),
129 ])
130 .add_row(vec![
131 String::from("State"),
132 option_debug_display(&issue.state),
133 ])
134 .add_row(vec![
135 String::from("Description"),
136 option_display(&issue.body),
137 ]);
138
139 println!("{table}", table = table.show());
140}
141
142async fn present_issue_comments(
143 ctx: &BergContext<ViewIssueArgs>,
144 issue: &Issue,
145) -> miette::Result<()> {
146 let index = issue.number.context("Issue has no issue number")?;
147 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
148 let (header, comments) = {
149 let (_, comments_list) = ctx
150 .client
151 .issue_get_comments(
152 owner.as_str(),
153 repo.as_str(),
154 index,
155 IssueGetCommentsQuery::default(),
156 )
157 .send()
158 .await
159 .into_diagnostic()?;
160 let header = format!(
161 "Issue #{} {}",
162 index,
163 if comments_list.is_empty() {
164 "(no comments)"
165 } else {
166 "comments"
167 }
168 );
169 (header, comments_list)
170 };
171
172 let table = ctx
173 .make_table()
174 .add_row(vec![header])
175 .add_rows(comments.into_iter().filter_map(|comment| {
176 let username = comment.user.as_ref()?.login.as_ref()?.as_str();
177 let creation_time = comment.created_at.as_ref()?;
178 let comment = comment.body.as_ref()?;
179 let comment = render_comment(username, creation_time, comment);
180 Some(vec![comment])
181 }));
182
183 println!("{table}", table = table.show());
184
185 Ok(())
186}