codeberg_cli/actions/milestone/
view.rs1use 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#[derive(Parser, Debug)]
25pub struct ViewMilestonesArgs {
26 #[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 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}