codeberg_cli/actions/milestone/
edit.rs1use crate::actions::GeneralArgs;
2use crate::render::spinner::spin_until_ready;
3use crate::render::ui::fuzzy_select_with_key;
4use crate::render::ui::multi_fuzzy_select_with_key;
5use crate::types::context::BergContext;
6use crate::types::git::OwnerRepo;
7use anyhow::Context;
8use forgejo_api::structs::EditMilestoneOption;
9use forgejo_api::structs::IssueGetMilestonesListQuery;
10use forgejo_api::structs::Milestone;
11use forgejo_api::structs::StateType;
12use strum::Display;
13use strum::VariantArray;
14
15use crate::actions::text_manipulation::input_prompt_for;
16use crate::actions::text_manipulation::select_prompt_for;
17
18use super::display_milestone;
19
20use clap::Parser;
21
22#[derive(Parser, Debug)]
24pub struct EditMilestoneArgs {}
25
26#[derive(Display, PartialEq, Eq, VariantArray)]
27enum EditableFields {
28 Title,
29 State,
30 Description,
31}
32
33impl EditMilestoneArgs {
34 pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
35 let _ = general_args;
36 let ctx = BergContext::new(self, general_args).await?;
37
38 let OwnerRepo { owner, repo } = ctx.owner_repo()?;
39 let milestone = select_milestone(&ctx).await?;
40 let milestone_id_or_name = milestone
41 .id
42 .as_ref()
43 .map(|id| id.to_string())
44 .or_else(|| milestone.title.clone())
45 .context("Selected milestone doesn't have an ID or Name")?;
46
47 let options = create_options(&ctx, &milestone).await?;
48
49 let updated_milestone = ctx
50 .client
51 .issue_edit_milestone(
52 owner.as_str(),
53 repo.as_str(),
54 milestone_id_or_name.as_str(),
55 options,
56 )
57 .await?;
58
59 tracing::debug!("{updated_milestone:?}");
60
61 Ok(())
62 }
63}
64
65async fn select_milestone(ctx: &BergContext<EditMilestoneArgs>) -> anyhow::Result<Milestone> {
66 let OwnerRepo { owner, repo } = ctx.owner_repo()?;
67 let milestones_list = spin_until_ready(ctx.client.issue_get_milestones_list(
68 owner.as_str(),
69 repo.as_str(),
70 IssueGetMilestonesListQuery::default(),
71 ))
72 .await?;
73
74 if milestones_list.is_empty() {
75 anyhow::bail!("No milestones found in this repository");
76 }
77
78 fuzzy_select_with_key(
79 &milestones_list,
80 select_prompt_for("milestone"),
81 display_milestone,
82 )
83 .cloned()
84}
85
86async fn create_options(
87 ctx: &BergContext<EditMilestoneArgs>,
88 milestone: &Milestone,
89) -> anyhow::Result<EditMilestoneOption> {
90 let selected_update_fields = multi_fuzzy_select_with_key(
91 EditableFields::VARIANTS,
92 select_prompt_for("options"),
93 |_| false,
94 |f| f.to_string(),
95 )?;
96
97 let mut options = EditMilestoneOption {
98 description: None,
99 due_on: None,
100 state: None,
101 title: None,
102 };
103
104 if selected_update_fields.contains(&&EditableFields::Title) {
105 let current_title = milestone.title.as_ref().cloned();
106 options
107 .title
108 .replace(milestone_title(ctx, current_title).await?);
109 }
110 if selected_update_fields.contains(&&EditableFields::State) {
111 let current_state = milestone.state.as_ref().cloned();
112 options
113 .state
114 .replace(milestone_state(ctx, current_state).await?);
115 }
116 if selected_update_fields.contains(&&EditableFields::Description) {
117 let current_description = milestone.description.as_ref().cloned();
118 options
119 .description
120 .replace(milestone_description(ctx, current_description).await?);
121 }
122
123 Ok(options)
124}
125
126async fn milestone_title(
127 _ctx: &BergContext<EditMilestoneArgs>,
128 current_title: Option<String>,
129) -> anyhow::Result<String> {
130 inquire::Text::new(input_prompt_for("Choose a new milestone title").as_str())
131 .with_default(
132 current_title
133 .as_deref()
134 .unwrap_or("Enter a milestone title"),
135 )
136 .prompt()
137 .map_err(anyhow::Error::from)
138}
139
140async fn milestone_state(
141 _ctx: &BergContext<EditMilestoneArgs>,
142 _current_state: Option<StateType>,
143) -> anyhow::Result<String> {
144 let selected_state = fuzzy_select_with_key(
145 &[StateType::Open, StateType::Closed],
146 select_prompt_for("states"),
147 |f| format!("{f:?}"),
148 )
149 .cloned()?;
150 Ok(format!("{selected_state:?}"))
151}
152
153async fn milestone_description(
154 ctx: &BergContext<EditMilestoneArgs>,
155 current_description: Option<String>,
156) -> anyhow::Result<String> {
157 ctx.editor_for(
158 "a description",
159 current_description
160 .as_deref()
161 .unwrap_or("Enter a milestone description"),
162 )
163}