codeberg_cli/actions/milestone/
edit.rs1use crate::actions::GlobalArgs;
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, global_args: GlobalArgs) -> anyhow::Result<()> {
37 let ctx = BergContext::new(self, global_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 .context("Selected milestone doesn't have an ID")?;
44
45 let options = create_options(&ctx, &milestone).await?;
46
47 let updated_milestone = ctx
48 .client
49 .issue_edit_milestone(owner.as_str(), repo.as_str(), milestone_id_or_name, options)
50 .await?;
51
52 match ctx.global_args.output_mode {
53 OutputMode::Pretty => {
54 tracing::debug!("{updated_milestone:?}");
55 }
56 OutputMode::Json => updated_milestone.print_json()?,
57 }
58
59 Ok(())
60 }
61}
62
63async fn select_milestone(ctx: &BergContext<EditMilestoneArgs>) -> anyhow::Result<Milestone> {
64 let OwnerRepo { owner, repo } = ctx.owner_repo()?;
65 let (_, milestones_list) = spin_until_ready(
66 ctx.client
67 .issue_get_milestones_list(
68 owner.as_str(),
69 repo.as_str(),
70 IssueGetMilestonesListQuery::default(),
71 )
72 .send(),
73 )
74 .await?;
75
76 if milestones_list.is_empty() {
77 anyhow::bail!("No milestones found in this repository");
78 }
79
80 fuzzy_select_with_key(
81 &milestones_list,
82 select_prompt_for("milestone"),
83 display_milestone,
84 )
85 .cloned()
86}
87
88async fn create_options(
89 ctx: &BergContext<EditMilestoneArgs>,
90 milestone: &Milestone,
91) -> anyhow::Result<EditMilestoneOption> {
92 let selected_update_fields = multi_fuzzy_select_with_key(
93 EditableFields::VARIANTS,
94 select_prompt_for("options"),
95 |_| false,
96 |f| f.to_string(),
97 )?;
98
99 let mut options = EditMilestoneOption {
100 description: None,
101 due_on: None,
102 state: None,
103 title: None,
104 };
105
106 if selected_update_fields.contains(&&EditableFields::Title) {
107 let current_title = milestone.title.as_ref().cloned();
108 options
109 .title
110 .replace(milestone_title(ctx, current_title).await?);
111 }
112 if selected_update_fields.contains(&&EditableFields::State) {
113 let current_state = milestone.state.as_ref().cloned();
114 options
115 .state
116 .replace(milestone_state(ctx, current_state).await?);
117 }
118 if selected_update_fields.contains(&&EditableFields::Description) {
119 let current_description = milestone.description.as_ref().cloned();
120 options
121 .description
122 .replace(milestone_description(ctx, current_description).await?);
123 }
124
125 Ok(options)
126}
127
128async fn milestone_title(
129 _ctx: &BergContext<EditMilestoneArgs>,
130 current_title: Option<String>,
131) -> anyhow::Result<String> {
132 inquire::Text::new(input_prompt_for("Choose a new milestone title").as_str())
133 .with_default(
134 current_title
135 .as_deref()
136 .unwrap_or("Enter a milestone title"),
137 )
138 .prompt()
139 .map_err(anyhow::Error::from)
140}
141
142async fn milestone_state(
143 _ctx: &BergContext<EditMilestoneArgs>,
144 _current_state: Option<StateType>,
145) -> anyhow::Result<String> {
146 let selected_state = fuzzy_select_with_key(
147 &[StateType::Open, StateType::Closed],
148 select_prompt_for("states"),
149 |f| format!("{f:?}"),
150 )
151 .cloned()?;
152 Ok(format!("{selected_state:?}"))
153}
154
155async fn milestone_description(
156 ctx: &BergContext<EditMilestoneArgs>,
157 current_description: Option<String>,
158) -> anyhow::Result<String> {
159 ctx.editor_for(
160 "a description",
161 current_description
162 .as_deref()
163 .unwrap_or("Enter a milestone description"),
164 )
165}