Skip to main content

codeberg_cli/actions/milestone/
view.rs

1use forgejo_api::structs::{
2    IssueGetMilestonesListQuery, IssueListIssuesQuery, Milestone, StateType,
3};
4use miette::IntoDiagnostic;
5
6use crate::actions::GlobalArgs;
7use crate::render::datetime::render_datetime_regular;
8use crate::render::json::JsonToStdout;
9use crate::render::option::{option_debug_display, option_display};
10use crate::render::spinner::spin_until_ready;
11use crate::render::ui::fuzzy_select_with_key;
12
13use crate::actions::text_manipulation::select_prompt_for;
14use crate::types::api::state_type::ViewStateType;
15use crate::types::context::BergContext;
16use crate::types::git::OwnerRepo;
17use crate::types::output::OutputMode;
18
19use super::display_milestone;
20
21use clap::Parser;
22
23/// View details of selected milestone
24#[derive(Parser, Debug)]
25pub struct ViewMilestonesArgs {
26    /// Select from milestones with the chosen state
27    #[arg(short, long, value_enum, default_value_t = ViewStateType::All)]
28    pub state: ViewStateType,
29}
30
31impl ViewMilestonesArgs {
32    pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
33        let ctx = BergContext::new(self, global_args).await?;
34
35        let OwnerRepo { repo, owner } = ctx.owner_repo()?;
36        let (_, milestones_list) = spin_until_ready(
37            ctx.client
38                .issue_get_milestones_list(
39                    owner.as_str(),
40                    repo.as_str(),
41                    IssueGetMilestonesListQuery::default(),
42                )
43                .send(),
44        )
45        .await
46        .into_diagnostic()?;
47
48        let selected_milestone = fuzzy_select_with_key(
49            &milestones_list,
50            select_prompt_for("milestone"),
51            display_milestone,
52        )?;
53
54        match ctx.global_args.output_mode {
55            OutputMode::Pretty => {
56                present_milestone_overview(&ctx, selected_milestone).await?;
57            }
58            OutputMode::Json => selected_milestone.print_json()?,
59        }
60
61        Ok(())
62    }
63}
64
65async fn present_milestone_overview(
66    ctx: &BergContext<ViewMilestonesArgs>,
67    milestone: &Milestone,
68) -> miette::Result<()> {
69    let OwnerRepo { repo, owner } = ctx.owner_repo()?;
70    let (_, issues_list) = spin_until_ready(
71        ctx.client
72            .issue_list_issues(
73                owner.as_str(),
74                repo.as_str(),
75                IssueListIssuesQuery {
76                    milestones: milestone
77                        .id
78                        .as_ref()
79                        .map(|id| id.to_string())
80                        .or_else(|| milestone.title.clone()),
81                    ..Default::default()
82                },
83            )
84            .send(),
85    )
86    .await
87    .into_diagnostic()?;
88
89    let mut milestone_issues = issues_list
90        .iter()
91        .filter(|&issue| {
92            issue.milestone.as_ref().is_some_and(|issue_milestone| {
93                issue_milestone
94                    .id
95                    .is_some_and(|id| milestone.id == Some(id))
96            })
97        })
98        .map(|issue| {
99            format!(
100                "#{}{}",
101                option_display(&issue.number),
102                // TODO: Adjust
103                if issue.state == Some(StateType::Closed) {
104                    "✓ "
105                } else {
106                    "○ "
107                }
108            )
109        })
110        .collect::<Vec<_>>();
111
112    milestone_issues.sort();
113
114    let mut table = ctx
115        .make_table()
116        .set_header(vec![format!(
117            "Milestone #{}",
118            option_display(&milestone.id)
119        )])
120        .add_row(vec![String::from("Name"), option_display(&milestone.title)])
121        .add_row(vec![
122            String::from("Status"),
123            option_debug_display(&milestone.state),
124        ])
125        .add_row(vec![
126            String::from("Description"),
127            option_display(&milestone.description),
128        ]);
129
130    if !milestone_issues.is_empty() {
131        table = table.add_row(vec![
132            String::from("Related Issues"),
133            milestone_issues.join(", "),
134        ]);
135    }
136
137    table = table
138        .add_row(vec![
139            String::from("Due On"),
140            option_display(&milestone.due_on.as_ref().map(render_datetime_regular)),
141        ])
142        .add_row(vec![
143            String::from("Progress"),
144            format!(
145                "Progress: {} / {} done",
146                option_display(&milestone.closed_issues),
147                milestone.open_issues.unwrap_or_default()
148                    + milestone.closed_issues.unwrap_or_default()
149            ),
150        ]);
151
152    println!("{table}", table = table.show());
153
154    Ok(())
155}