diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs index 848fdef5..ca7e15b8 100644 --- a/radicle-httpd/src/api.rs +++ b/radicle-httpd/src/api.rs @@ -51,8 +51,8 @@ impl Context { let doc = repo.identity_of(self.profile.id())?; let payload = doc.project()?; let delegates = doc.delegates; - let issues = (Issues::open(self.profile.public_key, &repo)?).count()?; - let patches = (Patches::open(self.profile.public_key, &repo)?).count()?; + let issues = Issues::open(self.profile.public_key, &repo)?.counts()?; + let patches = Patches::open(self.profile.public_key, &repo)?.counts()?; Ok(project::Info { payload, @@ -143,6 +143,7 @@ mod project { use nonempty::NonEmpty; use serde::Serialize; + use radicle::cob; use radicle::git::Oid; use radicle::identity::project::Project; use radicle::identity::Id; @@ -157,8 +158,8 @@ mod project { pub payload: Project, pub delegates: NonEmpty, pub head: Oid, - pub patches: usize, - pub issues: usize, + pub patches: cob::patch::PatchCounts, + pub issues: cob::issue::IssueCounts, pub id: Id, } } diff --git a/radicle-httpd/src/api/v1/delegates.rs b/radicle-httpd/src/api/v1/delegates.rs index 530dc76d..31f72295 100644 --- a/radicle-httpd/src/api/v1/delegates.rs +++ b/radicle-httpd/src/api/v1/delegates.rs @@ -49,9 +49,9 @@ async fn delegates_projects_handler( } let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None }; - let Ok(issues) = (*issues).count() else { return None }; + let Ok(issues) = issues.counts() else { return None }; let Ok(patches) = Patches::open(ctx.profile.public_key, &repo) else { return None }; - let Ok(patches) = (*patches).count() else { return None }; + let Ok(patches) = patches.counts() else { return None }; Some(Info { payload, @@ -96,8 +96,15 @@ mod routes { "defaultBranch": "master", "delegates": ["did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"], "head": HEAD, - "patches": 1, - "issues": 1, + "patches": { + "proposed": 1, + "draft": 0, + "archived": 0, + }, + "issues": { + "open": 1, + "closed": 0, + }, "id": "rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp" } ]) diff --git a/radicle-httpd/src/api/v1/projects.rs b/radicle-httpd/src/api/v1/projects.rs index 1bde6fee..4940015b 100644 --- a/radicle-httpd/src/api/v1/projects.rs +++ b/radicle-httpd/src/api/v1/projects.rs @@ -81,9 +81,9 @@ async fn project_root_handler( let Ok(payload) = repo.project_of(ctx.profile.id()) else { return None }; let Ok(doc) = repo.identity_of(ctx.profile.id()) else { return None }; let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None }; - let Ok(issues) = (*issues).count() else { return None }; + let Ok(issues) = issues.counts() else { return None }; let Ok(patches) = Patches::open(ctx.profile.public_key, &repo) else { return None }; - let Ok(patches) = (*patches).count() else { return None }; + let Ok(patches) = patches.counts() else { return None }; let delegates = doc.delegates; Some(Info { @@ -583,8 +583,15 @@ mod routes { "defaultBranch": "master", "delegates": ["did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"], "head": HEAD, - "patches": 1, - "issues": 1, + "patches": { + "proposed": 1, + "draft": 0, + "archived": 0, + }, + "issues": { + "open": 1, + "closed": 0, + }, "id": "rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp" } ]) @@ -606,8 +613,15 @@ mod routes { "defaultBranch": "master", "delegates": ["did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"], "head": HEAD, - "patches": 1, - "issues": 1, + "patches": { + "proposed": 1, + "draft": 0, + "archived": 0, + }, + "issues": { + "open": 1, + "closed": 0, + }, "id": "rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp" }) ); diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs index 32baf697..5f724015 100644 --- a/radicle/src/cob/issue.rs +++ b/radicle/src/cob/issue.rs @@ -385,6 +385,14 @@ impl<'a> Deref for Issues<'a> { } } +/// Detailed information on issue states +#[derive(Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IssueCounts { + pub open: usize, + pub closed: usize, +} + impl<'a> Issues<'a> { /// Open an issues store. pub fn open( @@ -443,6 +451,22 @@ impl<'a> Issues<'a> { }) } + /// Issues count by state. + pub fn counts(&self) -> Result { + let all = self.all()?; + let state_groups = + all.filter_map(|s| s.ok()) + .fold(IssueCounts::default(), |mut state, (_, p, _)| { + match p.state() { + State::Open => state.open += 1, + State::Closed { .. } => state.closed += 1, + } + state + }); + + Ok(state_groups) + } + /// Remove an issue. pub fn remove(&self, id: &ObjectId) -> Result<(), store::Error> { self.raw.remove(id) diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs index 5a23ab90..d6639864 100644 --- a/radicle/src/cob/patch.rs +++ b/radicle/src/cob/patch.rs @@ -758,6 +758,15 @@ impl<'a, 'g> Deref for PatchMut<'a, 'g> { } } +/// Detailed information on patch states +#[derive(Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PatchCounts { + pub proposed: usize, + pub draft: usize, + pub archived: usize, +} + pub struct Patches<'a> { raw: store::Store<'a, Patch>, } @@ -804,6 +813,23 @@ impl<'a> Patches<'a> { Ok(PatchMut::new(id, patch, clock, self)) } + /// Patches count by state. + pub fn counts(&self) -> Result { + let all = self.all()?; + let state_groups = + all.filter_map(|s| s.ok()) + .fold(PatchCounts::default(), |mut state, (_, p, _)| { + match p.state() { + State::Draft => state.draft += 1, + State::Proposed => state.proposed += 1, + State::Archived => state.archived += 1, + } + state + }); + + Ok(state_groups) + } + /// Get a patch. pub fn get(&self, id: &ObjectId) -> Result, store::Error> { self.raw.get(id).map(|r| r.map(|(p, _)| p))