codeberg_cli/actions/pull_request/
view.rs

1use super::display_pull_request;
2use crate::actions::{GlobalArgs, 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, global_args: GlobalArgs) -> anyhow::Result<()> {
39        let ctx = BergContext::new(self, global_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(
48            ctx.client
49                .repo_list_pull_requests(
50                    owner.as_str(),
51                    repo.as_str(),
52                    RepoListPullRequestsQuery {
53                        state: Some(state),
54                        ..Default::default()
55                    },
56                )
57                .send(),
58        )
59        .await?;
60
61        let pull_request = fuzzy_select_with_key(
62            &pull_requests_list,
63            select_prompt_for("pull request"),
64            display_pull_request,
65        )?;
66
67        match ctx.global_args.output_mode {
68            OutputMode::Pretty => {
69                if ctx.args.comments {
70                    spin_until_ready(present_pull_request_comments(&ctx, pull_request)).await?;
71                } else {
72                    present_pull_request_overview(&ctx, pull_request);
73                }
74            }
75            OutputMode::Json => pull_request.print_json()?,
76        }
77
78        Ok(())
79    }
80}
81
82fn present_pull_request_overview(
83    ctx: &BergContext<ViewPullRequestsArgs>,
84    pull_request: &PullRequest,
85) {
86    let rendered_datetime = pull_request
87        .created_at
88        .as_ref()
89        .map(render_datetime_and_info)
90        .unwrap_or_else(|| String::from("?"));
91
92    let mut table = ctx.make_table();
93
94    table
95        .set_header(vec![format!(
96            "Pull Request #{}",
97            option_display(&pull_request.id)
98        )])
99        .add_row(vec![
100            String::from("Title"),
101            option_display(&pull_request.title),
102        ])
103        .add_row(vec![String::from("Created"), rendered_datetime])
104        .add_row(vec![
105            String::from("Labels"),
106            pull_request
107                .labels
108                .iter()
109                .flatten()
110                .map(|label| option_display(&label.name))
111                .join(", "),
112        ])
113        .add_row(vec![
114            String::from("Description"),
115            option_display(&pull_request.body),
116        ]);
117
118    println!("{table}", table = table.show());
119}
120
121async fn present_pull_request_comments(
122    ctx: &BergContext<ViewPullRequestsArgs>,
123    pull_request: &PullRequest,
124) -> anyhow::Result<()> {
125    let OwnerRepo { repo, owner } = ctx.owner_repo()?;
126    let nr = pull_request.id.context("Selected pull request has no ID")?;
127    let (_, comments) = ctx
128        .client
129        .issue_get_comments(
130            owner.as_str(),
131            repo.as_str(),
132            nr,
133            IssueGetCommentsQuery::default(),
134        )
135        .await?;
136    let header = format!(
137        "Pull Request #{} {}",
138        option_display(&pull_request.id),
139        if comments.is_empty() {
140            "(no comments)"
141        } else {
142            "comments"
143        }
144    );
145
146    let mut table = ctx.make_table();
147
148    table
149        .set_header(vec![header])
150        .add_rows(comments.into_iter().filter_map(|comment| {
151            let username = comment.user.as_ref()?.login.as_ref()?.as_str();
152            let creation_time = comment.created_at.as_ref()?;
153            let comment = comment.body.as_ref()?;
154            let comment = render_comment(&ctx.config, username, creation_time, comment);
155            Some(vec![comment])
156        }));
157
158    println!("{table}", table = table.show());
159
160    Ok(())
161}