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