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