Show push step progress in publish dialogs
Rust / build (macos-latest, stable) (push) Canceled after 0s
Rust / build (ubuntu-latest, stable) (push) Canceled after 0s
Rust / build (windows-latest, stable) (push) Canceled after 0s
Rust / build (macos-latest, stable) (pull_request) Canceled after 0s
Rust / build (ubuntu-latest, stable) (pull_request) Canceled after 0s
Rust / build (windows-latest, stable) (pull_request) Canceled after 0s
Rust / build (macos-latest, stable) (push) Canceled after 0s
Rust / build (ubuntu-latest, stable) (push) Canceled after 0s
Rust / build (windows-latest, stable) (push) Canceled after 0s
Rust / build (macos-latest, stable) (pull_request) Canceled after 0s
Rust / build (ubuntu-latest, stable) (pull_request) Canceled after 0s
Rust / build (windows-latest, stable) (pull_request) Canceled after 0s
This commit is contained in:
@@ -528,6 +528,7 @@ impl Backend {
|
||||
}
|
||||
|
||||
let owner = public_key.to_bech32().unwrap();
|
||||
let addr = RepoAddr::new(public_key, repo_id.clone());
|
||||
let servers = grasp_servers.clone();
|
||||
let client = self.client.clone();
|
||||
let signer = self.signer.clone();
|
||||
@@ -542,7 +543,7 @@ impl Backend {
|
||||
folder.join(dir_name)
|
||||
};
|
||||
|
||||
cx.spawn(async move |_this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let work = cx.background_spawn({
|
||||
let destination = destination.clone();
|
||||
let name = name.clone();
|
||||
@@ -589,8 +590,19 @@ impl Backend {
|
||||
mode: GitPushMode::Main,
|
||||
};
|
||||
|
||||
let on_step = &mut |status: PushStepStatus, cx: &mut AsyncApp| {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(BackendEvent::PushProgress {
|
||||
addr: addr.clone(),
|
||||
status,
|
||||
});
|
||||
})?;
|
||||
Ok::<(), Error>(())
|
||||
};
|
||||
|
||||
let event =
|
||||
announce_and_push_or_retract(&client, &signer, &state, announcement, cx).await?;
|
||||
announce_and_push_or_retract(&client, &signer, &state, announcement, cx, on_step)
|
||||
.await?;
|
||||
|
||||
let announcement = Announcement::from_event(&event)
|
||||
.ok_or_else(|| anyhow!("failed to parse announcement"))?;
|
||||
@@ -638,12 +650,13 @@ impl Backend {
|
||||
}
|
||||
|
||||
let owner = public_key.to_bech32().unwrap();
|
||||
let addr = RepoAddr::new(public_key, repo_id.clone());
|
||||
let servers = grasp_servers.clone();
|
||||
|
||||
let client = self.client.clone();
|
||||
let signer = self.signer.clone();
|
||||
|
||||
cx.spawn(async move |_this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let (mut state, euc) = cx
|
||||
.background_spawn({
|
||||
let path = path.clone();
|
||||
@@ -670,7 +683,19 @@ impl Backend {
|
||||
|
||||
let euc = euc.and_then(|commit: String| Sha1Hash::from_str(&commit).ok());
|
||||
let ann = build_announcement(&repo_id, &name, &description, &owner, &servers, euc);
|
||||
let event = announce_and_push_or_retract(&client, &signer, &state, ann, cx).await?;
|
||||
|
||||
let on_step = &mut |status: PushStepStatus, cx: &mut AsyncApp| {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(BackendEvent::PushProgress {
|
||||
addr: addr.clone(),
|
||||
status,
|
||||
});
|
||||
})?;
|
||||
Ok::<(), Error>(())
|
||||
};
|
||||
|
||||
let event =
|
||||
announce_and_push_or_retract(&client, &signer, &state, ann, cx, on_step).await?;
|
||||
|
||||
if let Some(base) = servers.first().and_then(grasp_base_url) {
|
||||
let url = format!("{base}/{owner}/{repo_id}.git");
|
||||
@@ -1151,15 +1176,17 @@ fn build_announcement(
|
||||
|
||||
/// Announces a repository and pushes its state to grasp servers,
|
||||
/// or retracts the announcement if the push fails.
|
||||
async fn announce_and_push_or_retract<S>(
|
||||
async fn announce_and_push_or_retract<S, P>(
|
||||
client: &Client,
|
||||
signer: &S,
|
||||
state: &SyncState,
|
||||
announcement: GitRepositoryAnnouncement,
|
||||
cx: &mut AsyncApp,
|
||||
on_step: &mut P,
|
||||
) -> Result<Event, Error>
|
||||
where
|
||||
S: AsyncGetPublicKey + AsyncSignEvent + ?Sized,
|
||||
P: FnMut(PushStepStatus, &mut AsyncApp) -> Result<(), Error>,
|
||||
{
|
||||
let state_event = state
|
||||
.state_event(&announcement.id)
|
||||
@@ -1168,13 +1195,7 @@ where
|
||||
let announcement_event = announcement.finalize_async(signer).await?;
|
||||
|
||||
match state
|
||||
.announce_and_push(
|
||||
client,
|
||||
announcement_event.clone(),
|
||||
state_event,
|
||||
cx,
|
||||
&mut |_, _| Ok(()),
|
||||
)
|
||||
.announce_and_push(client, announcement_event.clone(), state_event, cx, on_step)
|
||||
.await
|
||||
{
|
||||
Ok(event) => Ok(event),
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
use gpui::prelude::*;
|
||||
use gpui::{AnyElement, App, SharedString, div};
|
||||
use gpui_component::ActiveTheme;
|
||||
use gpui::{AnyElement, App, Context, SharedString, Subscription, div};
|
||||
use gpui_component::spinner::Spinner;
|
||||
use gpui_component::{ActiveTheme, Icon, IconName, Sizable, h_flex, v_flex};
|
||||
use signed_core::RepoAddr;
|
||||
use signed_state::{Backend, BackendEvent, PushStepStatus};
|
||||
|
||||
/// Progress of an async dialog action: a busy flag that disables the form and an error shown below it.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DialogProgress {
|
||||
pub busy: bool,
|
||||
pub error: Option<SharedString>,
|
||||
// The repository whose push checklist is shown; `None` outside a publish.
|
||||
push_addr: Option<RepoAddr>,
|
||||
pub push_steps: Vec<PushStepStatus>,
|
||||
_push_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl DialogProgress {
|
||||
@@ -16,6 +23,37 @@ impl DialogProgress {
|
||||
self.error = None;
|
||||
}
|
||||
|
||||
/// Starts a publish for the repository at `addr` and listens for its push steps.
|
||||
pub fn begin_push(&mut self, addr: RepoAddr, cx: &mut Context<Self>) {
|
||||
self.begin();
|
||||
self.push_addr = Some(addr);
|
||||
self.push_steps.clear();
|
||||
|
||||
if self._push_subscription.is_none() {
|
||||
let backend = Backend::global(cx);
|
||||
self._push_subscription = Some(cx.subscribe(&backend, |this, _backend, event, cx| {
|
||||
let BackendEvent::PushProgress { addr, status } = event else {
|
||||
return;
|
||||
};
|
||||
|
||||
if this.push_addr.as_ref() != Some(addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
match this
|
||||
.push_steps
|
||||
.iter_mut()
|
||||
.find(|step| step.step() == status.step())
|
||||
{
|
||||
Some(step) => *step = status.clone(),
|
||||
None => this.push_steps.push(status.clone()),
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks an action as failed, enabling the form and showing `message`.
|
||||
pub fn fail(&mut self, message: impl Into<SharedString>) {
|
||||
self.busy = false;
|
||||
@@ -33,3 +71,46 @@ pub fn error_row(error: &Option<SharedString>, cx: &App) -> Option<AnyElement> {
|
||||
.into_any_element()
|
||||
})
|
||||
}
|
||||
|
||||
/// The push checklist of a running publish, `None` before any step is reported.
|
||||
pub fn push_steps_row(steps: &[PushStepStatus], cx: &App) -> Option<AnyElement> {
|
||||
if steps.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rows = steps
|
||||
.iter()
|
||||
.map(|status| {
|
||||
let label = SharedString::from(status.step().label());
|
||||
|
||||
match status {
|
||||
PushStepStatus::Running(_) => h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(Spinner::new().small())
|
||||
.child(label)
|
||||
.into_any_element(),
|
||||
PushStepStatus::Done(_) => h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().foreground)
|
||||
.child(Icon::new(IconName::CircleCheck).small())
|
||||
.child(label)
|
||||
.into_any_element(),
|
||||
PushStepStatus::Failed(_, reason) => h_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().danger)
|
||||
.child(Icon::new(IconName::Close).small())
|
||||
.child(SharedString::from(format!(
|
||||
"{}: {reason}",
|
||||
status.step().label()
|
||||
)))
|
||||
.into_any_element(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(v_flex().gap_1().children(rows).into_any_element())
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ use gpui_component::input::{Input, InputState, Textarea};
|
||||
use gpui_component::select::{Select, SelectState};
|
||||
use gpui_component::{ActiveTheme, Disableable, WindowExt};
|
||||
use settings::SettingsStore;
|
||||
use signed_core::RepoAddr;
|
||||
use signed_git::Repo;
|
||||
use signed_state::Backend;
|
||||
use signed_ui::SelectOption;
|
||||
|
||||
use super::RepoDetailView;
|
||||
use crate::views::dialog_state::{DialogProgress, error_row};
|
||||
use crate::views::dialog_state::{DialogProgress, error_row, push_steps_row};
|
||||
use crate::views::sidebar::grasp_servers::{
|
||||
GraspServersState, grasp_servers_field, load_user_grasp_servers,
|
||||
};
|
||||
@@ -120,6 +121,7 @@ pub fn open(
|
||||
)
|
||||
.child(grasp_servers_field(&grasp_state, &relay_input, cx)),
|
||||
)
|
||||
.children(push_steps_row(&state.read(cx).push_steps, cx))
|
||||
.children(error_row(&error, cx))
|
||||
.child(
|
||||
DialogFooter::new().justify_end().child(
|
||||
@@ -189,9 +191,15 @@ fn init_repository(
|
||||
return;
|
||||
}
|
||||
|
||||
state.update(cx, |state, _| state.begin());
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let Some(user) = backend.read(cx).current_user() else {
|
||||
state.update(cx, |state, _| state.fail("Sign in to publish a repository"));
|
||||
return;
|
||||
};
|
||||
|
||||
let addr = RepoAddr::new(user, RepoAddr::identifier_from_name(&name));
|
||||
state.update(cx, |state, cx| state.begin_push(addr, cx));
|
||||
|
||||
let task = backend.update(cx, |backend, cx| {
|
||||
backend.publish_local_repo(
|
||||
local_path.clone(),
|
||||
|
||||
@@ -10,11 +10,12 @@ use gpui_component::form::{field, v_form};
|
||||
use gpui_component::input::{Input, InputState, Textarea};
|
||||
use gpui_component::{Disableable, IconName, WindowExt, h_flex};
|
||||
use settings::SettingsStore;
|
||||
use signed_core::RepoAddr;
|
||||
use signed_state::{Backend, CheckoutsStore};
|
||||
|
||||
use super::super::open_repo_panel;
|
||||
use super::grasp_servers::{GraspServersState, grasp_servers_field, load_user_grasp_servers};
|
||||
use crate::views::dialog_state::{DialogProgress, error_row};
|
||||
use crate::views::dialog_state::{DialogProgress, error_row, push_steps_row};
|
||||
|
||||
/// Progress of the create-repository flow, so async results can be rendered.
|
||||
pub type CreateRepoState = DialogProgress;
|
||||
@@ -112,6 +113,7 @@ pub fn open(dock_area: WeakEntity<DockArea>, window: &mut Window, cx: &mut App)
|
||||
)
|
||||
.child(grasp_servers_field(&grasp_state, &relay_input, cx)),
|
||||
)
|
||||
.children(push_steps_row(&state.read(cx).push_steps, cx))
|
||||
.children(error_row(&error, cx))
|
||||
.child(
|
||||
DialogFooter::new().justify_end().child(
|
||||
@@ -209,9 +211,15 @@ fn create_repository(
|
||||
return;
|
||||
}
|
||||
|
||||
state.update(cx, |state, _| state.begin());
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let Some(user) = backend.read(cx).current_user() else {
|
||||
state.update(cx, |state, _| state.fail("Sign in to create a repository"));
|
||||
return;
|
||||
};
|
||||
|
||||
let addr = RepoAddr::new(user, RepoAddr::identifier_from_name(&name));
|
||||
state.update(cx, |state, cx| state.begin_push(addr, cx));
|
||||
|
||||
let task = backend.update(cx, |backend, cx| {
|
||||
backend.create_repository(&name, &description, folder, servers, cx)
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user