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