radicle: add `list_by_status` method to `cob::issue::Issues`

Provides the same `list_by_status` and `<status>` methods that
`cob::patch::Patches` has to `Issues`
This commit is contained in:
Sebastian Martinez 2024-10-04 12:28:56 +02:00 committed by cloudhead
parent 7c902b6905
commit 985987468d
No known key found for this signature in database
1 changed files with 96 additions and 1 deletions

View File

@ -13,7 +13,7 @@ use crate::crypto::Signer;
use crate::prelude::{Did, RepoId};
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
use super::{Issue, IssueCounts, IssueId, IssueMut, State};
use super::{CloseReason, Issue, IssueCounts, IssueId, IssueMut, State};
/// A set of read-only methods for a [`Issue`] store.
pub trait Issues {
@ -31,9 +31,33 @@ pub trait Issues {
/// List all issues that are in the store.
fn list(&self) -> Result<Self::Iter<'_>, Self::Error>;
/// List all issues in the store that match the provided `status`.
///
/// Also see [`Issues::opened`], [`Issues::solved`] and [`Issues::closed`].
fn list_by_status(&self, status: &State) -> Result<Self::Iter<'_>, Self::Error>;
/// Get the [`IssueCounts`] of all the issues in the store.
fn counts(&self) -> Result<IssueCounts, Self::Error>;
/// List all open issues in the store.
fn opened(&self) -> Result<Self::Iter<'_>, Self::Error> {
self.list_by_status(&State::Open)
}
/// List all closed issues with `CloseReason::Solved` in the store.
fn solved(&self) -> Result<Self::Iter<'_>, Self::Error> {
self.list_by_status(&State::Closed {
reason: CloseReason::Solved,
})
}
/// List all closed issues with `CloseReasion::Other` in the store.
fn closed(&self) -> Result<Self::Iter<'_>, Self::Error> {
self.list_by_status(&State::Closed {
reason: CloseReason::Other,
})
}
/// Returns `true` if there are no issues in the store.
fn is_empty(&self) -> Result<bool, Self::Error> {
Ok(self.counts()?.total() == 0)
@ -368,6 +392,19 @@ where
.map_err(super::Error::from)
}
fn list_by_status(&self, status: &State) -> Result<Self::Iter<'_>, Self::Error> {
let status = *status;
self.store
.all()
.map(move |inner| NoCacheIter {
inner: Box::new(inner.into_iter().filter_map(move |res| match res {
Ok((id, issue)) => (status == issue.state).then_some((id, issue)).map(Ok),
Err(e) => Some(Err(e.into())),
})),
})
.map_err(super::Error::from)
}
fn counts(&self) -> Result<IssueCounts, Self::Error> {
self.store.counts().map_err(super::Error::from)
}
@ -425,6 +462,10 @@ where
query::list(&self.cache.db, &self.rid())
}
fn list_by_status(&self, status: &State) -> Result<Self::Iter<'_>, Self::Error> {
query::list_by_status(&self.cache.db, &self.rid(), status)
}
fn counts(&self) -> Result<IssueCounts, Self::Error> {
query::counts(&self.cache.db, &self.rid())
}
@ -445,6 +486,10 @@ where
query::list(&self.cache.db, &self.rid())
}
fn list_by_status(&self, status: &State) -> Result<Self::Iter<'_>, Self::Error> {
query::list_by_status(&self.cache.db, &self.rid(), status)
}
fn counts(&self) -> Result<IssueCounts, Self::Error> {
query::counts(&self.cache.db, &self.rid())
}
@ -497,6 +542,26 @@ mod query {
})
}
pub(super) fn list_by_status<'a>(
db: &'a sql::ConnectionThreadSafe,
rid: &RepoId,
filter: &State,
) -> Result<IssuesIter<'a>, Error> {
let mut stmt = db.prepare(
"SELECT id, issue
FROM issues
WHERE repo = ?1
AND issue->>'$.state.status' = ?2
ORDER BY id
",
)?;
stmt.bind((1, rid))?;
stmt.bind((2, sql::Value::String(filter.to_string())))?;
Ok(IssuesIter {
inner: stmt.into_iter(),
})
}
pub(super) fn counts(
db: &sql::ConnectionThreadSafe,
rid: &RepoId,
@ -678,6 +743,36 @@ mod tests {
assert_eq!(issues, list);
}
#[test]
fn test_list_by_status() {
let repo = arbitrary::gen::<MockRepository>(1);
let mut cache = memory(repo);
let ids = (0..arbitrary::gen::<u8>(1))
.map(|_| IssueId::from(arbitrary::oid()))
.collect::<BTreeSet<IssueId>>();
let mut issues = Vec::with_capacity(ids.len());
for id in ids.iter() {
let issue = Issue {
title: id.to_string(),
..Issue::new(Thread::default())
};
cache
.update(&cache.rid(), &IssueId::from(*id), &issue)
.unwrap();
issues.push((*id, issue));
}
let mut list = cache
.list_by_status(&State::Open)
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
list.sort_by_key(|(id, _)| *id);
issues.sort_by_key(|(id, _)| *id);
assert_eq!(issues, list);
}
#[test]
fn test_remove() {
let repo = arbitrary::gen::<MockRepository>(1);