httpd: Add `assignees` to `Issues::create`

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez 2023-02-09 15:31:36 -03:00 committed by Alexis Sellier
parent 540c56cac6
commit 881e9af219
No known key found for this signature in database
5 changed files with 104 additions and 45 deletions

View File

@ -11,7 +11,7 @@ The issue is now listed under our project.
``` ```
$ rad issue list $ rad issue list
17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 "flux capacitor underpowered" 2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered"
``` ```
Great! Now we've documented the issue for ourselves and others. Great! Now we've documented the issue for ourselves and others.
@ -22,20 +22,20 @@ others to work on. This is to ensure work is not duplicated.
Let's assign ourselves to this one. Let's assign ourselves to this one.
``` ```
$ rad assign 17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi $ rad assign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
``` ```
It will now show in the list of issues assigned to us. It will now show in the list of issues assigned to us.
``` ```
$ rad issue list --assigned $ rad issue list --assigned
17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 "flux capacitor underpowered" z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered" z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
``` ```
Note: this can always be undone with the `unassign` subcommand. Note: this can always be undone with the `unassign` subcommand.
``` ```
$ rad unassign 17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi $ rad unassign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
``` ```
Great, now we have communicated to the world about our car's defect. Great, now we have communicated to the world about our car's defect.
@ -44,8 +44,8 @@ But wait! We've found an important detail about the car's power requirements.
It will help whoever works on a fix. It will help whoever works on a fix.
``` ```
$ rad comment 17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 --message 'The flux capacitor needs 1.21 Gigawatts' $ rad comment 2b4650e3c66d568132034de0d02871a2fbf9c5b5 --message 'The flux capacitor needs 1.21 Gigawatts'
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/6
$ rad comment 17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 --reply-to z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/6 --message 'More power!'
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/7 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/7
$ rad comment 2b4650e3c66d568132034de0d02871a2fbf9c5b5 --reply-to z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/7 --message 'More power!'
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/8
``` ```

View File

@ -38,6 +38,7 @@ Options
pub struct Metadata { pub struct Metadata {
title: String, title: String,
labels: Vec<Tag>, labels: Vec<Tag>,
assignees: Vec<cob::ActorId>,
} }
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
@ -205,7 +206,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
title: Some(title), title: Some(title),
description: Some(description), description: Some(description),
} => { } => {
issues.create(title, description, &[], &signer)?; issues.create(title, description, &[], &[], &signer)?;
} }
Operation::Show { id } => { Operation::Show { id } => {
let issue = issues let issue = issues
@ -227,6 +228,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let meta = Metadata { let meta = Metadata {
title: title.unwrap_or("Enter a title".to_owned()), title: title.unwrap_or("Enter a title".to_owned()),
labels: vec![], labels: vec![],
assignees: vec![],
}; };
let yaml = serde_yaml::to_string(&meta)?; let yaml = serde_yaml::to_string(&meta)?;
let doc = format!( let doc = format!(
@ -263,6 +265,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
&meta.title, &meta.title,
description.trim(), description.trim(),
meta.labels.as_slice(), meta.labels.as_slice(),
meta.assignees.as_slice(),
&signer, &signer,
)?; )?;
} }

View File

@ -23,7 +23,7 @@ use crate::api::{auth, Context};
pub const HEAD: &str = "1e978d19f251cd9821d9d9a76d1bd436bf0690d5"; pub const HEAD: &str = "1e978d19f251cd9821d9d9a76d1bd436bf0690d5";
pub const HEAD_1: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7"; pub const HEAD_1: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7";
pub const PATCH_ID: &str = "4250f0117659ee4de9af99e699a63395cd6ffa1c"; pub const PATCH_ID: &str = "4250f0117659ee4de9af99e699a63395cd6ffa1c";
pub const ISSUE_ID: &str = "d8131af9738258fac139c4c96b71c02411f94892"; pub const ISSUE_ID: &str = "8adca8aad2a2cb99b9847d20193930cde2042a57";
const PASSWORD: &str = "radicle"; const PASSWORD: &str = "radicle";
@ -113,6 +113,7 @@ pub fn seed(dir: &Path) -> Context {
"Issue #1".to_string(), "Issue #1".to_string(),
"Change 'hello world' to 'hello everyone'".to_string(), "Change 'hello world' to 'hello everyone'".to_string(),
&[], &[],
&[],
&signer, &signer,
) )
.unwrap(); .unwrap();

View File

@ -14,7 +14,7 @@ use tower_http::set_header::SetResponseHeaderLayer;
use radicle::cob::issue::{Action, Issues}; use radicle::cob::issue::{Action, Issues};
use radicle::cob::patch::Patches; use radicle::cob::patch::Patches;
use radicle::cob::{thread, Tag}; use radicle::cob::{thread, ActorId, Tag};
use radicle::identity::Id; use radicle::identity::Id;
use radicle::node::NodeId; use radicle::node::NodeId;
use radicle::storage::{git::paths, ReadRepository, WriteStorage}; use radicle::storage::{git::paths, ReadRepository, WriteStorage};
@ -404,6 +404,7 @@ pub struct IssueCreate {
pub title: String, pub title: String,
pub description: String, pub description: String,
pub tags: Vec<Tag>, pub tags: Vec<Tag>,
pub assignees: Vec<ActorId>,
} }
/// Create a new issue. /// Create a new issue.
@ -424,7 +425,13 @@ async fn issue_create_handler(
let repo = storage.repository(project)?; let repo = storage.repository(project)?;
let mut issues = Issues::open(ctx.profile.public_key, &repo)?; let mut issues = Issues::open(ctx.profile.public_key, &repo)?;
let issue = issues let issue = issues
.create(issue.title, issue.description, &issue.tags, &signer) .create(
issue.title,
issue.description,
&issue.tags,
&issue.assignees,
&signer,
)
.map_err(Error::from)?; .map_err(Error::from)?;
Ok::<_, Error>(( Ok::<_, Error>((
@ -551,7 +558,7 @@ mod routes {
use crate::api::test::{self, get, patch, post, HEAD, HEAD_1, ISSUE_ID, PATCH_ID}; use crate::api::test::{self, get, patch, post, HEAD, HEAD_1, ISSUE_ID, PATCH_ID};
const CREATED_ISSUE_ID: &str = "768c6735912a34856552ae6a9ca77d728962bf31"; const CREATED_ISSUE_ID: &str = "b56febfba1e7dd20f4aea43ca2fe9dcf1fd448ba";
#[tokio::test] #[tokio::test]
async fn test_projects_root() { async fn test_projects_root() {
@ -1041,6 +1048,7 @@ mod routes {
"title": "Issue #2", "title": "Issue #2",
"description": "Change 'hello world' to 'hello everyone'", "description": "Change 'hello world' to 'hello everyone'",
"tags": ["bug"], "tags": ["bug"],
"assignees": [],
})) }))
.unwrap(); .unwrap();
let response = post( let response = post(
@ -1147,7 +1155,7 @@ mod routes {
"replyTo": null, "replyTo": null,
}, },
{ {
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4", "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/5",
"author": { "author": {
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi", "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
}, },
@ -1217,7 +1225,7 @@ mod routes {
"replyTo": null, "replyTo": null,
}, },
{ {
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4", "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/5",
"author": { "author": {
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi", "id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
}, },

View File

@ -193,7 +193,11 @@ impl Deref for Issue {
} }
impl store::Transaction<Issue> { impl store::Transaction<Issue> {
pub fn assign(&mut self, add: Vec<ActorId>, remove: Vec<ActorId>) -> OpId { pub fn assign(
&mut self,
add: impl IntoIterator<Item = ActorId>,
remove: impl IntoIterator<Item = ActorId>,
) -> OpId {
let add = add.into_iter().collect::<Vec<_>>(); let add = add.into_iter().collect::<Vec<_>>();
let remove = remove.into_iter().collect::<Vec<_>>(); let remove = remove.into_iter().collect::<Vec<_>>();
@ -273,10 +277,10 @@ impl<'a, 'g> IssueMut<'a, 'g> {
/// Assign one or more actors to an issue. /// Assign one or more actors to an issue.
pub fn assign<G: Signer>( pub fn assign<G: Signer>(
&mut self, &mut self,
assignees: Vec<ActorId>, assignees: impl IntoIterator<Item = ActorId>,
signer: &G, signer: &G,
) -> Result<OpId, Error> { ) -> Result<OpId, Error> {
self.transaction("Assign", signer, |tx| tx.assign(assignees, vec![])) self.transaction("Assign", signer, |tx| tx.assign(assignees, []))
} }
/// Set the issue title. /// Set the issue title.
@ -332,10 +336,10 @@ impl<'a, 'g> IssueMut<'a, 'g> {
/// Unassign one or more actors from an issue. /// Unassign one or more actors from an issue.
pub fn unassign<G: Signer>( pub fn unassign<G: Signer>(
&mut self, &mut self,
assignees: Vec<ActorId>, assignees: impl IntoIterator<Item = ActorId>,
signer: &G, signer: &G,
) -> Result<OpId, Error> { ) -> Result<OpId, Error> {
self.transaction("Unassign", signer, |tx| tx.assign(vec![], assignees)) self.transaction("Unassign", signer, |tx| tx.assign([], assignees))
} }
pub fn transaction<G, F, T>( pub fn transaction<G, F, T>(
@ -416,16 +420,18 @@ impl<'a> Issues<'a> {
title: impl ToString, title: impl ToString,
description: impl ToString, description: impl ToString,
tags: &[Tag], tags: &[Tag],
assignees: &[ActorId],
signer: &G, signer: &G,
) -> Result<IssueMut<'a, 'g>, Error> { ) -> Result<IssueMut<'a, 'g>, Error> {
let (id, issue, clock) = let (id, issue, clock) =
Transaction::initial("Create issue", &mut self.raw, signer, |tx| { Transaction::initial("Create issue", &mut self.raw, signer, |tx| {
tx.thread(description); tx.thread(description);
tx.assign(assignees.to_owned(), []);
tx.edit(title); tx.edit(title);
tx.tag(tags.to_owned(), []); tx.tag(tags.to_owned(), []);
})?; })?;
// Just a sanity check that our clock is advancing as expected. // Just a sanity check that our clock is advancing as expected.
debug_assert_eq!(clock.get(), 3); debug_assert_eq!(clock.get(), 4);
Ok(IssueMut { Ok(IssueMut {
id, id,
@ -498,15 +504,30 @@ mod test {
let assignee: ActorId = arbitrary::gen(1); let assignee: ActorId = arbitrary::gen(1);
let assignee_two: ActorId = arbitrary::gen(1); let assignee_two: ActorId = arbitrary::gen(1);
let mut issue = issues let issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create(
"My first issue",
"Blah blah blah.",
&[],
&[assignee],
&signer,
)
.unwrap(); .unwrap();
issue.assign(vec![assignee, assignee_two], &signer).unwrap();
let id = issue.id; let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>(); let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
assert_eq!(1, assignees.len());
assert!(assignees.contains(&assignee));
let mut issue = issues.get_mut(&id).unwrap();
issue.assign([assignee_two], &signer).unwrap();
let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
assert_eq!(2, assignees.len()); assert_eq!(2, assignees.len());
assert!(assignees.contains(&assignee)); assert!(assignees.contains(&assignee));
assert!(assignees.contains(&assignee_two)); assert!(assignees.contains(&assignee_two));
@ -519,18 +540,27 @@ mod test {
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let assignee: ActorId = arbitrary::gen(1); let assignee: ActorId = arbitrary::gen(1);
let assignee_two: ActorId = arbitrary::gen(1);
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create(
"My first issue",
"Blah blah blah.",
&[],
&[assignee],
&signer,
)
.unwrap(); .unwrap();
issue.assign(vec![assignee], &signer).unwrap(); issue.assign([assignee_two], &signer).unwrap();
issue.assign(vec![assignee], &signer).unwrap(); issue.assign([assignee_two], &signer).unwrap();
let id = issue.id; let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>(); let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
assert_eq!(1, assignees.len());
assert_eq!(2, assignees.len());
assert!(assignees.contains(&assignee)); assert!(assignees.contains(&assignee));
assert!(assignees.contains(&assignee_two));
} }
#[test] #[test]
@ -539,9 +569,10 @@ mod test {
let (_, signer, project) = test::setup::context(&tmp); let (_, signer, project) = test::setup::context(&tmp);
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let created = issues let created = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &[], &signer)
.unwrap(); .unwrap();
assert_eq!(created.clock().get(), 3);
assert_eq!(created.clock().get(), 4);
let (id, created) = (created.id, created.issue); let (id, created) = (created.id, created.issue);
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
@ -560,7 +591,7 @@ mod test {
let (_, signer, project) = test::setup::context(&tmp); let (_, signer, project) = test::setup::context(&tmp);
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &[], &signer)
.unwrap(); .unwrap();
issue issue
@ -574,6 +605,7 @@ mod test {
let id = issue.id; let id = issue.id;
let mut issue = issues.get_mut(&id).unwrap(); let mut issue = issues.get_mut(&id).unwrap();
assert_eq!( assert_eq!(
*issue.state(), *issue.state(),
State::Closed { State::Closed {
@ -583,6 +615,7 @@ mod test {
issue.lifecycle(State::Open, &signer).unwrap(); issue.lifecycle(State::Open, &signer).unwrap();
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
assert_eq!(*issue.state(), State::Open); assert_eq!(*issue.state(), State::Open);
} }
@ -595,15 +628,21 @@ mod test {
let assignee: ActorId = arbitrary::gen(1); let assignee: ActorId = arbitrary::gen(1);
let assignee_two: ActorId = arbitrary::gen(1); let assignee_two: ActorId = arbitrary::gen(1);
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create(
"My first issue",
"Blah blah blah.",
&[],
&[assignee, assignee_two],
&signer,
)
.unwrap(); .unwrap();
issue.assign(vec![assignee, assignee_two], &signer).unwrap(); issue.unassign([assignee], &signer).unwrap();
issue.unassign(vec![assignee], &signer).unwrap();
let id = issue.id; let id = issue.id;
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>(); let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
assert_eq!(1, assignees.len()); assert_eq!(1, assignees.len());
assert!(assignees.contains(&assignee_two)); assert!(assignees.contains(&assignee_two));
} }
@ -614,7 +653,7 @@ mod test {
let (_, signer, project) = test::setup::context(&tmp); let (_, signer, project) = test::setup::context(&tmp);
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &[], &signer)
.unwrap(); .unwrap();
issue.edit("Sorry typo", &signer).unwrap(); issue.edit("Sorry typo", &signer).unwrap();
@ -632,7 +671,7 @@ mod test {
let (_, signer, project) = test::setup::context(&tmp); let (_, signer, project) = test::setup::context(&tmp);
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &[], &signer)
.unwrap(); .unwrap();
let comment = OpId::initial(*signer.public_key()); let comment = OpId::initial(*signer.public_key());
@ -655,7 +694,7 @@ mod test {
let author = *signer.public_key(); let author = *signer.public_key();
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &[], &signer)
.unwrap(); .unwrap();
let root = OpId::root(author); let root = OpId::root(author);
@ -676,6 +715,7 @@ mod test {
issue.comment("Re: Ha. Ha. Ha.", c2, &signer).unwrap(); issue.comment("Re: Ha. Ha. Ha.", c2, &signer).unwrap();
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
assert_eq!(issue.replies(&c1).nth(0).unwrap().1.body(), "Re: Hi."); assert_eq!(issue.replies(&c1).nth(0).unwrap().1.body(), "Re: Hi.");
assert_eq!(issue.replies(&c2).nth(0).unwrap().1.body(), "Re: Ha."); assert_eq!(issue.replies(&c2).nth(0).unwrap().1.body(), "Re: Ha.");
assert_eq!(issue.replies(&c2).nth(1).unwrap().1.body(), "Re: Ha. Ha."); assert_eq!(issue.replies(&c2).nth(1).unwrap().1.body(), "Re: Ha. Ha.");
@ -690,12 +730,18 @@ mod test {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let (_, signer, project) = test::setup::context(&tmp); let (_, signer, project) = test::setup::context(&tmp);
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer)
.unwrap();
let bug_tag = Tag::new("bug").unwrap(); let bug_tag = Tag::new("bug").unwrap();
let ux_tag = Tag::new("ux").unwrap();
let wontfix_tag = Tag::new("wontfix").unwrap(); let wontfix_tag = Tag::new("wontfix").unwrap();
let mut issue = issues
.create(
"My first issue",
"Blah blah blah.",
&[ux_tag.clone()],
&[],
&signer,
)
.unwrap();
issue.tag([bug_tag.clone()], [], &signer).unwrap(); issue.tag([bug_tag.clone()], [], &signer).unwrap();
issue.tag([wontfix_tag.clone()], [], &signer).unwrap(); issue.tag([wontfix_tag.clone()], [], &signer).unwrap();
@ -704,6 +750,7 @@ mod test {
let issue = issues.get(&id).unwrap().unwrap(); let issue = issues.get(&id).unwrap().unwrap();
let tags = issue.tags().cloned().collect::<Vec<_>>(); let tags = issue.tags().cloned().collect::<Vec<_>>();
assert!(tags.contains(&ux_tag));
assert!(tags.contains(&bug_tag)); assert!(tags.contains(&bug_tag));
assert!(tags.contains(&wontfix_tag)); assert!(tags.contains(&wontfix_tag));
} }
@ -715,7 +762,7 @@ mod test {
let author = *signer.public_key(); let author = *signer.public_key();
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
let mut issue = issues let mut issue = issues
.create("My first issue", "Blah blah blah.", &[], &signer) .create("My first issue", "Blah blah blah.", &[], &[], &signer)
.unwrap(); .unwrap();
// The root thread op id is always the same. // The root thread op id is always the same.
@ -760,9 +807,9 @@ mod test {
let (_, signer, project) = test::setup::context(&tmp); let (_, signer, project) = test::setup::context(&tmp);
let mut issues = Issues::open(*signer.public_key(), &project).unwrap(); let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
issues.create("First", "Blah", &[], &signer).unwrap(); issues.create("First", "Blah", &[], &[], &signer).unwrap();
issues.create("Second", "Blah", &[], &signer).unwrap(); issues.create("Second", "Blah", &[], &[], &signer).unwrap();
issues.create("Third", "Blah", &[], &signer).unwrap(); issues.create("Third", "Blah", &[], &[], &signer).unwrap();
let issues = issues let issues = issues
.all() .all()