cobs.rs
1 use std::collections::BTreeMap; 2 3 use serde_json::{json, Value}; 4 5 use radicle::cob; 6 use radicle::cob::{issue, patch}; 7 use radicle_experiment_cob as experiment; 8 use radicle::identity; 9 use radicle::node::AliasStore; 10 use radicle::storage::{git, refs, RemoteRepository}; 11 12 use super::thread; 13 use super::{edit, reactions, Author}; 14 15 pub(crate) struct Issue<'a>(&'a issue::Issue); 16 17 impl<'a> Issue<'a> { 18 pub fn new(issue: &'a issue::Issue) -> Self { 19 Self(issue) 20 } 21 22 pub fn as_json(&self, id: issue::IssueId, aliases: &impl AliasStore) -> Value { 23 json!({ 24 "id": id.to_string(), 25 "author": Author::new(self.0.author().id()).as_json(aliases), 26 "title": self.0.title(), 27 "state": self.0.state(), 28 "assignees": self.0.assignees().map(|assignee| 29 Author::new(assignee).as_json(aliases) 30 ).collect::<Vec<_>>(), 31 "discussion": self.0.comments().map(|(id, c)| 32 thread::Comment::Issue(c).as_json(id, aliases) 33 ).collect::<Vec<_>>(), 34 "labels": self.0.labels().collect::<Vec<_>>(), 35 }) 36 } 37 } 38 39 pub(crate) struct Patch<'a>(&'a patch::Patch); 40 41 impl<'a> Patch<'a> { 42 pub fn new(patch: &'a patch::Patch) -> Self { 43 Self(patch) 44 } 45 46 pub fn as_json( 47 &self, 48 id: patch::PatchId, 49 repo: &git::Repository, 50 aliases: &impl AliasStore, 51 ) -> Value { 52 json!({ 53 "id": id.to_string(), 54 "author": Author::new(self.0.author().id()).as_json(aliases), 55 "title": self.0.title(), 56 "state": self.0.state(), 57 "target": self.0.target(), 58 "labels": self.0.labels().collect::<Vec<_>>(), 59 "merges": self.0.merges().map(|(nid, m)| json!({ 60 "author": Author::new(&identity::Did::from(nid)).as_json(aliases), 61 "commit": m.commit, 62 "timestamp": m.timestamp.as_secs(), 63 "revision": m.revision, 64 })).collect::<Vec<_>>(), 65 "assignees": self.0.assignees().map(|assignee| 66 Author::new(&assignee).as_json(aliases) 67 ).collect::<Vec<_>>(), 68 "revisions": self.0.revisions().map(|(id, rev)| 69 Revision::new(rev).as_json(id, repo, aliases) 70 ).collect::<Vec<_>>(), 71 }) 72 } 73 } 74 75 pub(crate) struct Revision<'a>(&'a patch::Revision); 76 77 impl<'a> Revision<'a> { 78 pub fn new(revision: &'a patch::Revision) -> Self { 79 Self(revision) 80 } 81 82 pub fn as_json( 83 &self, 84 id: patch::RevisionId, 85 repo: &git::Repository, 86 aliases: &impl AliasStore, 87 ) -> Value { 88 let reactions = self 89 .0 90 .reactions() 91 .iter() 92 .flat_map(|(location, reaction)| { 93 reactions( 94 reaction.iter().fold( 95 BTreeMap::new(), 96 |mut acc: BTreeMap<&cob::Reaction, Vec<_>>, (author, emoji)| { 97 acc.entry(emoji).or_default().push(author); 98 acc 99 }, 100 ), 101 location.as_ref(), 102 aliases, 103 ) 104 }) 105 .collect::<Vec<_>>(); 106 let refs = get_refs(repo, self.0.author().id(), &self.0.head()).unwrap_or_default(); 107 108 json!({ 109 "id": id, 110 "author": Author::new(self.0.author().id()).as_json(aliases), 111 "description": self.0.description(), 112 "edits": self.0.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(), 113 "reactions": reactions, 114 "base": self.0.base(), 115 "oid": self.0.head(), 116 "refs": refs, 117 "discussions": self.0.discussion().comments().map(|(id, c)| 118 thread::Comment::Patch(c).as_json(id, aliases) 119 ).collect::<Vec<_>>(), 120 "timestamp": self.0.timestamp().as_secs(), 121 "reviews": self.0.reviews().map(|(_, r)| 122 Review::new(r).as_json(aliases) 123 ).collect::<Vec<_>>(), 124 }) 125 } 126 } 127 128 pub(crate) struct Review<'a>(&'a patch::Review); 129 130 impl<'a> Review<'a> { 131 pub fn new(review: &'a patch::Review) -> Self { 132 Self(review) 133 } 134 135 pub fn as_json(&self, aliases: &impl AliasStore) -> Value { 136 json!({ 137 "id": self.0.id(), 138 "author": Author::new(self.0.author().id()).as_json(aliases), 139 "verdict": self.0.verdict(), 140 "summary": self.0.summary(), 141 "comments": self.0.comments().map(|(id, c)| 142 thread::Comment::Patch(c).as_json(id, aliases) 143 ).collect::<Vec<_>>(), 144 "timestamp": self.0.timestamp().as_secs(), 145 }) 146 } 147 } 148 149 pub(crate) struct Experiment<'a>(&'a experiment::Experiment); 150 151 impl<'a> Experiment<'a> { 152 pub fn new(experiment: &'a experiment::Experiment) -> Self { 153 Self(experiment) 154 } 155 156 pub fn as_json(&self, id: experiment::ExperimentId, aliases: &impl AliasStore) -> Value { 157 let e = self.0; 158 json!({ 159 "id": id.to_string(), 160 "author": Author::new(e.author.id()).as_json(aliases), 161 "description": e.description, 162 "parent": e.parent, 163 "baseCommit": e.base_commit, 164 "candidateRef": e.candidate_ref, 165 "candidateHead": e.candidate_head, 166 "touchedFiles": e.touched_files, 167 "linesAdded": e.lines_added, 168 "linesRemoved": e.lines_removed, 169 "harness": e.harness, 170 "command": e.command, 171 "metricName": e.metric_name, 172 "metricUnit": e.metric_unit, 173 "direction": e.direction, 174 "warmupRuns": e.warmup_runs, 175 "measurementRuns": e.measurement_runs, 176 "runnerClass": e.runner_class, 177 "os": e.os, 178 "cpu": e.cpu, 179 "baselineN": e.baseline_n, 180 "baselineMedianMilliunits": e.baseline_median_milliunits, 181 "candidateN": e.candidate_n, 182 "candidateMedianMilliunits": e.candidate_median_milliunits, 183 "deltaPctX100": e.delta_pct_x100, 184 "buildOk": e.build_ok, 185 "testsOk": e.tests_ok, 186 "sanitizersOk": e.sanitizers_ok, 187 "agentSystem": e.agent_system, 188 "agentModel": e.agent_model, 189 "reproductions": e.reproductions.iter().map(|r| json!({ 190 "verdict": r.verdict.to_string(), 191 "runnerClass": r.runner_class, 192 "baselineMedianMilliunits": r.baseline_median_milliunits, 193 "candidateMedianMilliunits": r.candidate_median_milliunits, 194 "deltaPctX100": r.delta_pct_x100, 195 "author": Author::new(r.author.id()).as_json(aliases), 196 "timestamp": r.timestamp.as_secs(), 197 })).collect::<Vec<_>>(), 198 "createdAt": e.created_at.as_secs(), 199 }) 200 } 201 } 202 203 fn get_refs( 204 repo: &git::Repository, 205 id: &cob::ActorId, 206 head: &radicle::git::Oid, 207 ) -> Result<Vec<radicle::git::fmt::RefString>, refs::Error> { 208 let remote = repo.remote(id)?; 209 let refs = remote 210 .refs 211 .iter() 212 .filter_map(|(name, o)| { 213 if o == head { 214 Some(name.to_owned()) 215 } else { 216 None 217 } 218 }) 219 .collect::<Vec<_>>(); 220 221 Ok(refs) 222 }