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