diff --git a/crates/radicle-tauri/src/commands/cob/issue.rs b/crates/radicle-tauri/src/commands/cob/issue.rs index d07e4e777..2a003d5ca 100644 --- a/crates/radicle-tauri/src/commands/cob/issue.rs +++ b/crates/radicle-tauri/src/commands/cob/issue.rs @@ -1,7 +1,7 @@ use radicle::git; use radicle::identity; -use radicle::issue::{Action, TYPENAME}; +use radicle::issue::TYPENAME; use radicle_types as types; use radicle_types::error::Error; use radicle_types::traits::cobs::Cobs; @@ -63,6 +63,6 @@ pub fn activity_by_issue( ctx: tauri::State, rid: identity::RepoId, id: git::Oid, -) -> Result>, Error> { +) -> Result>, Error> { ctx.activity_by_id(rid, &TYPENAME, id) } diff --git a/crates/radicle-tauri/src/commands/cob/patch.rs b/crates/radicle-tauri/src/commands/cob/patch.rs index 5e83844cc..707b0abc8 100644 --- a/crates/radicle-tauri/src/commands/cob/patch.rs +++ b/crates/radicle-tauri/src/commands/cob/patch.rs @@ -1,4 +1,4 @@ -use radicle::patch::{Action, TYPENAME}; +use radicle::patch::TYPENAME; use radicle::{cob, git, identity}; use radicle_types as types; @@ -105,6 +105,6 @@ pub fn activity_by_patch( ctx: tauri::State, rid: identity::RepoId, id: git::Oid, -) -> Result>, Error> { +) -> Result>, Error> { ctx.activity_by_id(rid, &TYPENAME, id) } diff --git a/crates/radicle-types/bindings/cob/issue/Action.ts b/crates/radicle-types/bindings/cob/issue/Action.ts index e87a2ff4a..1d7e7e315 100644 --- a/crates/radicle-types/bindings/cob/issue/Action.ts +++ b/crates/radicle-types/bindings/cob/issue/Action.ts @@ -1,9 +1,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Author } from "../Author"; import type { Embed } from "../thread/Embed"; import type { State } from "./State"; export type Action = - | { "type": "assign"; assignees: Array } + | { "type": "assign"; assignees: Array } | { "type": "edit"; title: string } | { "type": "lifecycle"; state: State } | { "type": "label"; labels: Array } diff --git a/crates/radicle-types/bindings/cob/patch/Action.ts b/crates/radicle-types/bindings/cob/patch/Action.ts index 42207a5f6..5daf64c45 100644 --- a/crates/radicle-types/bindings/cob/patch/Action.ts +++ b/crates/radicle-types/bindings/cob/patch/Action.ts @@ -1,4 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Author } from "../Author"; import type { CodeLocation } from "../thread/CodeLocation"; import type { Embed } from "../thread/Embed"; import type { Verdict } from "./Verdict"; @@ -7,7 +8,7 @@ export type Action = | { "type": "edit"; title: string; target: string } | { "type": "label"; labels: Array } | { "type": "lifecycle"; state: { status: "draft" | "open" | "archived" } } - | { "type": "assign"; assignees: Array } + | { "type": "assign"; assignees: Array } | { "type": "merge"; revision: string; commit: string } | { "type": "review"; diff --git a/crates/radicle-types/src/cobs.rs b/crates/radicle-types/src/cobs.rs index 52653b0c3..0a6a2e860 100644 --- a/crates/radicle-types/src/cobs.rs +++ b/crates/radicle-types/src/cobs.rs @@ -1,3 +1,4 @@ +use radicle::profile::Aliases; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -11,7 +12,7 @@ pub mod repo; pub mod stream; pub mod thread; -#[derive(Debug, Clone, Serialize, TS, Deserialize)] +#[derive(Debug, Clone, Serialize, TS, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "camelCase")] #[ts(export)] #[ts(export_to = "cob/")] @@ -30,6 +31,14 @@ impl Author { alias: aliases.alias(did), } } + + pub fn did(&self) -> &identity::Did { + &self.did + } +} + +pub trait FromRadicleAction { + fn from_radicle_action(value: A, aliases: &Aliases) -> Self; } /// Everything that can be done in the system is represented by an `Op`. diff --git a/crates/radicle-types/src/cobs/issue.rs b/crates/radicle-types/src/cobs/issue.rs index 6dc4f65db..b9c6cdc9b 100644 --- a/crates/radicle-types/src/cobs/issue.rs +++ b/crates/radicle-types/src/cobs/issue.rs @@ -10,6 +10,9 @@ use radicle::issue; use crate::cobs; +use super::Author; +use super::FromRadicleAction; + #[derive(TS, Serialize)] #[ts(export)] #[ts(export_to = "cob/issue/")] @@ -136,10 +139,7 @@ pub struct NewIssue { #[ts(export_to = "cob/issue/")] pub enum Action { #[serde(rename = "assign")] - Assign { - #[ts(as = "Vec")] - assignees: BTreeSet, - }, + Assign { assignees: BTreeSet }, #[serde(rename = "edit")] Edit { title: String }, @@ -189,3 +189,48 @@ pub enum Action { active: bool, }, } + +impl FromRadicleAction for Action { + fn from_radicle_action( + value: radicle::issue::Action, + aliases: &radicle::profile::Aliases, + ) -> Self { + match value { + radicle::issue::Action::Assign { assignees } => Self::Assign { + assignees: assignees + .iter() + .map(|a| Author::new(a, aliases)) + .collect::>(), + }, + radicle::issue::Action::Comment { + body, + reply_to, + embeds, + } => Self::Comment { + body, + reply_to, + embeds: embeds.into_iter().map(Into::into).collect::>(), + }, + radicle::issue::Action::CommentEdit { id, body, embeds } => Self::CommentEdit { + id, + body, + embeds: embeds.into_iter().map(Into::into).collect::>(), + }, + radicle::issue::Action::CommentReact { + id, + reaction, + active, + } => Self::CommentReact { + id, + reaction, + active, + }, + radicle::issue::Action::CommentRedact { id } => Self::CommentRedact { id }, + radicle::issue::Action::Label { labels } => Self::Label { labels }, + radicle::issue::Action::Lifecycle { state } => Self::Lifecycle { + state: state.into(), + }, + radicle::issue::Action::Edit { title } => Self::Edit { title }, + } + } +} diff --git a/crates/radicle-types/src/domain/patch/models/patch.rs b/crates/radicle-types/src/domain/patch/models/patch.rs index 4b5dce1bf..5152aee34 100644 --- a/crates/radicle-types/src/domain/patch/models/patch.rs +++ b/crates/radicle-types/src/domain/patch/models/patch.rs @@ -2,15 +2,17 @@ use std::collections::BTreeMap; use std::collections::BTreeSet; use radicle::node::AliasStore; +use radicle::profile::Aliases; use serde::{Deserialize, Serialize}; use ts_rs::TS; use radicle::cob; use radicle::git; -use radicle::identity; use radicle::patch; use crate::cobs; +use crate::cobs::Author; +use crate::cobs::FromRadicleAction; #[derive(Debug, TS, Serialize)] #[ts(export)] @@ -329,10 +331,7 @@ pub enum Action { state: patch::Lifecycle, }, #[serde(rename = "assign")] - Assign { - #[ts(as = "Vec")] - assignees: BTreeSet, - }, + Assign { assignees: BTreeSet }, #[serde(rename = "merge")] Merge { #[ts(as = "String")] @@ -512,3 +511,160 @@ pub enum Action { active: bool, }, } + +impl FromRadicleAction for Action { + fn from_radicle_action(value: radicle::patch::Action, aliases: &Aliases) -> Self { + match value { + radicle::patch::Action::ReviewRedact { review } => Self::ReviewRedact { review }, + radicle::patch::Action::RevisionCommentReact { + revision, + comment, + reaction, + active, + } => Self::RevisionCommentReact { + revision, + comment, + reaction, + active, + }, + radicle::patch::Action::RevisionCommentRedact { revision, comment } => { + Self::RevisionCommentRedact { revision, comment } + } + radicle::patch::Action::RevisionCommentEdit { + revision, + comment, + body, + embeds, + } => Self::RevisionCommentEdit { + revision, + comment, + body, + embeds: embeds.into_iter().map(Into::into).collect::>(), + }, + radicle::patch::Action::RevisionComment { + revision, + location, + body, + reply_to, + embeds, + } => Self::RevisionComment { + revision, + location: location.map(Into::into), + body, + reply_to, + embeds: embeds.into_iter().map(Into::into).collect::>(), + }, + radicle::patch::Action::RevisionRedact { revision } => { + Self::RevisionRedact { revision } + } + radicle::patch::Action::RevisionReact { + revision, + location, + reaction, + active, + } => Self::RevisionReact { + revision, + location: location.map(Into::into), + reaction, + active, + }, + radicle::patch::Action::RevisionEdit { + revision, + description, + embeds, + } => Self::RevisionEdit { + revision, + description, + embeds: embeds.into_iter().map(Into::into).collect::>(), + }, + radicle::patch::Action::Assign { assignees } => Self::Assign { + assignees: assignees + .iter() + .map(|a| Author::new(a, aliases)) + .collect::>(), + }, + radicle::patch::Action::Edit { title, target } => Self::Edit { title, target }, + radicle::patch::Action::Label { labels } => Self::Label { labels }, + radicle::patch::Action::Lifecycle { state } => Self::Lifecycle { state }, + radicle::patch::Action::Merge { revision, commit } => Self::Merge { revision, commit }, + radicle::patch::Action::Revision { + description, + base, + oid, + resolves, + } => Self::Revision { + description, + base, + oid, + resolves, + }, + + radicle::patch::Action::Review { + revision, + summary, + verdict, + labels, + } => Self::Review { + revision, + summary, + verdict: verdict.map(Into::into), + labels, + }, + radicle::patch::Action::ReviewComment { + review, + body, + location, + reply_to, + embeds, + } => Self::ReviewComment { + review, + body, + location: location.map(Into::into), + reply_to, + embeds: embeds.into_iter().map(Into::into).collect::>(), + }, + radicle::patch::Action::ReviewCommentEdit { + review, + comment, + body, + embeds, + } => Self::ReviewCommentEdit { + review, + comment, + body, + embeds: embeds.into_iter().map(Into::into).collect::>(), + }, + radicle::patch::Action::ReviewCommentReact { + review, + comment, + reaction, + active, + } => Self::ReviewCommentReact { + review, + comment, + reaction, + active, + }, + radicle::patch::Action::ReviewCommentRedact { review, comment } => { + Self::ReviewCommentRedact { review, comment } + } + radicle::patch::Action::ReviewCommentResolve { review, comment } => { + Self::ReviewCommentResolve { review, comment } + } + radicle::patch::Action::ReviewCommentUnresolve { review, comment } => { + Self::ReviewCommentUnresolve { review, comment } + } + radicle::patch::Action::ReviewEdit { + review, + summary, + verdict, + labels, + } => Self::ReviewEdit { + review, + summary, + verdict: verdict.map(Into::into), + labels, + }, + } + } +} diff --git a/crates/radicle-types/src/traits/cobs.rs b/crates/radicle-types/src/traits/cobs.rs index d98674293..35d35aea1 100644 --- a/crates/radicle-types/src/traits/cobs.rs +++ b/crates/radicle-types/src/traits/cobs.rs @@ -2,17 +2,18 @@ use radicle::storage::ReadStorage; use radicle::{cob, git, identity}; use serde::de::DeserializeOwned; +use crate::cobs::FromRadicleAction; use crate::error::Error; use crate::traits::Profile; pub trait Cobs: Profile { #[allow(clippy::unnecessary_filter_map)] - fn activity_by_id( + fn activity_by_id>( &self, rid: identity::RepoId, type_name: &cob::TypeName, id: git::Oid, - ) -> Result>, Error> { + ) -> Result>, Error> { let profile = self.profile(); let aliases = profile.aliases(); let repo = profile.storage.repository(rid)?; @@ -23,7 +24,16 @@ pub trait Cobs: Profile { let actions = op .actions .iter() - .filter_map(|a| serde_json::from_slice(a).ok()) + .filter_map(|a| { + if let Ok(r) = serde_json::from_slice::(a) { + let x = B::from_radicle_action(r, &aliases); + Some(x) + } else { + log::error!("Not able to deserialize the action"); + + None + } + }) .collect::>(); Some(crate::cobs::Operation { diff --git a/crates/radicle-types/src/traits/issue.rs b/crates/radicle-types/src/traits/issue.rs index 32ed926fe..6d6d9b75a 100644 --- a/crates/radicle-types/src/traits/issue.rs +++ b/crates/radicle-types/src/traits/issue.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use radicle::issue::cache::Issues as _; use radicle::node::Handle; use radicle::storage::ReadStorage; @@ -142,7 +144,10 @@ pub trait IssuesMut: Profile { issue.lifecycle(state.into(), &signer)?; } cobs::issue::Action::Assign { assignees } => { - issue.assign(assignees, &signer)?; + issue.assign( + assignees.iter().map(|a| *a.did()).collect::>(), + &signer, + )?; } cobs::issue::Action::Label { labels } => { issue.label(labels, &signer)?; diff --git a/crates/radicle-types/src/traits/patch.rs b/crates/radicle-types/src/traits/patch.rs index f5a1bbfcf..a5e1a76ab 100644 --- a/crates/radicle-types/src/traits/patch.rs +++ b/crates/radicle-types/src/traits/patch.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use radicle::node::Handle; use radicle::patch::cache::Patches as _; use radicle::storage::ReadStorage; @@ -198,7 +200,10 @@ pub trait PatchesMut: Profile { patch.lifecycle(state, &signer)?; } models::patch::Action::Assign { assignees } => { - patch.assign(assignees, &signer)?; + patch.assign( + assignees.iter().map(|a| *a.did()).collect::>(), + &signer, + )?; } models::patch::Action::Label { labels } => { patch.label(labels, &signer)?; diff --git a/crates/test-http-api/src/api.rs b/crates/test-http-api/src/api.rs index 118a1aa89..223a0c5c3 100644 --- a/crates/test-http-api/src/api.rs +++ b/crates/test-http-api/src/api.rs @@ -13,10 +13,10 @@ use tower_http::cors::{self, CorsLayer}; use radicle::{git, identity}; use radicle_types as types; -use radicle_types::cobs; use radicle_types::cobs::issue; use radicle_types::cobs::issue::NewIssue; use radicle_types::cobs::CobOptions; +use radicle_types::cobs::{self, FromRadicleAction}; use radicle_types::domain::inbox::models::notification::NotificationCount; use radicle_types::domain::patch::models; use radicle_types::domain::patch::service::Service; @@ -71,11 +71,11 @@ pub fn router(ctx: Context) -> Router { .route("/diff_stats", post(diff_stats_handler)) .route( "/activity_by_issue", - post(activity_issue_handler::), + post(activity_issue_handler::), ) .route( "/activity_by_patch", - post(activity_patch_handler::), + post(activity_patch_handler::), ) .route("/get_diff", post(diff_handler)) .route("/list_issues", post(issues_handler)) @@ -259,20 +259,26 @@ struct ActivityBody { pub id: git::Oid, } -async fn activity_issue_handler( +async fn activity_issue_handler< + A: serde::Serialize + serde::de::DeserializeOwned, + B: FromRadicleAction + serde::Serialize, +>( State(ctx): State, Json(ActivityBody { rid, id }): Json, ) -> impl IntoResponse { - let activity = ctx.activity_by_id::(rid, &radicle::cob::issue::TYPENAME, id)?; + let activity = ctx.activity_by_id::(rid, &radicle::cob::issue::TYPENAME, id)?; Ok::<_, Error>(Json(activity)) } -async fn activity_patch_handler( +async fn activity_patch_handler< + A: serde::Serialize + serde::de::DeserializeOwned, + B: FromRadicleAction + serde::Serialize, +>( State(ctx): State, Json(ActivityBody { rid, id }): Json, ) -> impl IntoResponse { - let activity = ctx.activity_by_id::(rid, &radicle::cob::patch::TYPENAME, id)?; + let activity = ctx.activity_by_id::(rid, &radicle::cob::patch::TYPENAME, id)?; Ok::<_, Error>(Json(activity)) } diff --git a/src/components/IssueTimeline.svelte b/src/components/IssueTimeline.svelte index f46939099..6b329da21 100644 --- a/src/components/IssueTimeline.svelte +++ b/src/components/IssueTimeline.svelte @@ -18,11 +18,9 @@ formatTimestamp, issueStatusColor, pluralize, - publicKeyFromDid, } from "@app/lib/utils"; import Icon from "./Icon.svelte"; import NodeId from "./NodeId.svelte"; - import { invoke } from "@app/lib/invoke"; import Id from "./Id.svelte"; interface Props { @@ -66,7 +64,7 @@ return result; } - function itemDiff(previousState: string[], newState: string[]) { + function itemDiff(previousState: A[], newState: A[]) { const removed = previousState.filter(x => !newState.includes(x)); const added = newState.filter(x => !previousState.includes(x)); return { removed, added }; @@ -169,7 +167,7 @@
{#if op.previous && op.previous.type === op.type} - {@const changed = itemDiff( + {@const changed = itemDiff( op.previous?.assignees ?? [], op.assignees, )} @@ -177,38 +175,20 @@ {#if changed.added.length} assigned {#each changed.added as assignee} - {#await invoke( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias} - - {/await} + {/each} {/if} {#if changed.removed.length} unassigned {#each changed.removed as assignee} - {#await invoke( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias} - - {/await} + {/each} {/if} {/if} {:else} assigned {#each op.assignees as assignee} - {#await invoke( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias} - - {/await} + {/each} {/if}
diff --git a/src/components/PatchTimeline.svelte b/src/components/PatchTimeline.svelte index fc3eabc6e..cbfc53f05 100644 --- a/src/components/PatchTimeline.svelte +++ b/src/components/PatchTimeline.svelte @@ -18,12 +18,10 @@ formatTimestamp, patchStatusColor, pluralize, - publicKeyFromDid, } from "@app/lib/utils"; import Icon from "./Icon.svelte"; import Id from "./Id.svelte"; import NodeId from "./NodeId.svelte"; - import { invoke } from "@app/lib/invoke"; interface Props { patchId: string; @@ -67,7 +65,7 @@ return result; } - function itemDiff(previousState: string[], newState: string[]) { + function itemDiff(previousState: A[], newState: A[]) { const removed = previousState.filter(x => !newState.includes(x)); const added = newState.filter(x => !previousState.includes(x)); return { removed, added }; @@ -190,44 +188,26 @@
{#if op.previous && op.previous.type === op.type} - {@const changed = itemDiff( + {@const changed = itemDiff( op.previous?.assignees ?? [], op.assignees, )} {#if changed.added.length} assigned {#each changed.added as assignee} - {#await invoke( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias} - - {/await} + {/each} {/if} {#if changed.removed.length} unassigned {#each changed.removed as assignee} - {#await invoke( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias} - - {/await} + {/each} {/if} {:else} assigned {#each op.assignees as assignee} - {#await invoke( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias} - - {/await} + {/each} {/if}
diff --git a/src/views/repo/Issue.svelte b/src/views/repo/Issue.svelte index 9388210e9..f420641f4 100644 --- a/src/views/repo/Issue.svelte +++ b/src/views/repo/Issue.svelte @@ -126,7 +126,7 @@ cobId: issue.id, action: { type: "assign", - assignees: assignees.map(a => a.did), + assignees, }, opts: { announce: $nodeRunning && $announce }, }); diff --git a/src/views/repo/Patch.svelte b/src/views/repo/Patch.svelte index aafef4e97..3a27ffbb1 100644 --- a/src/views/repo/Patch.svelte +++ b/src/views/repo/Patch.svelte @@ -157,7 +157,7 @@ cobId: patch.id, action: { type: "assign", - assignees: assignees.map(a => a.did), + assignees, }, opts: { announce: $nodeRunning && $announce }, });