Skip to main content

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::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/// Edit selected issue
26#[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}