Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Convert cob actions into customized desktop actions
Sebastian Martinez committed 1 year ago
commit c730e821878ae36fb493103789c06e915cd26311
parent b1bf3c5
15 files changed +278 -80
modified 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<AppState>,
    rid: identity::RepoId,
    id: git::Oid,
-
) -> Result<Vec<types::cobs::Operation<Action>>, Error> {
+
) -> Result<Vec<types::cobs::Operation<types::cobs::issue::Action>>, Error> {
    ctx.activity_by_id(rid, &TYPENAME, id)
}
modified 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<AppState>,
    rid: identity::RepoId,
    id: git::Oid,
-
) -> Result<Vec<types::cobs::Operation<Action>>, Error> {
+
) -> Result<Vec<types::cobs::Operation<models::patch::Action>>, Error> {
    ctx.activity_by_id(rid, &TYPENAME, id)
}
modified 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<string> }
+
  | { "type": "assign"; assignees: Array<Author> }
  | { "type": "edit"; title: string }
  | { "type": "lifecycle"; state: State }
  | { "type": "label"; labels: Array<string> }
modified 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<string> }
  | { "type": "lifecycle"; state: { status: "draft" | "open" | "archived" } }
-
  | { "type": "assign"; assignees: Array<string> }
+
  | { "type": "assign"; assignees: Array<Author> }
  | { "type": "merge"; revision: string; commit: string }
  | {
    "type": "review";
modified 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<A> {
+
    fn from_radicle_action(value: A, aliases: &Aliases) -> Self;
}

/// Everything that can be done in the system is represented by an `Op`.
modified 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<String>")]
-
        assignees: BTreeSet<identity::Did>,
-
    },
+
    Assign { assignees: BTreeSet<Author> },

    #[serde(rename = "edit")]
    Edit { title: String },
@@ -189,3 +189,48 @@ pub enum Action {
        active: bool,
    },
}
+

+
impl FromRadicleAction<radicle::issue::Action> 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::<BTreeSet<_>>(),
+
            },
+
            radicle::issue::Action::Comment {
+
                body,
+
                reply_to,
+
                embeds,
+
            } => Self::Comment {
+
                body,
+
                reply_to,
+
                embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
+
            },
+
            radicle::issue::Action::CommentEdit { id, body, embeds } => Self::CommentEdit {
+
                id,
+
                body,
+
                embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
+
            },
+
            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 },
+
        }
+
    }
+
}
modified 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<String>")]
-
        assignees: BTreeSet<identity::Did>,
-
    },
+
    Assign { assignees: BTreeSet<cobs::Author> },
    #[serde(rename = "merge")]
    Merge {
        #[ts(as = "String")]
@@ -512,3 +511,160 @@ pub enum Action {
        active: bool,
    },
}
+

+
impl FromRadicleAction<radicle::patch::Action> 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::<Vec<_>>(),
+
            },
+
            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::<Vec<_>>(),
+
            },
+
            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::<Vec<_>>(),
+
            },
+
            radicle::patch::Action::Assign { assignees } => Self::Assign {
+
                assignees: assignees
+
                    .iter()
+
                    .map(|a| Author::new(a, aliases))
+
                    .collect::<BTreeSet<_>>(),
+
            },
+
            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::<Vec<_>>(),
+
            },
+
            radicle::patch::Action::ReviewCommentEdit {
+
                review,
+
                comment,
+
                body,
+
                embeds,
+
            } => Self::ReviewCommentEdit {
+
                review,
+
                comment,
+
                body,
+
                embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
+
            },
+
            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,
+
            },
+
        }
+
    }
+
}
modified 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<A: DeserializeOwned>(
+
    fn activity_by_id<A: DeserializeOwned, B: FromRadicleAction<A>>(
        &self,
        rid: identity::RepoId,
        type_name: &cob::TypeName,
        id: git::Oid,
-
    ) -> Result<Vec<crate::cobs::Operation<A>>, Error> {
+
    ) -> Result<Vec<crate::cobs::Operation<B>>, 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>(a) {
+
                            let x = B::from_radicle_action(r, &aliases);
+
                            Some(x)
+
                        } else {
+
                            log::error!("Not able to deserialize the action");
+

+
                            None
+
                        }
+
                    })
                    .collect::<Vec<_>>();

                Some(crate::cobs::Operation {
modified 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::<BTreeSet<_>>(),
+
                    &signer,
+
                )?;
            }
            cobs::issue::Action::Label { labels } => {
                issue.label(labels, &signer)?;
modified 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::<BTreeSet<_>>(),
+
                    &signer,
+
                )?;
            }
            models::patch::Action::Label { labels } => {
                patch.label(labels, &signer)?;
modified 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::<issue::Action>),
+
            post(activity_issue_handler::<radicle::issue::Action, issue::Action>),
        )
        .route(
            "/activity_by_patch",
-
            post(activity_patch_handler::<models::patch::Action>),
+
            post(activity_patch_handler::<radicle::patch::Action, models::patch::Action>),
        )
        .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<A: serde::Serialize + serde::de::DeserializeOwned>(
+
async fn activity_issue_handler<
+
    A: serde::Serialize + serde::de::DeserializeOwned,
+
    B: FromRadicleAction<A> + serde::Serialize,
+
>(
    State(ctx): State<Context>,
    Json(ActivityBody { rid, id }): Json<ActivityBody>,
) -> impl IntoResponse {
-
    let activity = ctx.activity_by_id::<A>(rid, &radicle::cob::issue::TYPENAME, id)?;
+
    let activity = ctx.activity_by_id::<A, B>(rid, &radicle::cob::issue::TYPENAME, id)?;

    Ok::<_, Error>(Json(activity))
}

-
async fn activity_patch_handler<A: serde::Serialize + serde::de::DeserializeOwned>(
+
async fn activity_patch_handler<
+
    A: serde::Serialize + serde::de::DeserializeOwned,
+
    B: FromRadicleAction<A> + serde::Serialize,
+
>(
    State(ctx): State<Context>,
    Json(ActivityBody { rid, id }): Json<ActivityBody>,
) -> impl IntoResponse {
-
    let activity = ctx.activity_by_id::<A>(rid, &radicle::cob::patch::TYPENAME, id)?;
+
    let activity = ctx.activity_by_id::<A, B>(rid, &radicle::cob::patch::TYPENAME, id)?;

    Ok::<_, Error>(Json(activity))
}
modified 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<A>(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 @@
        <div class="wrapper">
          <NodeId {...authorForNodeId(op.author)} />
          {#if op.previous && op.previous.type === op.type}
-
            {@const changed = itemDiff(
+
            {@const changed = itemDiff<Author>(
              op.previous?.assignees ?? [],
              op.assignees,
            )}
@@ -177,38 +175,20 @@
              {#if changed.added.length}
                assigned
                {#each changed.added as assignee}
-
                  {#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
-
                    <NodeId
-
                      {...authorForNodeId({
-
                        did: assignee,
-
                        alias: alias ?? undefined,
-
                      })} />
-
                  {/await}
+
                  <NodeId {...authorForNodeId(assignee)} />
                {/each}
              {/if}
              {#if changed.removed.length}
                unassigned
                {#each changed.removed as assignee}
-
                  {#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
-
                    <NodeId
-
                      {...authorForNodeId({
-
                        did: assignee,
-
                        alias: alias ?? undefined,
-
                      })} />
-
                  {/await}
+
                  <NodeId {...authorForNodeId(assignee)} />
                {/each}
              {/if}
            {/if}
          {:else}
            assigned
            {#each op.assignees as assignee}
-
              {#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
-
                <NodeId
-
                  {...authorForNodeId({
-
                    did: assignee,
-
                    alias: alias ?? undefined,
-
                  })} />
-
              {/await}
+
              <NodeId {...authorForNodeId(assignee)} />
            {/each}
          {/if}
          <div title={absoluteTimestamp(op.timestamp)}>
modified 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<A>(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 @@
        <div class="wrapper">
          <NodeId {...authorForNodeId(op.author)} />
          {#if op.previous && op.previous.type === op.type}
-
            {@const changed = itemDiff(
+
            {@const changed = itemDiff<Author>(
              op.previous?.assignees ?? [],
              op.assignees,
            )}
            {#if changed.added.length}
              assigned
              {#each changed.added as assignee}
-
                {#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
-
                  <NodeId
-
                    {...authorForNodeId({
-
                      did: assignee,
-
                      alias: alias ?? undefined,
-
                    })} />
-
                {/await}
+
                <NodeId {...authorForNodeId(assignee)} />
              {/each}
            {/if}
            {#if changed.removed.length}
              unassigned
              {#each changed.removed as assignee}
-
                {#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
-
                  <NodeId
-
                    {...authorForNodeId({
-
                      did: assignee,
-
                      alias: alias ?? undefined,
-
                    })} />
-
                {/await}
+
                <NodeId {...authorForNodeId(assignee)} />
              {/each}
            {/if}
          {:else}
            assigned
            {#each op.assignees as assignee}
-
              {#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
-
                <NodeId
-
                  {...authorForNodeId({
-
                    did: assignee,
-
                    alias: alias ?? undefined,
-
                  })} />
-
              {/await}
+
              <NodeId {...authorForNodeId(assignee)} />
            {/each}
          {/if}
          <div title={absoluteTimestamp(op.timestamp)}>
modified 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 },
      });
modified 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 },
      });