codeberg_cli/actions/issue/
view.rs

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