feat: multi-steps push progress #30
@@ -18,7 +18,7 @@ use crate::checkouts::CheckoutsStore;
|
|||||||
use crate::patch_series::{MAX_PATCH_EVENT_BYTES, PatchSeries};
|
use crate::patch_series::{MAX_PATCH_EVENT_BYTES, PatchSeries};
|
||||||
use crate::push::{PushOutcome, grasp_base_url, grasp06_prs_url, pr_clone_urls};
|
use crate::push::{PushOutcome, grasp_base_url, grasp06_prs_url, pr_clone_urls};
|
||||||
use crate::repos::RepoListStore;
|
use crate::repos::RepoListStore;
|
||||||
use crate::state::{GitRef, SyncState};
|
use crate::state::{GitRef, PushStepStatus, SyncState};
|
||||||
use crate::sync_status::SyncStatusStore;
|
use crate::sync_status::SyncStatusStore;
|
||||||
|
|
||||||
pub struct RepoStore {
|
pub struct RepoStore {
|
||||||
@@ -48,6 +48,7 @@ pub struct RepoStore {
|
|||||||
// The repository is out of sync on the rejected servers until republished.
|
// The repository is out of sync on the rejected servers until republished.
|
||||||
pub last_push_warning: Option<String>,
|
pub last_push_warning: Option<String>,
|
||||||
pub pushing: bool,
|
pub pushing: bool,
|
||||||
|
pub push_steps: Vec<PushStepStatus>,
|
||||||
pub cloning: bool,
|
pub cloning: bool,
|
||||||
// Avoids re-subscribing and re-fetching on every refresh.
|
// Avoids re-subscribing and re-fetching on every refresh.
|
||||||
repo_relays: HashSet<RelayUrl>,
|
repo_relays: HashSet<RelayUrl>,
|
||||||
@@ -101,6 +102,7 @@ impl RepoStore {
|
|||||||
last_warning: None,
|
last_warning: None,
|
||||||
last_push_warning: None,
|
last_push_warning: None,
|
||||||
pushing: false,
|
pushing: false,
|
||||||
|
push_steps: Vec::new(),
|
||||||
cloning: false,
|
cloning: false,
|
||||||
repo_relays: HashSet::new(),
|
repo_relays: HashSet::new(),
|
||||||
root_fetches: HashSet::new(),
|
root_fetches: HashSet::new(),
|
||||||
@@ -131,6 +133,7 @@ impl RepoStore {
|
|||||||
last_warning: None,
|
last_warning: None,
|
||||||
last_push_warning: None,
|
last_push_warning: None,
|
||||||
pushing: false,
|
pushing: false,
|
||||||
|
push_steps: Vec::new(),
|
||||||
cloning: false,
|
cloning: false,
|
||||||
repo_relays: HashSet::new(),
|
repo_relays: HashSet::new(),
|
||||||
root_fetches: HashSet::new(),
|
root_fetches: HashSet::new(),
|
||||||
@@ -169,6 +172,14 @@ impl RepoStore {
|
|||||||
let backend = Backend::global(cx);
|
let backend = Backend::global(cx);
|
||||||
|
|
||||||
cx.subscribe(&backend, |this, _backend, event, cx| {
|
cx.subscribe(&backend, |this, _backend, event, cx| {
|
||||||
|
if let BackendEvent::PushProgress { addr, status } = event {
|
||||||
|
if this.addr.as_ref() == Some(addr) {
|
||||||
|
this.apply_push_step(status.clone(), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(addr) = this.addr.as_ref() else {
|
let Some(addr) = this.addr.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -203,6 +214,19 @@ impl RepoStore {
|
|||||||
self.addr.as_ref()
|
self.addr.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_push_step(&mut self, status: PushStepStatus, cx: &mut Context<Self>) {
|
||||||
|
match self
|
||||||
|
.push_steps
|
||||||
|
.iter_mut()
|
||||||
|
.find(|entry| entry.step() == status.step())
|
||||||
|
{
|
||||||
|
Some(entry) => *entry = status,
|
||||||
|
None => self.push_steps.push(status),
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> SharedString {
|
pub fn name(&self) -> SharedString {
|
||||||
self.announcement
|
self.announcement
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -1186,6 +1210,7 @@ impl RepoStore {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<(), Error>> {
|
) -> Task<Result<(), Error>> {
|
||||||
self.pushing = true;
|
self.pushing = true;
|
||||||
|
self.push_steps.clear();
|
||||||
self.last_error = None;
|
self.last_error = None;
|
||||||
self.last_push_warning = None;
|
self.last_push_warning = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -1195,6 +1220,7 @@ impl RepoStore {
|
|||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.pushing = false;
|
this.pushing = false;
|
||||||
|
this.push_steps.clear();
|
||||||
|
|
||||||
match &result {
|
match &result {
|
||||||
Ok(outcome) => {
|
Ok(outcome) => {
|
||||||
|
|||||||
@@ -67,6 +67,28 @@ pub enum PushStepStatus {
|
|||||||
Failed(PushStep, String),
|
Failed(PushStep, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PushStep {
|
||||||
|
pub fn label(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::AnnounceRepository => "Announce repository".to_owned(),
|
||||||
|
Self::StageStateEvent(relay) => format!("Publish state event -> {relay}"),
|
||||||
|
Self::BroadcastStateEvent => "Publish state event to relays".to_owned(),
|
||||||
|
Self::PushObjects(relay) => format!(
|
||||||
|
"Push objects -> {}",
|
||||||
|
grasp_base_url(relay).unwrap_or_else(|| relay.to_string())
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PushStepStatus {
|
||||||
|
pub fn step(&self) -> &PushStep {
|
||||||
|
match self {
|
||||||
|
Self::Running(step) | Self::Done(step) | Self::Failed(step, _) => step,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SyncState {
|
impl SyncState {
|
||||||
pub fn build(id: &str, refs: &[GitRef], head: Option<&str>) -> EventBuilder {
|
pub fn build(id: &str, refs: &[GitRef], head: Option<&str>) -> EventBuilder {
|
||||||
let mut tags: Vec<Tag> = vec![Tag::identifier(id.to_owned())];
|
let mut tags: Vec<Tag> = vec![Tag::identifier(id.to_owned())];
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ use signed_core::{Announcement, RepoAddr, RepoStatus};
|
|||||||
use signed_git::{FileCommit, GitCache, RefSync, Repo};
|
use signed_git::{FileCommit, GitCache, RefSync, Repo};
|
||||||
use signed_state::{
|
use signed_state::{
|
||||||
Backend, CheckoutStatus, CheckoutSyncStatus, CheckoutsStore, LocalReposStore, Mirrors,
|
Backend, CheckoutStatus, CheckoutSyncStatus, CheckoutsStore, LocalReposStore, Mirrors,
|
||||||
Nip34Binding, Nip34Kind, ProfileStore, RepoListStore, RepoStore, SyncStatusStore,
|
Nip34Binding, Nip34Kind, ProfileStore, PushStepStatus, RepoListStore, RepoStore,
|
||||||
|
SyncStatusStore,
|
||||||
};
|
};
|
||||||
use signed_ui::{
|
use signed_ui::{
|
||||||
Avatar, CountBadge, DropdownButton, PixelAvatar, copy_row, menu_copy_row, ref_selector_trigger,
|
Avatar, CountBadge, DropdownButton, PixelAvatar, copy_row, menu_copy_row, ref_selector_trigger,
|
||||||
@@ -1747,6 +1748,59 @@ impl RepoDetailView {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn render_push_progress_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||||
|
let store = self.store.read(cx);
|
||||||
|
if !store.pushing || store.push_steps.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let steps = store
|
||||||
|
.push_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()
|
||||||
|
.p_4()
|
||||||
|
.gap_1()
|
||||||
|
.w_full()
|
||||||
|
.bg(cx.theme().muted)
|
||||||
|
.child(div().text_sm().child("Pushing"))
|
||||||
|
.children(steps)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn render_push_warning_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
pub(super) fn render_push_warning_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||||
let store = self.store.read(cx);
|
let store = self.store.read(cx);
|
||||||
let warning = store.last_push_warning.clone()?;
|
let warning = store.last_push_warning.clone()?;
|
||||||
@@ -2189,6 +2243,9 @@ impl Render for RepoDetailView {
|
|||||||
.id("repo")
|
.id("repo")
|
||||||
.size_full()
|
.size_full()
|
||||||
.when_some(banner, |this, banner| this.child(banner))
|
.when_some(banner, |this, banner| this.child(banner))
|
||||||
|
.when_some(self.render_push_progress_banner(cx), |this, banner| {
|
||||||
|
this.child(banner)
|
||||||
|
})
|
||||||
.when_some(self.render_push_warning_banner(cx), |this, banner| {
|
.when_some(self.render_push_warning_banner(cx), |this, banner| {
|
||||||
this.child(banner)
|
this.child(banner)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user