httpd: Add `assignees` to `Issues::create`
Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
parent
540c56cac6
commit
881e9af219
|
|
@ -11,7 +11,7 @@ The issue is now listed under our project.
|
|||
|
||||
```
|
||||
$ rad issue list
|
||||
17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 "flux capacitor underpowered"
|
||||
2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered"
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
$ rad assign 17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
$ rad assign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
```
|
||||
|
||||
It will now show in the list of issues assigned to us.
|
||||
|
||||
```
|
||||
$ rad issue list --assigned
|
||||
17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 "flux capacitor underpowered" z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered" z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -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.
|
||||
|
||||
```
|
||||
$ rad comment 17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 --message 'The flux capacitor needs 1.21 Gigawatts'
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/6
|
||||
$ rad comment 17e52fd6b2cd2e0f9317e18371dc978a691ed1e3 --reply-to z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/6 --message 'More power!'
|
||||
$ rad comment 2b4650e3c66d568132034de0d02871a2fbf9c5b5 --message 'The flux capacitor needs 1.21 Gigawatts'
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/7
|
||||
$ rad comment 2b4650e3c66d568132034de0d02871a2fbf9c5b5 --reply-to z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/7 --message 'More power!'
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/8
|
||||
```
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ Options
|
|||
pub struct Metadata {
|
||||
title: String,
|
||||
labels: Vec<Tag>,
|
||||
assignees: Vec<cob::ActorId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
|
|
@ -205,7 +206,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
title: Some(title),
|
||||
description: Some(description),
|
||||
} => {
|
||||
issues.create(title, description, &[], &signer)?;
|
||||
issues.create(title, description, &[], &[], &signer)?;
|
||||
}
|
||||
Operation::Show { id } => {
|
||||
let issue = issues
|
||||
|
|
@ -227,6 +228,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
let meta = Metadata {
|
||||
title: title.unwrap_or("Enter a title".to_owned()),
|
||||
labels: vec![],
|
||||
assignees: vec![],
|
||||
};
|
||||
let yaml = serde_yaml::to_string(&meta)?;
|
||||
let doc = format!(
|
||||
|
|
@ -263,6 +265,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
&meta.title,
|
||||
description.trim(),
|
||||
meta.labels.as_slice(),
|
||||
meta.assignees.as_slice(),
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use crate::api::{auth, Context};
|
|||
pub const HEAD: &str = "1e978d19f251cd9821d9d9a76d1bd436bf0690d5";
|
||||
pub const HEAD_1: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7";
|
||||
pub const PATCH_ID: &str = "4250f0117659ee4de9af99e699a63395cd6ffa1c";
|
||||
pub const ISSUE_ID: &str = "d8131af9738258fac139c4c96b71c02411f94892";
|
||||
pub const ISSUE_ID: &str = "8adca8aad2a2cb99b9847d20193930cde2042a57";
|
||||
|
||||
const PASSWORD: &str = "radicle";
|
||||
|
||||
|
|
@ -113,6 +113,7 @@ pub fn seed(dir: &Path) -> Context {
|
|||
"Issue #1".to_string(),
|
||||
"Change 'hello world' to 'hello everyone'".to_string(),
|
||||
&[],
|
||||
&[],
|
||||
&signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use tower_http::set_header::SetResponseHeaderLayer;
|
|||
|
||||
use radicle::cob::issue::{Action, Issues};
|
||||
use radicle::cob::patch::Patches;
|
||||
use radicle::cob::{thread, Tag};
|
||||
use radicle::cob::{thread, ActorId, Tag};
|
||||
use radicle::identity::Id;
|
||||
use radicle::node::NodeId;
|
||||
use radicle::storage::{git::paths, ReadRepository, WriteStorage};
|
||||
|
|
@ -404,6 +404,7 @@ pub struct IssueCreate {
|
|||
pub title: String,
|
||||
pub description: String,
|
||||
pub tags: Vec<Tag>,
|
||||
pub assignees: Vec<ActorId>,
|
||||
}
|
||||
|
||||
/// Create a new issue.
|
||||
|
|
@ -424,7 +425,13 @@ async fn issue_create_handler(
|
|||
let repo = storage.repository(project)?;
|
||||
let mut issues = Issues::open(ctx.profile.public_key, &repo)?;
|
||||
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)?;
|
||||
|
||||
Ok::<_, Error>((
|
||||
|
|
@ -551,7 +558,7 @@ mod routes {
|
|||
|
||||
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]
|
||||
async fn test_projects_root() {
|
||||
|
|
@ -1041,6 +1048,7 @@ mod routes {
|
|||
"title": "Issue #2",
|
||||
"description": "Change 'hello world' to 'hello everyone'",
|
||||
"tags": ["bug"],
|
||||
"assignees": [],
|
||||
}))
|
||||
.unwrap();
|
||||
let response = post(
|
||||
|
|
@ -1147,7 +1155,7 @@ mod routes {
|
|||
"replyTo": null,
|
||||
},
|
||||
{
|
||||
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4",
|
||||
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/5",
|
||||
"author": {
|
||||
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
|
||||
},
|
||||
|
|
@ -1217,7 +1225,7 @@ mod routes {
|
|||
"replyTo": null,
|
||||
},
|
||||
{
|
||||
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4",
|
||||
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/5",
|
||||
"author": {
|
||||
"id": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -193,7 +193,11 @@ impl Deref for 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 remove = remove.into_iter().collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -273,10 +277,10 @@ impl<'a, 'g> IssueMut<'a, 'g> {
|
|||
/// Assign one or more actors to an issue.
|
||||
pub fn assign<G: Signer>(
|
||||
&mut self,
|
||||
assignees: Vec<ActorId>,
|
||||
assignees: impl IntoIterator<Item = ActorId>,
|
||||
signer: &G,
|
||||
) -> Result<OpId, Error> {
|
||||
self.transaction("Assign", signer, |tx| tx.assign(assignees, vec![]))
|
||||
self.transaction("Assign", signer, |tx| tx.assign(assignees, []))
|
||||
}
|
||||
|
||||
/// Set the issue title.
|
||||
|
|
@ -332,10 +336,10 @@ impl<'a, 'g> IssueMut<'a, 'g> {
|
|||
/// Unassign one or more actors from an issue.
|
||||
pub fn unassign<G: Signer>(
|
||||
&mut self,
|
||||
assignees: Vec<ActorId>,
|
||||
assignees: impl IntoIterator<Item = ActorId>,
|
||||
signer: &G,
|
||||
) -> 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>(
|
||||
|
|
@ -416,16 +420,18 @@ impl<'a> Issues<'a> {
|
|||
title: impl ToString,
|
||||
description: impl ToString,
|
||||
tags: &[Tag],
|
||||
assignees: &[ActorId],
|
||||
signer: &G,
|
||||
) -> Result<IssueMut<'a, 'g>, Error> {
|
||||
let (id, issue, clock) =
|
||||
Transaction::initial("Create issue", &mut self.raw, signer, |tx| {
|
||||
tx.thread(description);
|
||||
tx.assign(assignees.to_owned(), []);
|
||||
tx.edit(title);
|
||||
tx.tag(tags.to_owned(), []);
|
||||
})?;
|
||||
// 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 {
|
||||
id,
|
||||
|
|
@ -498,15 +504,30 @@ mod test {
|
|||
|
||||
let assignee: ActorId = arbitrary::gen(1);
|
||||
let assignee_two: ActorId = arbitrary::gen(1);
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
let issue = issues
|
||||
.create(
|
||||
"My first issue",
|
||||
"Blah blah blah.",
|
||||
&[],
|
||||
&[assignee],
|
||||
&signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue.assign(vec![assignee, 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!(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!(assignees.contains(&assignee));
|
||||
assert!(assignees.contains(&assignee_two));
|
||||
|
|
@ -519,18 +540,27 @@ mod test {
|
|||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
|
||||
let assignee: ActorId = arbitrary::gen(1);
|
||||
let assignee_two: ActorId = arbitrary::gen(1);
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create(
|
||||
"My first issue",
|
||||
"Blah blah blah.",
|
||||
&[],
|
||||
&[assignee],
|
||||
&signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue.assign(vec![assignee], &signer).unwrap();
|
||||
issue.assign(vec![assignee], &signer).unwrap();
|
||||
issue.assign([assignee_two], &signer).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!(1, assignees.len());
|
||||
|
||||
assert_eq!(2, assignees.len());
|
||||
assert!(assignees.contains(&assignee));
|
||||
assert!(assignees.contains(&assignee_two));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -539,9 +569,10 @@ mod test {
|
|||
let (_, signer, project) = test::setup::context(&tmp);
|
||||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
let created = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
|
||||
.unwrap();
|
||||
assert_eq!(created.clock().get(), 3);
|
||||
|
||||
assert_eq!(created.clock().get(), 4);
|
||||
|
||||
let (id, created) = (created.id, created.issue);
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
|
@ -560,7 +591,7 @@ mod test {
|
|||
let (_, signer, project) = test::setup::context(&tmp);
|
||||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
|
||||
.unwrap();
|
||||
|
||||
issue
|
||||
|
|
@ -574,6 +605,7 @@ mod test {
|
|||
|
||||
let id = issue.id;
|
||||
let mut issue = issues.get_mut(&id).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*issue.state(),
|
||||
State::Closed {
|
||||
|
|
@ -583,6 +615,7 @@ mod test {
|
|||
|
||||
issue.lifecycle(State::Open, &signer).unwrap();
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
||||
assert_eq!(*issue.state(), State::Open);
|
||||
}
|
||||
|
||||
|
|
@ -595,15 +628,21 @@ mod test {
|
|||
let assignee: ActorId = arbitrary::gen(1);
|
||||
let assignee_two: ActorId = arbitrary::gen(1);
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create(
|
||||
"My first issue",
|
||||
"Blah blah blah.",
|
||||
&[],
|
||||
&[assignee, assignee_two],
|
||||
&signer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
issue.assign(vec![assignee, assignee_two], &signer).unwrap();
|
||||
issue.unassign(vec![assignee], &signer).unwrap();
|
||||
issue.unassign([assignee], &signer).unwrap();
|
||||
|
||||
let id = issue.id;
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(1, assignees.len());
|
||||
assert!(assignees.contains(&assignee_two));
|
||||
}
|
||||
|
|
@ -614,7 +653,7 @@ mod test {
|
|||
let (_, signer, project) = test::setup::context(&tmp);
|
||||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
|
||||
.unwrap();
|
||||
|
||||
issue.edit("Sorry typo", &signer).unwrap();
|
||||
|
|
@ -632,7 +671,7 @@ mod test {
|
|||
let (_, signer, project) = test::setup::context(&tmp);
|
||||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
|
||||
.unwrap();
|
||||
|
||||
let comment = OpId::initial(*signer.public_key());
|
||||
|
|
@ -655,7 +694,7 @@ mod test {
|
|||
let author = *signer.public_key();
|
||||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
|
||||
.unwrap();
|
||||
let root = OpId::root(author);
|
||||
|
||||
|
|
@ -676,6 +715,7 @@ mod test {
|
|||
issue.comment("Re: Ha. Ha. Ha.", c2, &signer).unwrap();
|
||||
|
||||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
|
||||
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(1).unwrap().1.body(), "Re: Ha. Ha.");
|
||||
|
|
@ -690,12 +730,18 @@ mod test {
|
|||
let tmp = tempfile::tempdir().unwrap();
|
||||
let (_, signer, project) = test::setup::context(&tmp);
|
||||
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 ux_tag = Tag::new("ux").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([wontfix_tag.clone()], [], &signer).unwrap();
|
||||
|
|
@ -704,6 +750,7 @@ mod test {
|
|||
let issue = issues.get(&id).unwrap().unwrap();
|
||||
let tags = issue.tags().cloned().collect::<Vec<_>>();
|
||||
|
||||
assert!(tags.contains(&ux_tag));
|
||||
assert!(tags.contains(&bug_tag));
|
||||
assert!(tags.contains(&wontfix_tag));
|
||||
}
|
||||
|
|
@ -715,7 +762,7 @@ mod test {
|
|||
let author = *signer.public_key();
|
||||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
let mut issue = issues
|
||||
.create("My first issue", "Blah blah blah.", &[], &signer)
|
||||
.create("My first issue", "Blah blah blah.", &[], &[], &signer)
|
||||
.unwrap();
|
||||
|
||||
// The root thread op id is always the same.
|
||||
|
|
@ -760,9 +807,9 @@ mod test {
|
|||
let (_, signer, project) = test::setup::context(&tmp);
|
||||
let mut issues = Issues::open(*signer.public_key(), &project).unwrap();
|
||||
|
||||
issues.create("First", "Blah", &[], &signer).unwrap();
|
||||
issues.create("Second", "Blah", &[], &signer).unwrap();
|
||||
issues.create("Third", "Blah", &[], &signer).unwrap();
|
||||
issues.create("First", "Blah", &[], &[], &signer).unwrap();
|
||||
issues.create("Second", "Blah", &[], &[], &signer).unwrap();
|
||||
issues.create("Third", "Blah", &[], &[], &signer).unwrap();
|
||||
|
||||
let issues = issues
|
||||
.all()
|
||||
|
|
|
|||
Loading…
Reference in New Issue