From 432db1ce5ba45646dacf0e86f2a9389da1d729ca Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 10 Jan 2025 15:57:12 +0100 Subject: [PATCH] Make `Operation` generic, and handle patch operations So far we only supported issue operations on the `activity_by_id` command. With this commit we separate the commands into a `activity_by_issue` and `activity_by_patch` command that calls a generic `activity_by_id` trait implementation. `Operation` now takes as a generic either `cob/issue/Action.ts` or `cob/patch/Action.ts`. Also `Operation` now stores the array of all `Action` that happened on said operation. I'm not entirely sure if this will help us in the future, but it represents more closely the state of the relation between an `Operation` and the cob `Actions`. --- crates/radicle-tauri/src/commands/cob.rs | 12 ---- .../radicle-tauri/src/commands/cob/issue.rs | 11 ++++ .../radicle-tauri/src/commands/cob/patch.rs | 11 ++++ crates/radicle-tauri/src/lib.rs | 3 +- .../radicle-types/bindings/cob/Operation.ts | 13 ++++ .../bindings/cob/issue/Operation.ts | 27 -------- crates/radicle-types/src/cobs.rs | 16 +++++ crates/radicle-types/src/cobs/issue.rs | 15 ----- crates/radicle-types/src/error.rs | 4 ++ crates/radicle-types/src/traits/cobs.rs | 43 +++++++------ crates/test-http-api/src/api.rs | 32 +++++++--- .../IssueTimelineLifecycleAction.svelte | 14 +++-- .../PatchTimelineLifecycleAction.svelte | 28 +++++++++ src/views/repo/Issue.svelte | 46 +++++++------- src/views/repo/Patch.svelte | 15 +++++ src/views/repo/router.ts | 62 ++++++++++++------- 16 files changed, 218 insertions(+), 134 deletions(-) create mode 100644 crates/radicle-types/bindings/cob/Operation.ts delete mode 100644 crates/radicle-types/bindings/cob/issue/Operation.ts create mode 100644 src/components/PatchTimelineLifecycleAction.svelte diff --git a/crates/radicle-tauri/src/commands/cob.rs b/crates/radicle-tauri/src/commands/cob.rs index 642bf22..5f918a0 100644 --- a/crates/radicle-tauri/src/commands/cob.rs +++ b/crates/radicle-tauri/src/commands/cob.rs @@ -2,12 +2,10 @@ use std::path::PathBuf; use anyhow::{Context, Result}; -use radicle::cob; use radicle::git; use radicle::identity; use radicle_types as types; use radicle_types::error::Error; -use radicle_types::traits::cobs::Cobs; use radicle_types::traits::thread::Thread; use tauri_plugin_clipboard_manager::ClipboardExt; use tauri_plugin_dialog::DialogExt; @@ -84,13 +82,3 @@ pub async fn save_embed_to_disk( ctx.save_embed_to_disk(rid, oid, path) } - -#[tauri::command] -pub fn activity_by_id( - ctx: tauri::State, - rid: identity::RepoId, - type_name: cob::TypeName, - id: git::Oid, -) -> Result, Error> { - ctx.activity_by_id(rid, type_name, id) -} diff --git a/crates/radicle-tauri/src/commands/cob/issue.rs b/crates/radicle-tauri/src/commands/cob/issue.rs index 8077be3..d07e4e7 100644 --- a/crates/radicle-tauri/src/commands/cob/issue.rs +++ b/crates/radicle-tauri/src/commands/cob/issue.rs @@ -1,8 +1,10 @@ use radicle::git; use radicle::identity; +use radicle::issue::{Action, TYPENAME}; use radicle_types as types; use radicle_types::error::Error; +use radicle_types::traits::cobs::Cobs; use radicle_types::traits::issue::Issues; use radicle_types::traits::issue::IssuesMut; @@ -55,3 +57,12 @@ pub(crate) fn comment_threads_by_issue_id( ) -> Result>, Error> { ctx.comment_threads_by_issue_id(rid, id) } + +#[tauri::command] +pub fn activity_by_issue( + ctx: tauri::State, + rid: identity::RepoId, + id: git::Oid, +) -> 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 38b4874..ebadb42 100644 --- a/crates/radicle-tauri/src/commands/cob/patch.rs +++ b/crates/radicle-tauri/src/commands/cob/patch.rs @@ -3,8 +3,10 @@ use radicle::git; use radicle::identity; use radicle::patch; +use radicle::patch::{Action, TYPENAME}; use radicle_types as types; use radicle_types::error::Error; +use radicle_types::traits::cobs::Cobs; use radicle_types::traits::patch::Patches; use radicle_types::traits::patch::PatchesMut; @@ -109,3 +111,12 @@ pub fn edit_patch( ) -> Result { ctx.edit_patch(rid, cob_id, action, opts) } + +#[tauri::command] +pub fn activity_by_patch( + ctx: tauri::State, + rid: identity::RepoId, + id: git::Oid, +) -> Result>, Error> { + ctx.activity_by_id(rid, &TYPENAME, id) +} diff --git a/crates/radicle-tauri/src/lib.rs b/crates/radicle-tauri/src/lib.rs index 13b67c3..800998c 100644 --- a/crates/radicle-tauri/src/lib.rs +++ b/crates/radicle-tauri/src/lib.rs @@ -78,15 +78,16 @@ pub fn run() { diff::get_diff, cob::get_embed, cob::save_embed_to_disk, - cob::activity_by_id, cob::save_embed_by_path, cob::save_embed_by_clipboard, cob::save_embed_by_bytes, + cob::issue::activity_by_issue, cob::issue::list_issues, cob::issue::issue_by_id, cob::issue::comment_threads_by_issue_id, cob::issue::create_issue, cob::issue::edit_issue, + cob::patch::activity_by_patch, cob::patch::list_patches, cob::patch::patch_by_id, cob::patch::edit_patch, diff --git a/crates/radicle-types/bindings/cob/Operation.ts b/crates/radicle-types/bindings/cob/Operation.ts new file mode 100644 index 0000000..f8dcd98 --- /dev/null +++ b/crates/radicle-types/bindings/cob/Operation.ts @@ -0,0 +1,13 @@ +// 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"; + +/** + * Everything that can be done in the system is represented by an `Op`. + * Operations are applied to an accumulator to yield a final state. + */ +export type Operation = { + id: string; + actions: Array; + author: Author; + timestamp: number; +}; diff --git a/crates/radicle-types/bindings/cob/issue/Operation.ts b/crates/radicle-types/bindings/cob/issue/Operation.ts deleted file mode 100644 index b6d4196..0000000 --- a/crates/radicle-types/bindings/cob/issue/Operation.ts +++ /dev/null @@ -1,27 +0,0 @@ -// 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 Operation = - & { entryId: string; timestamp: number; author: Author } - & ( - | { "type": "assign"; assignees: Array } - | { "type": "edit"; title: string } - | { "type": "lifecycle"; state: State } - | { "type": "label"; labels: Array } - | { - "type": "comment"; - body: string; - replyTo?: string; - embeds?: Array; - } - | { - "type": "comment.edit"; - id: string; - body: string; - embeds?: Array; - } - | { "type": "comment.redact"; id: string } - | { "type": "comment.react"; id: string; reaction: string; active: boolean } - ); diff --git a/crates/radicle-types/src/cobs.rs b/crates/radicle-types/src/cobs.rs index 43961e5..f3ab981 100644 --- a/crates/radicle-types/src/cobs.rs +++ b/crates/radicle-types/src/cobs.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use ts_rs::TS; +use radicle::cob; use radicle::identity; use radicle::node::{Alias, AliasStore}; @@ -31,6 +32,21 @@ impl Author { } } +/// Everything that can be done in the system is represented by an `Op`. +/// Operations are applied to an accumulator to yield a final state. +#[derive(Debug, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[ts(export_to = "cob/")] +pub struct Operation { + #[ts(as = "String")] + pub id: cob::EntryId, + pub actions: Vec, + pub author: Author, + #[ts(type = "number")] + pub timestamp: cob::Timestamp, +} + #[derive(Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] diff --git a/crates/radicle-types/src/cobs/issue.rs b/crates/radicle-types/src/cobs/issue.rs index 85e65b8..802753d 100644 --- a/crates/radicle-types/src/cobs/issue.rs +++ b/crates/radicle-types/src/cobs/issue.rs @@ -1,6 +1,5 @@ use std::collections::BTreeSet; -use radicle::git; use radicle::node::AliasStore; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -55,20 +54,6 @@ impl Issue { } } -#[derive(Serialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export)] -#[ts(export_to = "cob/issue/")] -pub struct Operation { - #[ts(as = "String")] - pub entry_id: git::Oid, - #[serde(flatten)] - pub action: Action, - #[ts(type = "number")] - pub timestamp: cob::Timestamp, - pub author: cobs::Author, -} - #[derive(Default, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase", tag = "status")] #[ts(export)] diff --git a/crates/radicle-types/src/error.rs b/crates/radicle-types/src/error.rs index 54740b8..e5b539c 100644 --- a/crates/radicle-types/src/error.rs +++ b/crates/radicle-types/src/error.rs @@ -9,6 +9,10 @@ pub enum Error { #[error(transparent)] Profile(#[from] radicle::profile::Error), + /// CobStore error. + #[error(transparent)] + CobStore(#[from] radicle::cob::store::Error), + /// Anyhow error. #[error(transparent)] Anyhow(#[from] anyhow::Error), diff --git a/crates/radicle-types/src/traits/cobs.rs b/crates/radicle-types/src/traits/cobs.rs index a82fa4a..5164e8e 100644 --- a/crates/radicle-types/src/traits/cobs.rs +++ b/crates/radicle-types/src/traits/cobs.rs @@ -2,39 +2,42 @@ use radicle::cob::object::Storage; use radicle::storage::refs::draft; use radicle::storage::{self, ReadStorage}; use radicle::{cob, git, identity}; +use serde::de::DeserializeOwned; use crate::error::Error; use crate::traits::Profile; pub trait Cobs: Profile { - fn activity_by_id( + #[allow(clippy::unnecessary_filter_map)] + fn activity_by_id( &self, rid: identity::RepoId, - type_name: cob::TypeName, + 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)?; - let ops = cob::store::ops(&id.into(), &type_name, &repo).unwrap(); - let mut actions: Vec = Vec::new(); - - for op in ops.into_iter() { - actions.extend(op.actions.iter().filter_map( - |action: &Vec| -> Option { - let action: crate::cobs::issue::Action = serde_json::from_slice(action).ok()?; + let iter = cob::store::ops(&id.into(), type_name, &repo)?; + let ops = iter + .into_iter() + .filter_map(|op| { + let actions = op + .actions + .iter() + .filter_map(|a| serde_json::from_slice(a).ok()) + .collect::>(); - Some(crate::cobs::issue::Operation { - entry_id: op.id, - action, - author: crate::cobs::Author::new(&op.author.into(), &aliases), - timestamp: op.timestamp, - }) - }, - )) - } + Some(crate::cobs::Operation { + id: op.id, + actions, + author: crate::cobs::Author::new(&op.author.into(), &aliases), + timestamp: op.timestamp, + }) + }) + .collect::>(); - Ok::<_, Error>(actions) + Ok::<_, Error>(ops) } fn publish_draft( diff --git a/crates/test-http-api/src/api.rs b/crates/test-http-api/src/api.rs index 734b68f..16e5d59 100644 --- a/crates/test-http-api/src/api.rs +++ b/crates/test-http-api/src/api.rs @@ -8,13 +8,14 @@ use axum::routing::post; use axum::Router; use hyper::header::CONTENT_TYPE; use hyper::Method; -use radicle::cob::TypeName; use serde::{Deserialize, Serialize}; use tower_http::cors::{self, CorsLayer}; use radicle::{git, identity}; use radicle_types as types; -use radicle_types::cobs::issue::{Action, NewIssue}; +use radicle_types::cobs::issue; +use radicle_types::cobs::issue::NewIssue; +use radicle_types::cobs::patch; use radicle_types::cobs::CobOptions; use radicle_types::error::Error; use radicle_types::traits::auth::Auth; @@ -57,7 +58,14 @@ pub fn router(ctx: Context) -> Router { .route("/list_repos", post(repo_root_handler)) .route("/repo_by_id", post(repo_handler)) .route("/diff_stats", post(diff_stats_handler)) - .route("/activity_by_id", post(activity_handler)) + .route( + "/activity_by_issue", + post(activity_issue_handler::), + ) + .route( + "/activity_by_patch", + post(activity_patch_handler::), + ) .route("/get_diff", post(diff_handler)) .route("/list_issues", post(issues_handler)) .route("/create_issue", post(create_issue_handler)) @@ -206,7 +214,7 @@ async fn create_issue_comment_handler( struct EditIssuesBody { pub rid: identity::RepoId, pub cob_id: git::Oid, - pub action: Action, + pub action: issue::Action, pub opts: CobOptions, } @@ -228,15 +236,23 @@ async fn edit_issue_handler( #[serde(rename_all = "camelCase")] struct ActivityBody { pub rid: identity::RepoId, - pub type_name: TypeName, pub id: git::Oid, } -async fn activity_handler( +async fn activity_issue_handler( + State(ctx): State, + Json(ActivityBody { rid, id }): Json, +) -> impl IntoResponse { + let activity = ctx.activity_by_id::(rid, &radicle::cob::issue::TYPENAME, id)?; + + Ok::<_, Error>(Json(activity)) +} + +async fn activity_patch_handler( State(ctx): State, - Json(ActivityBody { rid, type_name, id }): Json, + Json(ActivityBody { rid, id }): Json, ) -> impl IntoResponse { - let activity = ctx.activity_by_id(rid, type_name, id)?; + let activity = ctx.activity_by_id::(rid, &radicle::cob::patch::TYPENAME, id)?; Ok::<_, Error>(Json(activity)) } diff --git a/src/components/IssueTimelineLifecycleAction.svelte b/src/components/IssueTimelineLifecycleAction.svelte index 4795071..e548fe4 100644 --- a/src/components/IssueTimelineLifecycleAction.svelte +++ b/src/components/IssueTimelineLifecycleAction.svelte @@ -1,5 +1,6 @@
- + changed status to - - {formatTimestamp(operation.timestamp)} + + {formatTimestamp(op.timestamp)}
diff --git a/src/components/PatchTimelineLifecycleAction.svelte b/src/components/PatchTimelineLifecycleAction.svelte new file mode 100644 index 0000000..dde07a8 --- /dev/null +++ b/src/components/PatchTimelineLifecycleAction.svelte @@ -0,0 +1,28 @@ + + + +
+
+ + changed status to + + {formatTimestamp(op.timestamp)} +
+
+
diff --git a/src/views/repo/Issue.svelte b/src/views/repo/Issue.svelte index bd85995..cbf6234 100644 --- a/src/views/repo/Issue.svelte +++ b/src/views/repo/Issue.svelte @@ -1,9 +1,10 @@