codeberg_cli/actions/milestone/
edit.rs

1use 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/// Edit selected issue
25#[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}