httpd: Add reactions to revisions, improve json for comment reactions
Instead of using tuples and lots of arrays, this commit tries to use `BTreeMap`s to provide more context to review, issues, patch and other comments. Also the returned object by the httpd is more aligned with what is needed by the consumer, so less recomputation is needed for display.
This commit is contained in:
parent
3560e0066d
commit
5333f8217a
|
|
@ -1,10 +1,11 @@
|
|||
//! Utilities for building JSON responses of our API.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
|
||||
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||
use radicle::cob::CodeLocation;
|
||||
use radicle::cob::{CodeLocation, Reaction};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use radicle::cob::issue::{Issue, IssueId};
|
||||
|
|
@ -130,6 +131,15 @@ pub(crate) fn patch(
|
|||
"author": author(rev.author(), aliases.alias(rev.author().id())),
|
||||
"description": rev.description(),
|
||||
"edits": rev.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
|
||||
"reactions": rev.reactions().iter().flat_map(|(location, reaction)| {
|
||||
let reactions = reaction.iter().fold(BTreeMap::new(), |mut acc: BTreeMap<&Reaction, Vec<_>>, (author, emoji)| {
|
||||
acc.entry(emoji).or_default().push(author);
|
||||
acc
|
||||
});
|
||||
reactions.iter().map(|(emoji, authors)|
|
||||
json!({"location": location, "emoji": emoji, "authors": authors })
|
||||
).collect::<Vec<_>>()
|
||||
}).collect::<Vec<_>>(),
|
||||
"base": rev.base(),
|
||||
"oid": rev.head(),
|
||||
"refs": get_refs(repo, patch.author().id(), &rev.head()).unwrap_or_default(),
|
||||
|
|
@ -191,7 +201,9 @@ fn issue_comment(id: &CommentId, comment: &Comment, aliases: &impl AliasStore) -
|
|||
"body": comment.body(),
|
||||
"edits": comment.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
|
||||
"embeds": comment.embeds().to_vec(),
|
||||
"reactions": comment.reactions().collect::<Vec<_>>(),
|
||||
"reactions": comment.reactions().iter().map(|(emoji, authors)|
|
||||
json!({ "emoji": emoji, "authors": authors })
|
||||
).collect::<Vec<_>>(),
|
||||
"timestamp": comment.timestamp().as_secs(),
|
||||
"replyTo": comment.reply_to(),
|
||||
"resolved": comment.resolved(),
|
||||
|
|
@ -210,7 +222,9 @@ fn patch_comment(
|
|||
"body": comment.body(),
|
||||
"edits": comment.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
|
||||
"embeds": comment.embeds().to_vec(),
|
||||
"reactions": comment.reactions().collect::<Vec<_>>(),
|
||||
"reactions": comment.reactions().iter().map(|(emoji, authors)|
|
||||
json!({ "emoji": emoji, "authors": authors })
|
||||
).collect::<Vec<_>>(),
|
||||
"timestamp": comment.timestamp().as_secs(),
|
||||
"replyTo": comment.reply_to(),
|
||||
"location": comment.location(),
|
||||
|
|
@ -230,7 +244,9 @@ fn review_comment(
|
|||
"body": comment.body(),
|
||||
"edits": comment.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
|
||||
"embeds": comment.embeds().to_vec(),
|
||||
"reactions": comment.reactions().collect::<Vec<_>>(),
|
||||
"reactions": comment.reactions().iter().map(|(emoji, authors)|
|
||||
json!({ "emoji": emoji, "authors": authors })
|
||||
).collect::<Vec<_>>(),
|
||||
"timestamp": comment.timestamp().as_secs(),
|
||||
"replyTo": comment.reply_to(),
|
||||
"location": comment.location(),
|
||||
|
|
|
|||
|
|
@ -869,6 +869,12 @@ async fn patch_update_handler(
|
|||
patch.edit_revision(revision, description, embeds, &signer)?
|
||||
}
|
||||
patch::Action::RevisionRedact { revision } => patch.redact(revision, &signer)?,
|
||||
patch::Action::RevisionReact {
|
||||
revision,
|
||||
reaction,
|
||||
active,
|
||||
location,
|
||||
} => patch.react(revision, reaction, location, active, &signer)?,
|
||||
patch::Action::RevisionComment {
|
||||
revision,
|
||||
body,
|
||||
|
|
@ -903,9 +909,6 @@ async fn patch_update_handler(
|
|||
patch::Action::RevisionCommentRedact { revision, comment } => {
|
||||
patch.comment_redact(revision, comment, &signer)?
|
||||
}
|
||||
_ => {
|
||||
todo!();
|
||||
}
|
||||
};
|
||||
|
||||
announce_refs(node, repo.id())?;
|
||||
|
|
@ -2281,10 +2284,10 @@ mod routes {
|
|||
}
|
||||
],
|
||||
"reactions": [
|
||||
[
|
||||
"z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
|
||||
"🚀",
|
||||
],
|
||||
{
|
||||
"emoji": "🚀",
|
||||
"authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"]
|
||||
},
|
||||
],
|
||||
"timestamp": TIMESTAMP,
|
||||
"replyTo": null,
|
||||
|
|
@ -2433,6 +2436,7 @@ mod routes {
|
|||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"reactions": [],
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
|
|
@ -2485,6 +2489,7 @@ mod routes {
|
|||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"reactions": [],
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
|
|
@ -2576,6 +2581,7 @@ mod routes {
|
|||
"revisions": [
|
||||
{
|
||||
"id": CREATED_PATCH_ID,
|
||||
"reactions": [],
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
|
|
@ -2662,6 +2668,7 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
@ -2736,6 +2743,7 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
@ -2809,6 +2817,7 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
@ -2834,6 +2843,7 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
@ -2893,6 +2903,7 @@ mod routes {
|
|||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"reactions": [],
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
|
|
@ -2943,6 +2954,52 @@ mod routes {
|
|||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"type": "revision.react",
|
||||
"revision": CONTRIBUTOR_PATCH_ID,
|
||||
"reaction": "🚀",
|
||||
"location": {
|
||||
"commit": INITIAL_COMMIT,
|
||||
"path": "./README.md",
|
||||
"new": {
|
||||
"type": "lines",
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"active": true,
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"type": "revision.react",
|
||||
"revision": CONTRIBUTOR_PATCH_ID,
|
||||
"reaction": "🙏",
|
||||
"location": null,
|
||||
"active": true,
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
|
|
@ -2987,6 +3044,29 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [
|
||||
{
|
||||
"location": null,
|
||||
"emoji": "🙏",
|
||||
"authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"],
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"commit": INITIAL_COMMIT,
|
||||
"path": "./README.md",
|
||||
"old": null,
|
||||
"new": {
|
||||
"type": "lines",
|
||||
"range": {
|
||||
"start": 0,
|
||||
"end": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"emoji": "🚀",
|
||||
"authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"]
|
||||
},
|
||||
],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
@ -3123,6 +3203,7 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
@ -3169,7 +3250,12 @@ mod routes {
|
|||
"content": "git:94381b429d7f7fe87e1bade52d893ab348ae29cc",
|
||||
}
|
||||
],
|
||||
"reactions": [["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8","🚀"]],
|
||||
"reactions": [
|
||||
{
|
||||
"emoji": "🚀",
|
||||
"authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"]
|
||||
},
|
||||
],
|
||||
"timestamp": TIMESTAMP,
|
||||
"replyTo": null,
|
||||
"location": null,
|
||||
|
|
@ -3350,6 +3436,7 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
@ -3406,10 +3493,10 @@ mod routes {
|
|||
},
|
||||
],
|
||||
"reactions": [
|
||||
[
|
||||
"z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8",
|
||||
"🚀",
|
||||
],
|
||||
{
|
||||
"emoji": "🚀",
|
||||
"authors": ["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8"],
|
||||
},
|
||||
],
|
||||
"timestamp": 1671125284,
|
||||
"replyTo": null,
|
||||
|
|
@ -3506,6 +3593,7 @@ mod routes {
|
|||
"embeds": [],
|
||||
},
|
||||
],
|
||||
"reactions": [],
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"refs": [
|
||||
|
|
|
|||
|
|
@ -1167,9 +1167,10 @@ mod test {
|
|||
|
||||
let id = issue.id;
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
let (_, r) = issue.comment(&comment).unwrap().reactions().next().unwrap();
|
||||
let reactions = issue.comment(&comment).unwrap().reactions();
|
||||
let authors = reactions.get(&reaction).unwrap();
|
||||
|
||||
assert_eq!(r, &reaction);
|
||||
assert_eq!(authors.first().unwrap(), &node.signer.public_key());
|
||||
|
||||
// TODO: Test multiple reactions from same author and different authors
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1332,6 +1332,10 @@ impl Revision {
|
|||
&self.description.last().embeds
|
||||
}
|
||||
|
||||
pub fn reactions(&self) -> &BTreeMap<Option<CodeLocation>, BTreeSet<(PublicKey, Reaction)>> {
|
||||
&self.reactions
|
||||
}
|
||||
|
||||
/// Author of the revision.
|
||||
pub fn author(&self) -> &Author {
|
||||
&self.author
|
||||
|
|
@ -1593,6 +1597,22 @@ impl<R: ReadRepository> store::Transaction<Patch, R> {
|
|||
})
|
||||
}
|
||||
|
||||
/// React on a patch revision.
|
||||
pub fn react(
|
||||
&mut self,
|
||||
revision: RevisionId,
|
||||
reaction: Reaction,
|
||||
location: Option<CodeLocation>,
|
||||
active: bool,
|
||||
) -> Result<(), store::Error> {
|
||||
self.push(Action::RevisionReact {
|
||||
revision,
|
||||
reaction,
|
||||
location,
|
||||
active,
|
||||
})
|
||||
}
|
||||
|
||||
/// Comment on a patch revision.
|
||||
pub fn comment<S: ToString>(
|
||||
&mut self,
|
||||
|
|
@ -1924,6 +1944,20 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
/// React on a patch revision.
|
||||
pub fn react<G: Signer>(
|
||||
&mut self,
|
||||
revision: RevisionId,
|
||||
reaction: Reaction,
|
||||
location: Option<CodeLocation>,
|
||||
active: bool,
|
||||
signer: &G,
|
||||
) -> Result<EntryId, Error> {
|
||||
self.transaction("React", signer, |tx| {
|
||||
tx.react(revision, reaction, location, active)
|
||||
})
|
||||
}
|
||||
|
||||
/// Edit a comment on a patch revision.
|
||||
pub fn comment_edit<G: Signer, S: ToString>(
|
||||
&mut self,
|
||||
|
|
|
|||
|
|
@ -196,8 +196,13 @@ impl<L> Comment<L> {
|
|||
}
|
||||
|
||||
/// Comment reactions.
|
||||
pub fn reactions(&self) -> impl Iterator<Item = (&ActorId, &Reaction)> {
|
||||
self.reactions.iter().map(|(a, r)| (a, r))
|
||||
pub fn reactions(&self) -> BTreeMap<&Reaction, Vec<&ActorId>> {
|
||||
self.reactions
|
||||
.iter()
|
||||
.fold(BTreeMap::new(), |mut acc, (author, reaction)| {
|
||||
acc.entry(reaction).or_default().push(author);
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
/// Get comment location, if any.
|
||||
|
|
|
|||
Loading…
Reference in New Issue