codeberg_cli/actions/pull_request/
edit.rsuse crate::actions::GeneralArgs;
use crate::render::option::option_display;
use crate::render::spinner::spin_until_ready;
use crate::render::ui::{fuzzy_select_with_key, multi_fuzzy_select_with_key};
use crate::types::context::BergContext;
use crate::types::git::OwnerRepo;
use anyhow::Context;
use forgejo_api::structs::{
EditPullRequestOption, IssueListLabelsQuery, PullRequest, RepoListPullRequestsQuery, StateType,
};
use strum::{Display, VariantArray};
use crate::actions::text_manipulation::{edit_prompt_for, input_prompt_for, select_prompt_for};
use super::display_pull_request;
use clap::Parser;
#[derive(Parser, Debug)]
pub struct EditPullRequestArgs {}
#[derive(Display, PartialEq, Eq, VariantArray)]
enum EditableFields {
Assignees,
Description,
State,
Labels,
Title,
}
impl EditPullRequestArgs {
pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
let _ = general_args;
let ctx = BergContext::new(self).await?;
let OwnerRepo { repo, owner } = ctx.owner_repo()?;
let pull_request = select_pull_request(&ctx).await?;
let pull_request_id = pull_request
.number
.context("Selected pull_request doesn't have an ID")?;
let options = create_options(&ctx, &pull_request).await?;
tracing::debug!("{options:?}");
tracing::debug!("{owner:?}/{repo:?}");
tracing::debug!("{pull_request_id:?}");
let updated_pull_request = ctx
.client
.repo_edit_pull_request(owner.as_str(), repo.as_str(), pull_request_id, options)
.await?;
tracing::debug!("{updated_pull_request:?}");
Ok(())
}
}
async fn select_pull_request(
ctx: &BergContext<EditPullRequestArgs>,
) -> anyhow::Result<PullRequest> {
let OwnerRepo { repo, owner } = ctx.owner_repo()?;
let pull_requests_list = spin_until_ready(ctx.client.repo_list_pull_requests(
owner.as_str(),
repo.as_str(),
RepoListPullRequestsQuery::default(),
))
.await?;
fuzzy_select_with_key(
&pull_requests_list,
select_prompt_for("pull request"),
display_pull_request,
)
.cloned()
}
async fn create_options(
ctx: &BergContext<EditPullRequestArgs>,
pull_request: &PullRequest,
) -> anyhow::Result<EditPullRequestOption> {
let selected_update_fields = multi_fuzzy_select_with_key(
EditableFields::VARIANTS,
select_prompt_for("options"),
|_| false,
|f| f.to_string(),
)?;
let mut options = EditPullRequestOption {
assignee: None,
assignees: None,
body: None,
due_date: None,
milestone: None,
state: None,
title: None,
unset_due_date: None,
allow_maintainer_edit: None,
base: None,
labels: None,
};
if selected_update_fields.contains(&&EditableFields::Assignees) {
let current_assignees = pull_request
.assignees
.as_ref()
.map(|users| users.iter().filter_map(|user| user.id).collect::<Vec<_>>());
options
.assignees
.replace(pull_request_assignees(ctx, current_assignees).await?);
}
if selected_update_fields.contains(&&EditableFields::Description) {
options
.body
.replace(pull_request_description(ctx, pull_request.body.clone()).await?);
}
if selected_update_fields.contains(&&EditableFields::State) {
options
.state
.replace(pull_request_state(ctx, pull_request.state.clone())?);
}
if selected_update_fields.contains(&&EditableFields::Title) {
options
.title
.replace(pull_request_title(ctx, pull_request.title.clone()).await?);
}
if selected_update_fields.contains(&&EditableFields::Labels) {
let current_labels = pull_request.labels.as_ref().map(|labels| {
labels
.iter()
.filter_map(|label| label.id)
.collect::<Vec<_>>()
});
options
.labels
.replace(pull_request_labels(ctx, current_labels).await?);
}
Ok(options)
}
async fn pull_request_assignees(
ctx: &BergContext<EditPullRequestArgs>,
current_assignees: Option<Vec<u64>>,
) -> anyhow::Result<Vec<String>> {
let OwnerRepo { repo, owner } = ctx.owner_repo()?;
let current_assignees = current_assignees.unwrap_or_default();
let all_assignees = ctx
.client
.repo_get_assignees(owner.as_str(), repo.as_str())
.await?;
let selected_assignees = multi_fuzzy_select_with_key(
&all_assignees,
select_prompt_for("assignees"),
|u| u.id.is_some_and(|id| current_assignees.contains(&id)),
|u| option_display(&u.login),
)?;
Ok(selected_assignees
.into_iter()
.filter_map(|u| u.login.as_ref().cloned())
.collect::<Vec<_>>())
}
async fn pull_request_description(
_ctx: &BergContext<EditPullRequestArgs>,
current_description: Option<String>,
) -> anyhow::Result<String> {
inquire::Editor::new(edit_prompt_for("a description").as_str())
.with_predefined_text(
current_description
.as_deref()
.unwrap_or("Enter a pull request description"),
)
.prompt()
.map_err(anyhow::Error::from)
}
fn pull_request_state(
_ctx: &BergContext<EditPullRequestArgs>,
_current_state: Option<StateType>,
) -> anyhow::Result<String> {
let selected_state = fuzzy_select_with_key(
&[StateType::Open, StateType::Closed],
select_prompt_for("states"),
|f| format!("{f:?}"),
)?;
Ok(format!("{selected_state:?}"))
}
async fn pull_request_title(
_ctx: &BergContext<EditPullRequestArgs>,
current_title: Option<String>,
) -> anyhow::Result<String> {
inquire::Text::new(input_prompt_for("Choose a new pull request title").as_str())
.with_default(
current_title
.as_deref()
.unwrap_or("Enter pull request title"),
)
.prompt()
.map_err(anyhow::Error::from)
}
async fn pull_request_labels(
ctx: &BergContext<EditPullRequestArgs>,
current_labels: Option<Vec<u64>>,
) -> anyhow::Result<Vec<u64>> {
let OwnerRepo { repo, owner } = ctx.owner_repo()?;
let current_labels = current_labels.unwrap_or_default();
let all_labels = ctx
.client
.issue_list_labels(
owner.as_str(),
repo.as_str(),
IssueListLabelsQuery::default(),
)
.await?;
let selected_labels = multi_fuzzy_select_with_key(
&all_labels,
select_prompt_for("labels"),
|l| l.id.is_some_and(|id| current_labels.contains(&id)),
|l| option_display(&l.name),
)?;
let label_ids = selected_labels
.iter()
.filter_map(|l| l.id)
.collect::<Vec<_>>();
Ok(label_ids)
}