Skip to content

Commit 73511bc

Browse files
committed
Decision state comment builder
* add comment builder for the decision state * improve error handling * add factory for user status and add tests * cleanup unnecessary code
1 parent 026e922 commit 73511bc

File tree

5 files changed

+216
-14
lines changed

5 files changed

+216
-14
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ ignore = "0.4.18"
4545
postgres-types = { version = "0.2.4", features = ["derive"] }
4646
cron = { version = "0.12.0" }
4747
pretty_assertions = "1.2"
48+
factori = "1.1.0"
4849

4950
[dependencies.serde]
5051
version = "1"

parser/src/command/decision.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ pub enum Resolution {
8787
#[postgres(name = "hold")]
8888
Hold,
8989
}
90+
91+
impl fmt::Display for Resolution {
92+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93+
match self {
94+
Resolution::Merge => write!(f, "merge"),
95+
Resolution::Hold => write!(f, "hold"),
96+
}
97+
}
98+
}
99+
90100
#[cfg(test)]
91101
mod tests {
92102
use super::*;

src/db/issue_decision_state.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct IssueDecisionState {
1414
pub initiator: String,
1515
pub start_date: DateTime<Utc>,
1616
pub end_date: DateTime<Utc>,
17-
pub current: BTreeMap<String, UserStatus>,
17+
pub current: BTreeMap<String, Option<UserStatus>>,
1818
pub history: BTreeMap<String, Vec<UserStatus>>,
1919
pub reversibility: Reversibility,
2020
pub resolution: Resolution,
@@ -34,7 +34,7 @@ pub async fn insert_issue_decision_state(
3434
initiator: &String,
3535
start_date: &DateTime<Utc>,
3636
end_date: &DateTime<Utc>,
37-
current: &BTreeMap<String, UserStatus>,
37+
current: &BTreeMap<String, Option<UserStatus>>,
3838
history: &BTreeMap<String, Vec<UserStatus>>,
3939
reversibility: &Reversibility,
4040
resolution: &Resolution,
@@ -97,7 +97,8 @@ fn deserialize_issue_decision_state(row: &tokio_postgres::row::Row) -> Result<Is
9797
let initiator: String = row.try_get(1)?;
9898
let start_date: DateTime<Utc> = row.try_get(2)?;
9999
let end_date: DateTime<Utc> = row.try_get(3)?;
100-
let current: BTreeMap<String, UserStatus> = serde_json::from_value(row.try_get(4).unwrap())?;
100+
let current: BTreeMap<String, Option<UserStatus>> =
101+
serde_json::from_value(row.try_get(4).unwrap())?;
101102
let history: BTreeMap<String, Vec<UserStatus>> =
102103
serde_json::from_value(row.try_get(5).unwrap())?;
103104
let reversibility: Reversibility = row.try_get(6)?;

src/handlers/decision.rs

Lines changed: 180 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::db::jobs::*;
2+
use crate::github;
23
use crate::{
34
config::DecisionConfig, db::issue_decision_state::*, github::Event, handlers::Context,
45
interactions::ErrorComment,
56
};
7+
use anyhow::bail;
68
use anyhow::Context as Ctx;
79
use chrono::{DateTime, Duration, Utc};
810
use parser::command::decision::Resolution::{Hold, Merge};
@@ -93,16 +95,26 @@ pub(super) async fn handle_command(
9395
let end_date: DateTime<Utc> =
9496
start_date.checked_add_signed(Duration::days(10)).unwrap();
9597

96-
let mut current: BTreeMap<String, UserStatus> = BTreeMap::new();
98+
//TODO: change this to be configurable in toml / ask user to provide the team name
99+
// it should match the same team that we check for above when determining if the user is a member
100+
let team = github::get_team(&ctx.github, &"T-lang").await?.unwrap();
101+
102+
let mut current: BTreeMap<String, Option<UserStatus>> = BTreeMap::new();
103+
104+
for member in team.members {
105+
current.insert(member.name, None);
106+
}
107+
97108
current.insert(
98-
"mcass19".to_string(),
99-
UserStatus {
109+
user.login.clone(),
110+
Some(UserStatus {
100111
comment_id: "comment_id".to_string(),
101112
text: "something".to_string(),
102113
reversibility: Reversibility::Reversible,
103114
resolution: Merge,
104-
},
115+
}),
105116
);
117+
106118
let history: BTreeMap<String, Vec<UserStatus>> = BTreeMap::new();
107119

108120
insert_issue_decision_state(
@@ -120,7 +132,11 @@ pub(super) async fn handle_command(
120132

121133
let metadata = serde_json::value::to_value(DecisionProcessActionMetadata {
122134
message: "some message".to_string(),
123-
get_issue_url: format!("{}/issues/{}", issue.repository().url(), issue.number),
135+
get_issue_url: format!(
136+
"{}/issues/{}",
137+
issue.repository().url(),
138+
issue.number
139+
),
124140
status: Merge,
125141
})
126142
.unwrap();
@@ -133,12 +149,7 @@ pub(super) async fn handle_command(
133149
)
134150
.await?;
135151

136-
// let team = github::get_team(&ctx.github, &"T-lang"); // change this to be configurable in toml?
137-
138-
let comment = format!(
139-
"Wow, it looks like you want to merge this, {}.\n| Team member | State |\n|-------------|-------|\n| julmontesdeoca | merge |\n| mcass19 | |",
140-
user.login
141-
);
152+
let comment = build_status_comment(&history, &current)?;
142153

143154
issue
144155
.post_comment(&ctx.github, &comment)
@@ -152,6 +163,164 @@ pub(super) async fn handle_command(
152163
}
153164
}
154165

166+
fn build_status_comment(
167+
history: &BTreeMap<String, Vec<UserStatus>>,
168+
current: &BTreeMap<String, Option<UserStatus>>,
169+
) -> anyhow::Result<String> {
170+
let mut comment = "| Team member | State |\n|-------------|-------|".to_owned();
171+
for (user, statuses) in history {
172+
let mut user_statuses = format!("\n| {} |", user);
173+
174+
// previous stasuses
175+
for status in statuses {
176+
let status_item = format!(" ~~{}~~ ", status.resolution);
177+
user_statuses.push_str(&status_item);
178+
}
179+
180+
// current status
181+
let user_resolution = match current.get(user) {
182+
Some(current_status) => {
183+
if let Some(status) = current_status {
184+
format!("**{}**", status.resolution)
185+
} else {
186+
"".to_string()
187+
}
188+
}
189+
None => bail!("user {} not present in current statuses list", user),
190+
};
191+
192+
let status_item = format!(" {} |", user_resolution);
193+
user_statuses.push_str(&status_item);
194+
195+
comment.push_str(&user_statuses);
196+
}
197+
198+
Ok(comment)
199+
}
200+
201+
#[cfg(test)]
202+
mod tests {
203+
use super::*;
204+
use factori::{create, factori};
205+
206+
factori!(UserStatus, {
207+
default {
208+
comment_id = "the-comment-id".to_string(),
209+
text = "this is my argument for making this decision".to_string(),
210+
reversibility = Reversibility::Reversible,
211+
resolution = Resolution::Merge
212+
}
213+
214+
mixin hold {
215+
resolution = Resolution::Hold
216+
}
217+
});
218+
219+
#[test]
220+
fn test_successfuly_build_comment() {
221+
let mut history: BTreeMap<String, Vec<UserStatus>> = BTreeMap::new();
222+
let mut current_statuses: BTreeMap<String, Option<UserStatus>> = BTreeMap::new();
223+
224+
// user 1
225+
let mut user_1_statuses: Vec<UserStatus> = Vec::new();
226+
user_1_statuses.push(create!(UserStatus));
227+
user_1_statuses.push(create!(UserStatus, :hold));
228+
229+
history.insert("Niklaus".to_string(), user_1_statuses);
230+
231+
current_statuses.insert("Niklaus".to_string(), Some(create!(UserStatus)));
232+
233+
// user 2
234+
let mut user_2_statuses: Vec<UserStatus> = Vec::new();
235+
user_2_statuses.push(create!(UserStatus, :hold));
236+
user_2_statuses.push(create!(UserStatus));
237+
238+
history.insert("Barbara".to_string(), user_2_statuses);
239+
240+
current_statuses.insert("Barbara".to_string(), Some(create!(UserStatus)));
241+
242+
let build_result = build_status_comment(&history, &current_statuses)
243+
.expect("it shouldn't fail building the message");
244+
let expected_comment = "| Team member | State |\n\
245+
|-------------|-------|\n\
246+
| Barbara | ~~hold~~ ~~merge~~ **merge** |\n\
247+
| Niklaus | ~~merge~~ ~~hold~~ **merge** |"
248+
.to_string();
249+
250+
assert_eq!(build_result, expected_comment);
251+
}
252+
253+
#[test]
254+
fn test_successfuly_build_comment_user_no_votes() {
255+
let mut history: BTreeMap<String, Vec<UserStatus>> = BTreeMap::new();
256+
let mut current_statuses: BTreeMap<String, Option<UserStatus>> = BTreeMap::new();
257+
258+
// user 1
259+
let mut user_1_statuses: Vec<UserStatus> = Vec::new();
260+
user_1_statuses.push(create!(UserStatus));
261+
user_1_statuses.push(create!(UserStatus, :hold));
262+
263+
history.insert("Niklaus".to_string(), user_1_statuses);
264+
265+
current_statuses.insert("Niklaus".to_string(), Some(create!(UserStatus)));
266+
267+
// user 2
268+
let mut user_2_statuses: Vec<UserStatus> = Vec::new();
269+
user_2_statuses.push(create!(UserStatus, :hold));
270+
user_2_statuses.push(create!(UserStatus));
271+
272+
history.insert("Barbara".to_string(), user_2_statuses);
273+
274+
current_statuses.insert("Barbara".to_string(), Some(create!(UserStatus)));
275+
276+
// user 3
277+
history.insert("Tom".to_string(), Vec::new());
278+
279+
current_statuses.insert("Tom".to_string(), None);
280+
281+
let build_result = build_status_comment(&history, &current_statuses)
282+
.expect("it shouldn't fail building the message");
283+
let expected_comment = "| Team member | State |\n\
284+
|-------------|-------|\n\
285+
| Barbara | ~~hold~~ ~~merge~~ **merge** |\n\
286+
| Niklaus | ~~merge~~ ~~hold~~ **merge** |\n\
287+
| Tom | |"
288+
.to_string();
289+
290+
assert_eq!(build_result, expected_comment);
291+
}
292+
293+
#[test]
294+
fn test_build_comment_inconsistent_users() {
295+
let mut history: BTreeMap<String, Vec<UserStatus>> = BTreeMap::new();
296+
let mut current_statuses: BTreeMap<String, Option<UserStatus>> = BTreeMap::new();
297+
298+
// user 1
299+
let mut user_1_statuses: Vec<UserStatus> = Vec::new();
300+
user_1_statuses.push(create!(UserStatus));
301+
user_1_statuses.push(create!(UserStatus, :hold));
302+
303+
history.insert("Niklaus".to_string(), user_1_statuses);
304+
305+
current_statuses.insert("Niklaus".to_string(), Some(create!(UserStatus)));
306+
307+
// user 2
308+
let mut user_2_statuses: Vec<UserStatus> = Vec::new();
309+
user_2_statuses.push(create!(UserStatus, :hold));
310+
user_2_statuses.push(create!(UserStatus));
311+
312+
history.insert("Barbara".to_string(), user_2_statuses);
313+
314+
current_statuses.insert("Martin".to_string(), Some(create!(UserStatus)));
315+
316+
let build_result = build_status_comment(&history, &current_statuses);
317+
assert_eq!(
318+
format!("{}", build_result.unwrap_err()),
319+
"user Barbara not present in current statuses list"
320+
);
321+
}
322+
}
323+
155324
// #[cfg(test)]
156325
// mod tests {
157326
// use chrono::{Duration, Utc};

0 commit comments

Comments
 (0)