codeberg_cli/actions/issue/
view.rs

1use 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/// View details of selected issue
23#[derive(Parser, Debug)]
24pub struct ViewIssueArgs {
25    /// Select from issues with the chosen state
26    #[arg(short, long, value_enum, default_value_t = ViewStateType::All)]
27    pub state: ViewStateType,
28
29    /// Disabled: display issue summary | Enabled: display issue comment history
30    #[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}