codeberg_cli/actions/milestone/
view.rs1use forgejo_api::structs::{
2 IssueGetMilestonesListQuery, IssueListIssuesQuery, Milestone, StateType,
3};
4
5use crate::actions::GeneralArgs;
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, general_args: GeneralArgs) -> anyhow::Result<()> {
32 let ctx = BergContext::new(self, general_args).await?;
33
34 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
35 let (_, milestones_list) = spin_until_ready(ctx.client.issue_get_milestones_list(
36 owner.as_str(),
37 repo.as_str(),
38 IssueGetMilestonesListQuery::default(),
39 ))
40 .await?;
41
42 let selected_milestone = fuzzy_select_with_key(
43 &milestones_list,
44 select_prompt_for("milestone"),
45 display_milestone,
46 )?;
47
48 match general_args.output_mode {
49 OutputMode::Pretty => {
50 present_milestone_overview(&ctx, selected_milestone).await?;
51 }
52 OutputMode::Json => selected_milestone.print_json()?,
53 }
54
55 Ok(())
56 }
57}
58
59async fn present_milestone_overview(
60 ctx: &BergContext<ViewMilestonesArgs>,
61 milestone: &Milestone,
62) -> anyhow::Result<()> {
63 let OwnerRepo { repo, owner } = ctx.owner_repo()?;
64 let (_, issues_list) = spin_until_ready(
65 ctx.client.issue_list_issues(
66 owner.as_str(),
67 repo.as_str(),
68 IssueListIssuesQuery {
69 milestones: milestone
70 .id
71 .as_ref()
72 .map(|id| id.to_string())
73 .or_else(|| milestone.title.clone()),
74 ..Default::default()
75 },
76 ),
77 )
78 .await?;
79
80 let mut milestone_issues = issues_list
81 .iter()
82 .filter(|&issue| {
83 issue.milestone.as_ref().is_some_and(|issue_milestone| {
84 issue_milestone
85 .id
86 .is_some_and(|id| milestone.id == Some(id))
87 })
88 })
89 .map(|issue| {
90 format!(
91 "#{}{}",
92 option_display(&issue.number),
93 if issue.state == Some(StateType::Closed) {
95 "✓ "
96 } else {
97 "○ "
98 }
99 )
100 })
101 .collect::<Vec<_>>();
102
103 milestone_issues.sort();
104
105 let mut table = ctx.make_table();
106
107 table
108 .set_header(vec![format!(
109 "Milestone #{}",
110 option_display(&milestone.id)
111 )])
112 .add_row(vec![String::from("Name"), option_display(&milestone.title)])
113 .add_row(vec![
114 String::from("Status"),
115 option_debug_display(&milestone.state),
116 ])
117 .add_row(vec![
118 String::from("Description"),
119 option_display(&milestone.description),
120 ]);
121
122 if !milestone_issues.is_empty() {
123 table.add_row(vec![
124 String::from("Related Issues"),
125 milestone_issues.join(", "),
126 ]);
127 }
128
129 table
130 .add_row(vec![
131 String::from("Due On"),
132 option_display(&milestone.due_on.as_ref().map(render_datetime_regular)),
133 ])
134 .add_row(vec![
135 String::from("Progress"),
136 format!(
137 "Progress: {} / {} done",
138 option_display(&milestone.closed_issues),
139 milestone.open_issues.unwrap_or_default()
140 + milestone.closed_issues.unwrap_or_default()
141 ),
142 ]);
143
144 println!("{table}", table = table.show());
145
146 Ok(())
147}