Added display review
This commit is contained in:
@@ -11,3 +11,11 @@ util = { path = "../util" }
|
||||
eyre.workspace = true
|
||||
clap.workspace = true
|
||||
dirs.workspace = true
|
||||
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
comfy-table = "6.1.4"
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
mockall = "0.11.2"
|
||||
|
@@ -1,6 +1,8 @@
|
||||
mod auth;
|
||||
mod fuzzy_clone;
|
||||
mod gh;
|
||||
mod review;
|
||||
pub(crate) mod review_backend;
|
||||
|
||||
pub struct GitHub;
|
||||
|
||||
@@ -31,6 +33,7 @@ impl util::Cmd for GitHub {
|
||||
auth::Auth::cmd()?,
|
||||
gh::Gh::cmd()?,
|
||||
fuzzy_clone::FuzzyClone::cmd()?,
|
||||
review::Review::cmd()?,
|
||||
])
|
||||
.allow_external_subcommands(true))
|
||||
}
|
||||
@@ -41,6 +44,7 @@ impl util::Cmd for GitHub {
|
||||
Some(("fuzzy-clone", subm)) => fuzzy_clone::FuzzyClone::exec(subm),
|
||||
Some(("fc", subm)) => fuzzy_clone::FuzzyClone::exec(subm),
|
||||
Some(("gh", subm)) => gh::Gh::exec(subm),
|
||||
Some(("review", subm)) => review::Review::exec(subm),
|
||||
Some((external, args)) => Self::run(external, args),
|
||||
_ => Err(eyre::anyhow!("missing argument")),
|
||||
}
|
||||
|
140
crates/github/src/review.rs
Normal file
140
crates/github/src/review.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use crate::review_backend::{models::PullRequest, DefaultReviewBackend, DynReviewBackend};
|
||||
|
||||
use comfy_table::{presets::UTF8_HORIZONTAL_ONLY, Cell, Row, Table};
|
||||
#[cfg(test)]
|
||||
use mockall::{automock, mock, predicate::*};
|
||||
|
||||
pub struct Review {
|
||||
backend: DynReviewBackend,
|
||||
}
|
||||
|
||||
impl Default for Review {
|
||||
fn default() -> Self {
|
||||
Self::new(std::sync::Arc::new(DefaultReviewBackend::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Review {
|
||||
fn new(backend: DynReviewBackend) -> Self {
|
||||
Self { backend }
|
||||
}
|
||||
|
||||
// Workflow
|
||||
// 1. Fetch list of repos
|
||||
// 2. Present menu
|
||||
// 3. Choose begin quick review
|
||||
// 4. Present pr and use delta to view changes
|
||||
// 5. Approve, open, skip or quit
|
||||
// 6. Repeat from 4
|
||||
fn run(&self, review_requested: Option<String>) -> eyre::Result<()> {
|
||||
let prs = self.backend.get_prs(review_requested)?;
|
||||
|
||||
let prs_table = Self::generate_prs_table(&prs);
|
||||
|
||||
self.backend.present_prs(prs_table)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_prs_table(prs: &[PullRequest]) -> String {
|
||||
let mut table = Table::new();
|
||||
let table = table
|
||||
.load_preset(UTF8_HORIZONTAL_ONLY)
|
||||
.set_content_arrangement(comfy_table::ContentArrangement::Dynamic)
|
||||
.set_header(vec![
|
||||
Cell::new("repo"),
|
||||
Cell::new("title"),
|
||||
Cell::new("number"),
|
||||
])
|
||||
.add_rows(prs.iter().map(|pr| {
|
||||
let pr = pr.clone();
|
||||
vec![pr.repository.name, pr.title, pr.number.to_string()]
|
||||
}));
|
||||
|
||||
table.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl util::Cmd for Review {
|
||||
fn cmd() -> eyre::Result<clap::Command> {
|
||||
Ok(clap::Command::new("review"))
|
||||
}
|
||||
|
||||
fn exec(_: &clap::ArgMatches) -> eyre::Result<()> {
|
||||
Self::default().run(Some("lunarway/squad-aura".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::review_backend::{models::Repository, MockReviewBackend};
|
||||
|
||||
use super::*;
|
||||
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
|
||||
#[test]
|
||||
fn can_fetch_prs() {
|
||||
let mut backend = MockReviewBackend::new();
|
||||
let prs = vec![
|
||||
PullRequest {
|
||||
title: "some-title".into(),
|
||||
number: 0,
|
||||
repository: Repository {
|
||||
name: "some-name".into(),
|
||||
},
|
||||
},
|
||||
PullRequest {
|
||||
title: "some-other-title".into(),
|
||||
number: 1,
|
||||
repository: Repository {
|
||||
name: "some-other-name".into(),
|
||||
},
|
||||
},
|
||||
];
|
||||
let backendprs = prs.clone();
|
||||
backend
|
||||
.expect_get_prs()
|
||||
.with(eq(Some("kjuulh".into())))
|
||||
.times(1)
|
||||
.returning(move |_| Ok(backendprs.clone()));
|
||||
|
||||
backend.expect_present_prs().times(1).returning(|_| Ok(()));
|
||||
|
||||
let review = Review::new(std::sync::Arc::new(backend));
|
||||
review
|
||||
.run(Some("kjuulh".into()))
|
||||
.expect("to return a list of pull requests");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_generate_table() {
|
||||
let prs = vec![
|
||||
PullRequest {
|
||||
title: "some-title".into(),
|
||||
number: 0,
|
||||
repository: Repository {
|
||||
name: "some-name".into(),
|
||||
},
|
||||
},
|
||||
PullRequest {
|
||||
title: "some-other-title".into(),
|
||||
number: 1,
|
||||
repository: Repository {
|
||||
name: "some-other-name".into(),
|
||||
},
|
||||
},
|
||||
];
|
||||
let expected_table = "─────────────────────────────────────────────
|
||||
repo title number
|
||||
═════════════════════════════════════════════
|
||||
some-name some-title 0
|
||||
─────────────────────────────────────────────
|
||||
some-other-name some-other-title 1
|
||||
─────────────────────────────────────────────";
|
||||
|
||||
let output = Review::generate_prs_table(&prs);
|
||||
|
||||
assert_eq!(output, expected_table.to_string())
|
||||
}
|
||||
}
|
53
crates/github/src/review_backend/mod.rs
Normal file
53
crates/github/src/review_backend/mod.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
pub mod models;
|
||||
|
||||
use self::models::PullRequest;
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::{automock, predicate::*};
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait ReviewBackend {
|
||||
fn get_prs(&self, review_request: Option<String>) -> eyre::Result<Vec<PullRequest>>;
|
||||
fn present_prs(&self, table: String) -> eyre::Result<()>;
|
||||
}
|
||||
|
||||
pub type DynReviewBackend = std::sync::Arc<dyn ReviewBackend + Send + Sync>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DefaultReviewBackend;
|
||||
|
||||
impl ReviewBackend for DefaultReviewBackend {
|
||||
fn get_prs(&self, review_request: Option<String>) -> eyre::Result<Vec<PullRequest>> {
|
||||
let raw_prs = util::shell::run_with_input_and_output(
|
||||
&[
|
||||
"gh",
|
||||
"search",
|
||||
"prs",
|
||||
"--state=open",
|
||||
"--review-requested",
|
||||
review_request.unwrap().as_str(),
|
||||
"--label",
|
||||
"dependencies",
|
||||
"--checks=pending",
|
||||
"--json",
|
||||
"repository,number,title",
|
||||
],
|
||||
"".into(),
|
||||
)?;
|
||||
|
||||
let prs_json = std::str::from_utf8(raw_prs.stdout.as_slice())?;
|
||||
|
||||
let prs: Vec<PullRequest> = serde_json::from_str(prs_json)?;
|
||||
|
||||
Ok(prs)
|
||||
}
|
||||
|
||||
fn present_prs(&self, table: String) -> eyre::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultReviewBackend {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
14
crates/github/src/review_backend/models.rs
Normal file
14
crates/github/src/review_backend/models.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct Repository {
|
||||
#[serde(rename(deserialize = "nameWithOwner"))]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct PullRequest {
|
||||
pub title: String,
|
||||
pub number: usize,
|
||||
pub repository: Repository,
|
||||
}
|
Reference in New Issue
Block a user