codeberg_cli/actions/milestone/
edit.rs

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