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