codeberg_cli/actions/pull_request/
view.rs

1use super::display_pull_request;
2use crate::actions::{GeneralArgs, text_manipulation::select_prompt_for};
3use crate::render::comment::render_comment;
4use crate::render::datetime::render_datetime_and_info;
5use crate::render::json::JsonToStdout;
6use crate::render::option::option_display;
7use crate::render::spinner::spin_until_ready;
8use crate::render::ui::fuzzy_select_with_key;
9use crate::types::api::state_type::ViewStateType;
10use crate::types::context::BergContext;
11use crate::types::git::OwnerRepo;
12use crate::types::output::OutputMode;
13use anyhow::Context;
14use clap::Parser;
15use forgejo_api::structs::{
16    IssueGetCommentsQuery, PullRequest, RepoListPullRequestsQuery, RepoListPullRequestsQueryState,
17};
18use itertools::Itertools;
19
20#[derive(Parser, Debug)]
21#[command(about = "View details of a selected pull request")]
22pub struct ViewPullRequestsArgs {
23    /// Select from pull requests with the chosen state
24    #[arg(
25        short,
26        long,
27        value_enum,
28        default_value_t = ViewStateType::All,
29    )]
30    pub state: ViewStateType,
31
32    /// Disabled: display issue summary | Enabled: display issue comment history
33    #[arg(short, long)]
34    pub comments: bool,
35}
36
37impl ViewPullRequestsArgs {
38    pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
39        let ctx = BergContext::new(self, general_args).await?;
40
41        let OwnerRepo { repo, owner } = ctx.owner_repo()?;
42        let state = match ctx.args.state {
43            ViewStateType::Closed => RepoListPullRequestsQueryState::Closed,
44            ViewStateType::Open => RepoListPullRequestsQueryState::Open,
45            ViewStateType::All => RepoListPullRequestsQueryState::All,
46        };
47        let (_, pull_requests_list) = spin_until_ready(ctx.client.repo_list_pull_requests(
48            owner.as_str(),
49            repo.as_str(),
50            RepoListPullRequestsQuery {
51                state: Some(state),
52                ..Default::default()
53            },
54        ))
55        .await?;
56
57        let pull_request = fuzzy_select_with_key(
58            &pull_requests_list,
59            select_prompt_for("pull request"),
60            display_pull_request,
61        )?;
62
63        match general_args.output_mode {
64            OutputMode::Pretty => {
65                if ctx.args.comments {
66                    spin_until_ready(present_pull_request_comments(&ctx, pull_request)).await?;
67                } else {
68                    present_pull_request_overview(&ctx, pull_request);
69                }
70            }
71            OutputMode::Json => pull_request.print_json()?,
72        }
73
74        Ok(())
75    }
76}
77
78fn present_pull_request_overview(
79    ctx: &BergContext<ViewPullRequestsArgs>,
80    pull_request: &PullRequest,
81) {
82    let rendered_datetime = pull_request
83        .created_at
84        .as_ref()
85        .map(render_datetime_and_info)
86        .unwrap_or_else(|| String::from("?"));
87
88    let mut table = ctx.make_table();
89
90    table
91        .set_header(vec![format!(
92            "Pull Request #{}",
93            option_display(&pull_request.id)
94        )])
95        .add_row(vec![
96            String::from("Title"),
97            option_display(&pull_request.title),
98        ])
99        .add_row(vec![String::from("Created"), rendered_datetime])
100        .add_row(vec![
101            String::from("Labels"),
102            pull_request
103                .labels
104                .iter()
105                .flatten()
106                .map(|label| option_display(&label.name))
107                .join(", "),
108        ])
109        .add_row(vec![
110            String::from("Description"),
111            option_display(&pull_request.body),
112        ]);
113
114    println!("{table}", table = table.show());
115}
116
117async fn present_pull_request_comments(
118    ctx: &BergContext<ViewPullRequestsArgs>,
119    pull_request: &PullRequest,
120) -> anyhow::Result<()> {
121    let OwnerRepo { repo, owner } = ctx.owner_repo()?;
122    let nr = pull_request.id.context("Selected pull request has no ID")?;
123    let (_, comments) = ctx
124        .client
125        .issue_get_comments(
126            owner.as_str(),
127            repo.as_str(),
128            nr as u64,
129            IssueGetCommentsQuery::default(),
130        )
131        .await?;
132    let header = format!(
133        "Pull Request #{} {}",
134        option_display(&pull_request.id),
135        if comments.is_empty() {
136            "(no comments)"
137        } else {
138            "comments"
139        }
140    );
141
142    let mut table = ctx.make_table();
143
144    table
145        .set_header(vec![header])
146        .add_rows(comments.into_iter().filter_map(|comment| {
147            let username = comment.user.as_ref()?.login.as_ref()?.as_str();
148            let creation_time = comment.created_at.as_ref()?;
149            let comment = comment.body.as_ref()?;
150            let comment = render_comment(&ctx.config, username, creation_time, comment);
151            Some(vec![comment])
152        }));
153
154    println!("{table}", table = table.show());
155
156    Ok(())
157}