Use transactions for issue COB
Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
90f20f447a
commit
0b3a32627b
|
|
@ -1,7 +1,6 @@
|
||||||
use std::ops::{ControlFlow, Deref};
|
use std::ops::{ControlFlow, Deref};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use nonempty::NonEmpty;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -11,6 +10,7 @@ use radicle_crdt::{LWWReg, LWWSet, Max, Semilattice};
|
||||||
|
|
||||||
use crate::cob;
|
use crate::cob;
|
||||||
use crate::cob::common::{Author, Reaction, Tag};
|
use crate::cob::common::{Author, Reaction, Tag};
|
||||||
|
use crate::cob::store::Transaction;
|
||||||
use crate::cob::thread;
|
use crate::cob::thread;
|
||||||
use crate::cob::thread::{CommentId, Thread};
|
use crate::cob::thread::{CommentId, Thread};
|
||||||
use crate::cob::{store, ObjectId, OpId, TypeName};
|
use crate::cob::{store, ObjectId, OpId, TypeName};
|
||||||
|
|
@ -196,6 +196,61 @@ impl Deref for Issue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl store::Transaction<Issue> {
|
||||||
|
/// Set the issue title.
|
||||||
|
pub fn title(&mut self, title: impl ToString) -> OpId {
|
||||||
|
self.push(Action::Title {
|
||||||
|
title: title.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lifecycle an issue.
|
||||||
|
pub fn lifecycle(&mut self, state: State) -> OpId {
|
||||||
|
self.push(Action::Lifecycle { state })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Comment on an issue.
|
||||||
|
pub fn comment<S: ToString>(&mut self, body: S) -> CommentId {
|
||||||
|
self.push(Action::from(thread::Action::Comment {
|
||||||
|
body: body.to_string(),
|
||||||
|
reply_to: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tag an issue.
|
||||||
|
pub fn tag(
|
||||||
|
&mut self,
|
||||||
|
add: impl IntoIterator<Item = Tag>,
|
||||||
|
remove: impl IntoIterator<Item = Tag>,
|
||||||
|
) -> OpId {
|
||||||
|
let add = add.into_iter().collect::<Vec<_>>();
|
||||||
|
let remove = remove.into_iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
self.push(Action::Tag { add, remove })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reply to on an issue comment.
|
||||||
|
pub fn reply<S: ToString>(&mut self, parent: CommentId, body: S) -> OpId {
|
||||||
|
let body = body.to_string();
|
||||||
|
|
||||||
|
self.push(Action::from(thread::Action::Comment {
|
||||||
|
body,
|
||||||
|
reply_to: Some(parent),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// React to an issue comment.
|
||||||
|
pub fn react(&mut self, to: CommentId, reaction: Reaction) -> OpId {
|
||||||
|
self.push(Action::Thread {
|
||||||
|
action: thread::Action::React {
|
||||||
|
to,
|
||||||
|
reaction,
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IssueMut<'a, 'g> {
|
pub struct IssueMut<'a, 'g> {
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
clock: clock::Lamport,
|
clock: clock::Lamport,
|
||||||
|
|
@ -211,22 +266,16 @@ impl<'a, 'g> IssueMut<'a, 'g> {
|
||||||
|
|
||||||
/// Lifecycle an issue.
|
/// Lifecycle an issue.
|
||||||
pub fn lifecycle<G: Signer>(&mut self, state: State, signer: &G) -> Result<OpId, Error> {
|
pub fn lifecycle<G: Signer>(&mut self, state: State, signer: &G) -> Result<OpId, Error> {
|
||||||
let action = Action::Lifecycle { state };
|
self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state))
|
||||||
self.apply("Lifecycle", action, signer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comment on an issue.
|
/// Comment on an issue.
|
||||||
pub fn comment<G: Signer, S: Into<String>>(
|
pub fn comment<G: Signer, S: ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
body: S,
|
body: S,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<CommentId, Error> {
|
) -> Result<CommentId, Error> {
|
||||||
let body = body.into();
|
self.transaction("Comment", signer, |tx| tx.comment(body))
|
||||||
let action = Action::from(thread::Action::Comment {
|
|
||||||
body,
|
|
||||||
reply_to: None,
|
|
||||||
});
|
|
||||||
self.apply("Comment", action, signer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tag an issue.
|
/// Tag an issue.
|
||||||
|
|
@ -236,29 +285,18 @@ impl<'a, 'g> IssueMut<'a, 'g> {
|
||||||
remove: impl IntoIterator<Item = Tag>,
|
remove: impl IntoIterator<Item = Tag>,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<OpId, Error> {
|
) -> Result<OpId, Error> {
|
||||||
let add = add.into_iter().collect::<Vec<_>>();
|
self.transaction("Tag", signer, |tx| tx.tag(add, remove))
|
||||||
let remove = remove.into_iter().collect::<Vec<_>>();
|
|
||||||
let action = Action::Tag { add, remove };
|
|
||||||
|
|
||||||
self.apply("Tag", action, signer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reply to on an issue comment.
|
/// Reply to on an issue comment.
|
||||||
pub fn reply<G: Signer, S: Into<String>>(
|
pub fn reply<G: Signer, S: ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
parent: CommentId,
|
parent: CommentId,
|
||||||
body: S,
|
body: S,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<OpId, Error> {
|
) -> Result<OpId, Error> {
|
||||||
let body = body.into();
|
|
||||||
|
|
||||||
assert!(self.thread.comment(&parent).is_some());
|
assert!(self.thread.comment(&parent).is_some());
|
||||||
|
self.transaction("Reply", signer, |tx| tx.reply(parent, body))
|
||||||
let action = Action::from(thread::Action::Comment {
|
|
||||||
body,
|
|
||||||
reply_to: Some(parent),
|
|
||||||
});
|
|
||||||
self.apply("Reply", action, signer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// React to an issue comment.
|
/// React to an issue comment.
|
||||||
|
|
@ -268,38 +306,27 @@ impl<'a, 'g> IssueMut<'a, 'g> {
|
||||||
reaction: Reaction,
|
reaction: Reaction,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<OpId, Error> {
|
) -> Result<OpId, Error> {
|
||||||
let action = Action::Thread {
|
self.transaction("React", signer, |tx| tx.react(to, reaction))
|
||||||
action: thread::Action::React {
|
|
||||||
to,
|
|
||||||
reaction,
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
self.apply("React", action, signer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply an op to the issue.
|
pub fn transaction<G, F, T>(
|
||||||
pub fn apply<G: Signer>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
msg: &'static str,
|
message: &str,
|
||||||
action: Action,
|
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<OpId, Error> {
|
operations: F,
|
||||||
let cob = self
|
) -> Result<T, Error>
|
||||||
.store
|
where
|
||||||
.update(self.id, msg, NonEmpty::new(action.clone()), signer)
|
G: Signer,
|
||||||
.map_err(Error::Store)?;
|
F: FnOnce(&mut Transaction<Issue>) -> T,
|
||||||
let clock = cob.history().clock().into();
|
{
|
||||||
let timestamp = cob.history().timestamp().into();
|
let mut tx = Transaction::new(*signer.public_key(), self.clock);
|
||||||
let op = Op {
|
let output = operations(&mut tx);
|
||||||
action,
|
let (ops, clock) = tx.commit(message, self.id, &mut self.store.raw, signer)?;
|
||||||
author: *signer.public_key(),
|
|
||||||
clock,
|
|
||||||
timestamp,
|
|
||||||
};
|
|
||||||
self.issue.apply([op])?;
|
|
||||||
|
|
||||||
Ok((clock, *signer.public_key()))
|
self.issue.apply(ops)?;
|
||||||
|
self.clock = clock;
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,28 +384,26 @@ impl<'a> Issues<'a> {
|
||||||
/// Create a new issue.
|
/// Create a new issue.
|
||||||
pub fn create<'g, G: Signer>(
|
pub fn create<'g, G: Signer>(
|
||||||
&'g mut self,
|
&'g mut self,
|
||||||
title: impl Into<String>,
|
title: impl ToString,
|
||||||
description: impl Into<String>,
|
description: impl ToString,
|
||||||
tags: &[Tag],
|
tags: &[Tag],
|
||||||
signer: &G,
|
signer: &G,
|
||||||
) -> Result<IssueMut<'a, 'g>, Error> {
|
) -> Result<IssueMut<'a, 'g>, Error> {
|
||||||
let title = title.into();
|
let (id, issue, clock) =
|
||||||
let description = description.into();
|
Transaction::initial("Create issue", &mut self.raw, signer, |tx| {
|
||||||
let action = Action::Title { title };
|
tx.title(title);
|
||||||
let (id, issue, clock) = self
|
tx.comment(description);
|
||||||
.raw
|
tx.tag(tags.to_owned(), []);
|
||||||
.create("Create issue", NonEmpty::new(action), signer)?;
|
})?;
|
||||||
let mut issue = IssueMut {
|
// Just a sanity check that our clock is advancing as expected.
|
||||||
|
assert_eq!(clock.get(), 2);
|
||||||
|
|
||||||
|
Ok(IssueMut {
|
||||||
id,
|
id,
|
||||||
clock,
|
clock,
|
||||||
issue,
|
issue,
|
||||||
store: self,
|
store: self,
|
||||||
};
|
})
|
||||||
|
|
||||||
issue.comment(description, signer)?;
|
|
||||||
issue.tag(tags.to_owned(), [], signer)?;
|
|
||||||
|
|
||||||
Ok(issue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an issue.
|
/// Remove an issue.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue