Skip to content

Commit

Permalink
Start working on handler - wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Mauricio Cassola authored and joulei committed Oct 27, 2022
1 parent 5979ba0 commit 75edb24
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 370 deletions.
22 changes: 18 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ edition = "2021"
pulldown-cmark = "0.7.0"
log = "0.4"
regex = "1.6.0"
postgres-types = { version = "0.2.4", features = ["derive"] }

[dependencies.serde]
version = "1"
features = ["derive"]
191 changes: 28 additions & 163 deletions parser/src/command/decision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,182 +8,47 @@
//! Command: `@bot merge`, `@bot hold`, `@bot restart`, `@bot dissent`, `@bot stabilize` or `@bot close`.
//! ```
use crate::token::{Tokenizer};
use crate::error::Error;
use crate::token::{Token, Tokenizer};
use std::fmt;
use serde::{Deserialize, Serialize};
use postgres_types::{FromSql, ToSql};

/// A command as parsed and received from calling the bot with some arguments,
/// like `@rustbot merge`
#[derive(PartialEq, Eq, Debug)]
#[derive(Debug, Eq, PartialEq)]
pub struct DecisionCommand {
user: String,
disposition: Resolution,
reversibility: Reversibility,
issue_id: String,
comment_id: String,
pub resolution: Resolution,
pub reversibility: Reversibility
}


#[derive(Debug)]
pub enum Error {
/// The first command that was given to this bot is not a valid one.
/// Decision process must start with a resolution.
InvalidFirstCommand,
impl DecisionCommand {
pub fn parse<'a>(_input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
Ok(Some(Self {
resolution: Resolution::Merge,
reversibility: Reversibility::Reversible
}))
}
}

use Error::*;
#[derive(Debug, Eq, PartialEq)]
pub enum ParseError {
InvalidFirstCommand
}

#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
enum Reversibility {
#[derive(Serialize, Deserialize, Debug, ToSql, FromSql, Eq, PartialEq)]
#[postgres(name = "reversibility")]
pub enum Reversibility {
#[postgres(name = "reversible")]
Reversible,
#[postgres(name = "irreversible")]
Irreversible,
}

use Reversibility::*;

impl fmt::Display for Reversibility {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self {
Reversible => write!(formatter, "a **reversible**"),
Irreversible => writeln!(formatter, "an **irreversible**"),
}
}
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Resolution {
#[derive(Serialize, Deserialize, Debug, ToSql, FromSql, Eq, PartialEq)]
#[postgres(name = "resolution")]
pub enum Resolution {
#[postgres(name = "merge")]
Merge,
#[postgres(name = "hold")]
Hold,
Custom(String),
}

use Resolution::*;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UserStatus {
name: String,
issue_id: String,
comment_id: String,
}

impl UserStatus {
fn new(name: String, issue_id: String, comment_id: String) -> UserStatus {
UserStatus {
name,
issue_id,
comment_id,
}
}
}

impl fmt::Display for UserStatus {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{}", self.name)
}
}
pub trait LinkRenderer {
fn render_link(&self, data: &UserStatus) -> String;
}

#[derive(Debug, Serialize, Deserialize)]
pub struct State {
initiator: String,
team_members: Vec<String>,
period_start: DateTime<Utc>,
original_period_start: DateTime<Utc>,
current_statuses: HashMap<String, UserStatus>,
status_history: HashMap<String, Vec<UserStatus>>,
reversibility: Reversibility,
resolution: Resolution,
}

impl State {
/// Renders the current state to the form it will have when seen as a
/// comment in the github issue/PR
pub fn render(&self, renderer: &impl LinkRenderer) -> String {
let initiator = &self.initiator;
let reversibility = self.reversibility.to_string();
let comment = format!("Hello! {initiator} has proposed to merge this. This is {reversibility} decision, which means that it will be affirmed once the \"final comment period\" of 10 days have passed, unless a team member places a \"hold\" on the decision (or cancels it).\n\n");

let mut table = String::from(if self.status_history.is_empty() {
"| Team member | State |\n\
|-------------|-------|\n"
} else {
"| Team member | History | State |\n\
|-------------|---------|-------|\n"
});

for member in self.team_members.iter() {
let current_status = self
.current_statuses
.get(member)
.map(|status| {
let link = renderer.render_link(status);

format!("[{status}]({link})")
})
.unwrap_or_else(|| "".into());

if self.status_history.is_empty() {
table.push_str(&format!("| {member} | {current_status} |\n"));
} else {
let status_history = self
.status_history
.get(member)
.map(|statuses| {
statuses
.iter()
.map(|status| {
let link = renderer.render_link(status);

format!("[{status}]({link})")
})
.collect::<Vec<_>>()
.join(" ")
})
.unwrap_or_else(|| "".into());

table.push_str(&format!(
"| {member} | {status_history} | {current_status} |\n"
));
}
}

comment + &table
}
}

impl Command {
#[cfg(test)]
fn merge(user: String, issue_id: String, comment_id: String) -> Self {
Self {
user,
issue_id,
comment_id,
disposition: Custom("merge".to_owned()),
reversibility: Reversibility::Reversible,
}
}

#[cfg(test)]
fn hold(user: String, issue_id: String, comment_id: String) -> Self {
Self {
user,
issue_id,
comment_id,
disposition: Hold,
reversibility: Reversibility::Reversible,
}
}
}


pub struct Context {
team_members: Vec<String>,
now: DateTime<Utc>,
}

impl Context {
pub fn new(team_members: Vec<String>, now: DateTime<Utc>) -> Context {
Context { team_members, now }
}
}
10 changes: 4 additions & 6 deletions src/db/decision_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ use tokio_postgres::Client as DbClient;
pub struct DecisionState {
issue_id: String,
initiator: String,
team_members: Vec<String>,
start_date: DateTime<FixedOffset>,
period_date: DateTime<FixedOffset>,
end_date: DateTime<FixedOffset>,
current_statuses: HashMap<String, UserStatus>,
status_history: HashMap<String, Vec<UserStatus>>,
reversibility: Reversibility,
Expand All @@ -31,9 +30,8 @@ pub async fn insert_decision_state(
db: &DbClient,
issue_id: &String,
initiator: &String,
team_members: &Vec<String>,
start_date: &DateTime<FixedOffset>,
period_end_date: &DateTime<FixedOffset>,
end_date: &DateTime<FixedOffset>,
current_statuses: &HashMap<String, UserStatus>,
status_history: &HashMap<String, Vec<UserStatus>>,
reversibility: &Reversibility,
Expand All @@ -42,9 +40,9 @@ pub async fn insert_decision_state(
tracing::trace!("insert_decision_state(issue_id={})", issue_id);

db.execute(
"INSERT INTO decision_state (issue_id, initiator, team_members, start_date, period_end_date, current_statuses, status_history, reversibility, resolution) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
"INSERT INTO decision_state (issue_id, initiator, start_date, end_date, current_statuses, status_history, reversibility, resolution) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT issue_id DO NOTHING",
&[&issue_id, &initiator, &team_members, &start_date, &period_end_date, &current_statuses, &status_history, &reversibility, &resolution],
&[&issue_id, &initiator, &start_date, &end_date, &current_statuses, &status_history, &reversibility, &resolution],
)
.await
.context("Inserting decision state")?;
Expand Down
Loading

0 comments on commit 75edb24

Please sign in to comment.