codeberg_cli/actions/milestone/
view.rs

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