Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Optimise patch list command
Open rudolfs opened 1 year ago

So we don’t materialize the whole list multiple times especially when paginating. Mostly written by Finto, I was just driving.

check check-e2e

πŸ‘‰ Workflow runs πŸ‘‰ Branch on GitHub

8 files changed +111 -10 82560dbb β†’ 5052f0a9
modified Cargo.lock
@@ -4245,6 +4245,7 @@ version = "0.0.0"
dependencies = [
 "anyhow",
 "base64 0.22.1",
+
 "either",
 "log",
 "radicle",
 "radicle-surf",
modified crates/radicle-tauri/Cargo.toml
@@ -17,6 +17,7 @@ tauri-build = { version = "2.0.1", features = ["isolation"] }
[dependencies]
anyhow = { version = "1.0.90" }
base64 = { version = "0.22.1" }
+
either = { version = "1.15" }
log = { version = "0.4.22" }
radicle = { git = "https://ash.radicle.garden/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git", package = "radicle", rev = "7c902b6905724345ba850eb6cca8f8becc9a9c72" }
radicle-types = { version = "0.1.0", path = "../radicle-types" }
modified crates/radicle-tauri/src/commands/cob/patch.rs
@@ -15,6 +15,8 @@ use radicle_types::traits::Profile;

use crate::AppState;

+
use either::Either;
+

#[tauri::command]
pub async fn list_patches(
    ctx: tauri::State<'_, AppState>,
@@ -28,18 +30,15 @@ pub async fn list_patches(
    let profile = ctx.profile();
    let cursor = skip.unwrap_or(0);
    let aliases = profile.aliases();
-

-
    let patches = match status {
-
        None => sqlite_service.list(rid)?.collect::<Vec<_>>(),
-
        Some(s) => sqlite_service
-
            .list_by_status(rid, s.into())?
-
            .collect::<Vec<_>>(),
+
    let counts = sqlite_service.counts(rid)?;
+
    let patches = match status.clone() {
+
        None => Either::Left(sqlite_service.list(rid)?),
+
        Some(s) => Either::Right(sqlite_service.list_by_status(rid, s.into())?),
    };

    match take {
        None => {
            let content = patches
-
                .into_iter()
                .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases))
                .collect::<Vec<_>>();

@@ -50,10 +49,10 @@ pub async fn list_patches(
            })
        }
        Some(take) => {
-
            let more = cursor + take < patches.len();
+
            let total = status.map_or_else(|| counts.total(), |status| counts[status.into()]);
+
            let more = cursor + take < total;

            let content = patches
-
                .into_iter()
                .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases))
                .skip(cursor)
                .take(take)
modified crates/radicle-types/src/domain/patch/models/patch.rs
@@ -1,7 +1,9 @@
use std::collections::BTreeMap;
use std::collections::BTreeSet;
+
use std::ops::Index;

use radicle::node::AliasStore;
+
use radicle::patch::Status;
use radicle::profile::Aliases;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
@@ -668,3 +670,52 @@ impl FromRadicleAction<radicle::patch::Action> for Action {
        }
    }
}
+

+
#[derive(Debug, Default, TS, Serialize)]
+
#[ts(export)]
+
#[ts(export_to = "cob/patch/")]
+
#[serde(rename_all = "camelCase")]
+
pub struct PatchCounts {
+
    pub(crate) open: usize,
+
    pub(crate) draft: usize,
+
    pub(crate) archived: usize,
+
    pub(crate) merged: usize,
+
}
+

+
impl Index<Status> for PatchCounts {
+
    type Output = usize;
+

+
    fn index(&self, status: Status) -> &Self::Output {
+
        match status {
+
            Status::Draft => &self.draft,
+
            Status::Open => &self.open,
+
            Status::Archived => &self.archived,
+
            Status::Merged => &self.merged,
+
        }
+
    }
+
}
+

+
impl PatchCounts {
+
    pub fn new(open: usize, draft: usize, archived: usize, merged: usize) -> Self {
+
        Self {
+
            open,
+
            draft,
+
            archived,
+
            merged,
+
        }
+
    }
+

+
    pub fn total(&self) -> usize {
+
        self.open + self.draft + self.archived + self.merged
+
    }
+
}
+

+
#[derive(Debug, thiserror::Error)]
+
pub enum CountsError {
+
    #[error(transparent)]
+
    Sqlite(#[from] sqlite::Error),
+

+
    #[error(transparent)]
+
    Unknown(#[from] anyhow::Error),
+
    // to be extended as new error scenarios are introduced
+
}
modified crates/radicle-types/src/domain/patch/service.rs
@@ -5,6 +5,8 @@ use radicle::patch::PatchId;

use crate::domain::patch::traits::{PatchService, PatchStorage};

+
use super::models::patch::PatchCounts;
+

#[derive(Debug, Clone)]
pub struct Service<I>
where
@@ -42,4 +44,11 @@ where
    {
        self.patches.list_by_status(rid, status)
    }
+

+
    fn counts(
+
        &self,
+
        rid: identity::RepoId,
+
    ) -> Result<PatchCounts, super::models::patch::CountsError> {
+
        self.patches.counts(rid)
+
    }
}
modified crates/radicle-types/src/domain/patch/traits.rs
@@ -16,6 +16,11 @@ pub trait PatchStorage {
        rid: identity::RepoId,
        status: patch::Status,
    ) -> Result<impl Iterator<Item = (PatchId, Patch)>, models::patch::ListPatchesError>;
+

+
    fn counts(
+
        &self,
+
        rid: identity::RepoId,
+
    ) -> Result<models::patch::PatchCounts, models::patch::CountsError>;
}

pub trait PatchService {
@@ -29,4 +34,9 @@ pub trait PatchService {
        rid: identity::RepoId,
        status: patch::Status,
    ) -> Result<impl Iterator<Item = (PatchId, Patch)>, models::patch::ListPatchesError>;
+

+
    fn counts(
+
        &self,
+
        rid: identity::RepoId,
+
    ) -> Result<models::patch::PatchCounts, models::patch::CountsError>;
}
modified crates/radicle-types/src/error.rs
@@ -49,6 +49,9 @@ pub enum Error {
    #[error(transparent)]
    ListPatchesError(#[from] crate::domain::patch::models::patch::ListPatchesError),

+
    #[error(transparent)]
+
    PatchCountsError(#[from] crate::domain::patch::models::patch::CountsError),
+

    /// CobStore error.
    #[error(transparent)]
    CobStore(#[from] radicle::cob::store::Error),
modified crates/radicle-types/src/outbound/sqlite.rs
@@ -10,7 +10,7 @@ use sqlite as sql;

use crate::domain::inbox::models::notification;
use crate::domain::inbox::traits::InboxStorage;
-
use crate::domain::patch::models::patch::ListPatchesError;
+
use crate::domain::patch::models::patch::{CountsError, ListPatchesError, PatchCounts, State};
use crate::domain::patch::traits::PatchStorage;
use crate::error::Error;

@@ -34,6 +34,33 @@ impl Sqlite {
}

impl PatchStorage for Sqlite {
+
    fn counts(&self, rid: identity::RepoId) -> Result<PatchCounts, CountsError> {
+
        let mut stmt = self.db.prepare(
+
            "SELECT
+
                 patch->'$.state' AS state,
+
                 COUNT(*) AS count
+
             FROM patches
+
             WHERE repo = ?1
+
             GROUP BY patch->'$.state.status'",
+
        )?;
+
        stmt.bind((1, &rid))?;
+

+
        stmt.into_iter()
+
            .try_fold(PatchCounts::default(), |mut counts, row| {
+
                let row = row?;
+
                let count = row.read::<i64, _>("count") as usize;
+
                let status = serde_json::from_str::<State>(row.read::<&str, _>("state"))
+
                    .map_err(|err| CountsError::Unknown(err.into()))?;
+
                match status {
+
                    State::Draft => counts.draft += count,
+
                    State::Open { .. } => counts.open += count,
+
                    State::Archived => counts.archived += count,
+
                    State::Merged { .. } => counts.merged += count,
+
                }
+
                Ok(counts)
+
            })
+
    }
+

    fn list(
        &self,
        rid: identity::RepoId,