Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
radicle-desktop crates radicle-types src traits issue.rs
use std::collections::BTreeSet;

use radicle::cob::Title;
use radicle::issue::cache::Issues as _;
use radicle::node::Handle;
use radicle::storage::ReadStorage;
use radicle::{git, identity, Node};

use crate::cobs;
use crate::error::Error;
use crate::traits::Profile;

pub trait Issues: Profile {
    fn list_issues(
        &self,
        rid: identity::RepoId,
        status: Option<cobs::query::IssueStatus>,
    ) -> Result<Vec<cobs::issue::Issue>, Error> {
        let profile = self.profile();
        let repo = profile.storage.repository(rid)?;
        let status = status.unwrap_or_default();
        let issues = profile.issues(&repo)?;
        let mut issues: Vec<_> = issues
            .list()?
            .filter_map(|r| {
                let (id, issue) = r.ok()?;
                (status.matches(issue.state())).then_some((id, issue))
            })
            .collect::<Vec<_>>();

        issues.sort_by(|(_, a), (_, b)| b.timestamp().cmp(&a.timestamp()));
        let aliases = &profile.aliases();
        let issues = issues
            .into_iter()
            .map(|(id, issue)| cobs::issue::Issue::new(&id, &issue, aliases))
            .collect::<Vec<_>>();

        Ok::<_, Error>(issues)
    }

    fn issue_by_id(
        &self,
        rid: identity::RepoId,
        id: git::Oid,
    ) -> Result<Option<cobs::issue::Issue>, Error> {
        let profile = self.profile();
        let repo = profile.storage.repository(rid)?;
        let issues = profile.issues(&repo)?;
        let issue = issues.get(&id.into())?;

        let aliases = &profile.aliases();
        let issue = issue.map(|issue| cobs::issue::Issue::new(&id.into(), &issue, aliases));

        Ok::<_, Error>(issue)
    }

    fn comment_threads_by_issue_id(
        &self,
        rid: identity::RepoId,
        id: git::Oid,
    ) -> Result<Option<Vec<cobs::thread::Thread>>, Error> {
        let profile = self.profile();
        let repo = profile.storage.repository(rid)?;
        let issues = profile.issues(&repo)?;
        let issue = issues.get(&id.into())?;

        let aliases = &profile.aliases();
        let comments = issue.map(|issue| {
            issue
                .replies()
                // Filter out replies that aren't top level replies
                .filter(|c| {
                    let Some(oid) = c.1.reply_to() else {
                        return false;
                    };

                    oid == id
                })
                .map(|(oid, c)| {
                    let root = cobs::thread::Comment::<cobs::Never>::new(*oid, c.clone(), aliases);
                    let replies = issue
                        .replies_to(oid)
                        .map(|(oid, c)| {
                            cobs::thread::Comment::<cobs::Never>::new(*oid, c.clone(), aliases)
                        })
                        .collect::<Vec<_>>();

                    cobs::thread::Thread { root, replies }
                })
                .collect::<Vec<_>>()
        });

        Ok::<_, Error>(comments)
    }
}

pub trait IssuesMut: Profile {
    fn create_issue(
        &self,
        rid: identity::RepoId,
        new: cobs::issue::NewIssue,
        opts: cobs::CobOptions,
    ) -> Result<cobs::issue::Issue, Error> {
        let profile = self.profile();
        let mut node = Node::new(profile.socket());
        let repo = profile.storage.repository(rid)?;
        let signer = profile.signer()?;
        let aliases = profile.aliases();
        let mut issues = profile.issues_mut(&repo)?;
        let title = Title::try_from(new.title)?;
        let issue = issues.create(
            title,
            new.description,
            &new.labels,
            &new.assignees,
            new.embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
            &signer,
        )?;

        if opts.announce() {
            if let Err(e) = node.announce_refs_for(rid, [profile.public_key]) {
                log::error!("Not able to announce changes: {}", e)
            }
        }

        Ok::<_, Error>(cobs::issue::Issue::new(issue.id(), &issue, &aliases))
    }

    fn edit_issue(
        &self,
        rid: identity::RepoId,
        cob_id: git::Oid,
        action: cobs::issue::Action,
        opts: cobs::CobOptions,
    ) -> Result<cobs::issue::Issue, Error> {
        let profile = self.profile();
        let mut node = Node::new(profile.socket());
        let repo = profile.storage.repository(rid)?;
        let signer = profile.signer()?;
        let aliases = profile.aliases();
        let mut issues = profile.issues_mut(&repo)?;
        let mut issue = issues.get_mut(&cob_id.into())?;

        match action {
            cobs::issue::Action::Lifecycle { state } => {
                issue.lifecycle(state.into(), &signer)?;
            }
            cobs::issue::Action::Assign { assignees } => {
                issue.assign(
                    assignees.iter().map(|a| *a.did()).collect::<BTreeSet<_>>(),
                    &signer,
                )?;
            }
            cobs::issue::Action::Label { labels } => {
                issue.label(labels, &signer)?;
            }
            cobs::issue::Action::CommentReact {
                id,
                reaction,
                active,
            } => {
                issue.react(id, reaction, active, &signer)?;
            }
            cobs::issue::Action::CommentRedact { id } => {
                issue.redact_comment(id, &signer)?;
            }
            cobs::issue::Action::Comment {
                body,
                reply_to,
                embeds,
            } => {
                issue.comment(
                    body,
                    reply_to.unwrap_or(cob_id),
                    embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
                    &signer,
                )?;
            }
            cobs::issue::Action::CommentEdit { id, body, embeds } => {
                issue.edit_comment(
                    id,
                    body,
                    embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
                    &signer,
                )?;
            }
            cobs::issue::Action::Edit { title } => {
                issue.edit(Title::try_from(title)?, &signer)?;
            }
        }

        if opts.announce() {
            if let Err(e) = node.announce_refs_for(rid, [profile.public_key]) {
                log::error!("Not able to announce changes: {}", e)
            }
        }

        Ok::<_, Error>(cobs::issue::Issue::new(issue.id(), &issue, &aliases))
    }
}