浏览代码

Add remaining code to revamp oldest unreviewed PRs

jackh726 3 年之前
父节点
当前提交
c0e3c8d887
共有 6 个文件被更改,包括 281 次插入94 次删除
  1. 94 63
      src/actions.rs
  2. 14 19
      src/agenda.rs
  3. 16 9
      src/github.rs
  4. 155 1
      src/github/graphql.rs
  5. 1 1
      src/handlers.rs
  6. 1 1
      src/handlers/github_releases.rs

+ 94 - 63
src/actions.rs

@@ -19,7 +19,8 @@ pub struct Step<'a> {
 }
 
 pub struct Query<'a> {
-    pub repos: Vec<&'a str>,
+    /// Vec of (owner, name)
+    pub repos: Vec<(&'a str, &'a str)>,
     pub queries: Vec<QueryMap<'a>>,
 }
 
@@ -57,7 +58,7 @@ lazy_static! {
     };
 }
 
-fn to_human(d: DateTime<Utc>) -> String {
+pub fn to_human(d: DateTime<Utc>) -> String {
     let d1 = chrono::Utc::now() - d;
     let days = d1.num_days();
     if days > 60 {
@@ -78,74 +79,104 @@ impl<'a> Action for Step<'a> {
         for Query { repos, queries } in &self.actions {
             for repo in repos {
                 let repository = Repository {
-                    full_name: repo.to_string(),
+                    owner: repo.0.to_string(),
+                    name: repo.1.to_string(),
                 };
 
                 for QueryMap { name, kind, query } in queries {
-                    let query = match query {
-                        github::GithubQuery::REST(query) => query,
-                    };
-                    match kind {
-                        QueryKind::List => {
-                            let issues_search_result = repository.get_issues(&gh, &query).await;
-
-                            match issues_search_result {
-                                Ok(issues) => {
-                                    let issues_decorator: Vec<_> = issues
-                                        .iter()
-                                        .map(|issue| IssueDecorator {
-                                            title: issue.title.clone(),
-                                            number: issue.number,
-                                            html_url: issue.html_url.clone(),
-                                            repo_name: repository
-                                                .full_name
-                                                .split("/")
-                                                .last()
-                                                .expect("Failed to split repository name")
-                                                .to_string(),
-                                            labels: issue
-                                                .labels
-                                                .iter()
-                                                .map(|l| l.name.as_ref())
-                                                .collect::<Vec<_>>()
-                                                .join(", "),
-                                            assignees: issue
-                                                .assignees
+                    match query {
+                        github::GithubQuery::REST(query) => {
+                            match kind {
+                                QueryKind::List => {
+                                    let issues_search_result = repository.get_issues(&gh, &query).await;
+        
+                                    match issues_search_result {
+                                        Ok(issues) => {
+                                            let issues_decorator: Vec<_> = issues
                                                 .iter()
-                                                .map(|u| u.login.as_ref())
-                                                .collect::<Vec<_>>()
-                                                .join(", "),
-                                            updated_at: to_human(issue.updated_at),
-                                        })
-                                        .collect();
-
-                                    results
-                                        .entry(*name)
-                                        .or_insert(Vec::new())
-                                        .extend(issues_decorator);
+                                                .map(|issue| IssueDecorator {
+                                                    title: issue.title.clone(),
+                                                    number: issue.number,
+                                                    html_url: issue.html_url.clone(),
+                                                    repo_name: repository.name.clone(),
+                                                    labels: issue
+                                                        .labels
+                                                        .iter()
+                                                        .map(|l| l.name.as_ref())
+                                                        .collect::<Vec<_>>()
+                                                        .join(", "),
+                                                    assignees: issue
+                                                        .assignees
+                                                        .iter()
+                                                        .map(|u| u.login.as_ref())
+                                                        .collect::<Vec<_>>()
+                                                        .join(", "),
+                                                    updated_at: to_human(issue.updated_at),
+                                                })
+                                                .collect();
+        
+                                            results
+                                                .entry(*name)
+                                                .or_insert(Vec::new())
+                                                .extend(issues_decorator);
+                                        }
+                                        Err(err) => {
+                                            eprintln!("ERROR: {}", err);
+                                            err.chain()
+                                                .skip(1)
+                                                .for_each(|cause| eprintln!("because: {}", cause));
+                                            std::process::exit(1);
+                                        }
+                                    }
                                 }
-                                Err(err) => {
-                                    eprintln!("ERROR: {}", err);
-                                    err.chain()
-                                        .skip(1)
-                                        .for_each(|cause| eprintln!("because: {}", cause));
-                                    std::process::exit(1);
+        
+                                QueryKind::Count => {
+                                    let count = repository.get_issues_count(&gh, &query).await;
+        
+                                    match count {
+                                        Ok(count) => {
+                                            let result = if let Some(value) = context.get(*name) {
+                                                value.as_u64().unwrap() + count as u64
+                                            } else {
+                                                count as u64
+                                            };
+        
+                                            context.insert(*name, &result);
+                                        }
+                                        Err(err) => {
+                                            eprintln!("ERROR: {}", err);
+                                            err.chain()
+                                                .skip(1)
+                                                .for_each(|cause| eprintln!("because: {}", cause));
+                                            std::process::exit(1);
+                                        }
+                                    }
                                 }
                             }
-                        }
-
-                        QueryKind::Count => {
-                            let count = repository.get_issues_count(&gh, &query).await;
-
-                            match count {
-                                Ok(count) => {
-                                    let result = if let Some(value) = context.get(*name) {
-                                        value.as_u64().unwrap() + count as u64
-                                    } else {
-                                        count as u64
-                                    };
-
-                                    context.insert(*name, &result);
+                        },
+                        github::GithubQuery::GraphQL(query) => {
+                            let issues = query.query(&repository, &gh).await;
+
+                            match issues {
+                                Ok(issues_decorator) => {
+                                    match kind {
+                                        QueryKind::List => {
+                                            results
+                                                .entry(*name)
+                                                .or_insert(Vec::new())
+                                                .extend(issues_decorator);
+                                        }
+                                        QueryKind::Count => {
+                                            let count = issues_decorator.len();
+                                            let result = if let Some(value) = context.get(*name) {
+                                                value.as_u64().unwrap() + count as u64
+                                            } else {
+                                                count as u64
+                                            };
+        
+                                            context.insert(*name, &result);
+                                        }
+                                    }
                                 }
                                 Err(err) => {
                                     eprintln!("ERROR: {}", err);

+ 14 - 19
src/agenda.rs

@@ -92,7 +92,7 @@ pub fn prioritization<'a>() -> Box<dyn Action> {
     });
 
     actions.push(Query {
-        repos: vec!["rust-lang/compiler-team"],
+        repos: vec![("rust-lang", "compiler-team")],
         queries,
     });
 
@@ -135,7 +135,7 @@ pub fn prioritization<'a>() -> Box<dyn Action> {
     });
 
     actions.push(Query {
-        repos: vec!["rust-lang/rust"],
+        repos: vec![("rust-lang", "rust")],
         queries,
     });
 
@@ -178,7 +178,7 @@ pub fn prioritization<'a>() -> Box<dyn Action> {
     });
 
     actions.push(Query {
-        repos: vec!["rust-lang/rust-forge"],
+        repos: vec![("rust-lang", "rust-forge")],
         queries,
     });
 
@@ -481,16 +481,11 @@ pub fn prioritization<'a>() -> Box<dyn Action> {
     queries.push(QueryMap {
         name: "top_unreviewed_prs",
         kind: QueryKind::List,
-        query: github::GithubQuery::REST(github::Query {
-            filters: vec![("state", "open"), ("is", "pull-request"), ("draft", "false")],
-            include_labels: vec!["S-waiting-on-review", "T-compiler"],
-            exclude_labels: vec![],
-            ordering,
-        }),
+        query: github::GithubQuery::GraphQL(Box::new(github::graphql::LeastRecentlyReviewedPullRequests)),
     });
 
     actions.push(Query {
-        repos: vec!["rust-lang/rust"],
+        repos: vec![("rust-lang", "rust")],
         queries,
     });
 
@@ -511,7 +506,7 @@ pub fn prioritization<'a>() -> Box<dyn Action> {
     });
 
     actions.push(Query {
-        repos: vec!["rust-lang/rfcs"],
+        repos: vec![("rust-lang", "rfcs")],
         queries,
     });
 
@@ -526,7 +521,7 @@ pub fn lang<'a>() -> Box<dyn Action> {
         name: "lang_agenda",
         actions: vec![
             Query {
-                repos: vec!["rust-lang/lang-team"],
+                repos: vec![("rust-lang", "lang-team")],
                 queries: vec![
                     QueryMap {
                         name: "pending_project_proposals",
@@ -561,7 +556,7 @@ pub fn lang<'a>() -> Box<dyn Action> {
                 ],
             },
             Query {
-                repos: vec!["rust-lang/rfcs"],
+                repos: vec![("rust-lang", "rfcs")],
                 queries: vec![QueryMap {
                     name: "rfcs_waiting_to_be_merged",
                     kind: QueryKind::List,
@@ -579,10 +574,10 @@ pub fn lang<'a>() -> Box<dyn Action> {
             },
             Query {
                 repos: vec![
-                    "rust-lang/rfcs",
-                    "rust-lang/rust",
-                    "rust-lang/reference",
-                    "rust-lang/lang-team",
+                    ("rust-lang", "rfcs"),
+                    ("rust-lang", "rust"),
+                    ("rust-lang", "reference"),
+                    ("rust-lang", "lang-team"),
                 ],
                 queries: vec![
                     QueryMap {
@@ -646,7 +641,7 @@ pub fn lang_planning<'a>() -> Box<dyn Action> {
         name: "lang_planning_agenda",
         actions: vec![
             Query {
-                repos: vec!["rust-lang/lang-team"],
+                repos: vec![("rust-lang", "lang-team")],
                 queries: vec![
                     QueryMap {
                         name: "pending_project_proposals",
@@ -681,7 +676,7 @@ pub fn lang_planning<'a>() -> Box<dyn Action> {
                 ],
             },
             Query {
-                repos: vec!["rust-lang/lang-team"],
+                repos: vec![("rust-lang", "lang-team")],
                 queries: vec![QueryMap {
                     name: "active_initiatives",
                     kind: QueryKind::List,

+ 16 - 9
src/github.rs

@@ -13,7 +13,7 @@ use std::{
     time::{Duration, SystemTime},
 };
 
-mod graphql;
+pub mod graphql;
 
 #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
 pub struct User {
@@ -759,11 +759,17 @@ pub struct IssueSearchResult {
 
 #[derive(Debug, serde::Deserialize)]
 pub struct Repository {
-    pub full_name: String,
+    pub owner: String,
+    pub name: String,
 }
 
 impl Repository {
     const GITHUB_API_URL: &'static str = "https://api.github.com";
+    const GITHUB_GRAPHQL_API_URL: &'static str = "https://api.github.com/graphql";
+
+    pub fn full_name(&self) -> String {
+        return format!("{}/{}", self.owner, self.name);
+    }
 
     pub async fn get_issues<'a>(
         &self,
@@ -875,7 +881,7 @@ impl Repository {
         format!(
             "{}/repos/{}/{}?{}",
             Repository::GITHUB_API_URL,
-            self.full_name,
+            self.full_name(),
             endpoint,
             filters
         )
@@ -902,7 +908,7 @@ impl Repository {
                     .iter()
                     .map(|label| format!("-label:{}", label)),
             )
-            .chain(std::iter::once(format!("repo:{}", self.full_name)))
+            .chain(std::iter::once(format!("repo:{}", self.full_name())))
             .collect::<Vec<_>>()
             .join("+");
         format!(
@@ -918,6 +924,7 @@ impl Repository {
 
 pub enum GithubQuery<'a> {
     REST(Query<'a>),
+    GraphQL(Box<dyn graphql::IssuesQuery + Send + Sync>),
 }
 
 pub struct Query<'a> {
@@ -959,12 +966,12 @@ pub enum Event {
 }
 
 impl Event {
-    pub fn repo_name(&self) -> &str {
+    pub fn repo_name(&self) -> String {
         match self {
-            Event::Create(event) => &event.repository.full_name,
-            Event::IssueComment(event) => &event.repository.full_name,
-            Event::Issue(event) => &event.repository.full_name,
-            Event::Push(event) => &event.repository.full_name,
+            Event::Create(event) => event.repository.full_name(),
+            Event::IssueComment(event) => event.repository.full_name(),
+            Event::Issue(event) => event.repository.full_name(),
+            Event::Push(event) => event.repository.full_name(),
         }
     }
 

+ 155 - 1
src/github/graphql.rs

@@ -1,3 +1,6 @@
+use anyhow::Context;
+use async_trait::async_trait;
+
 #[cynic::schema_for_derives(file = "src/github/github.graphql", module = "schema")]
 mod queries {
     use super::schema;
@@ -8,13 +11,15 @@ mod queries {
 
     #[derive(cynic::FragmentArguments, Debug)]
     pub struct LeastRecentlyReviewedPullRequestsArguments {
+        pub repository_owner: String,
+        pub repository_name: String,
         pub after: Option<String>,
     }
 
     #[derive(cynic::QueryFragment, Debug)]
     #[cynic(graphql_type = "Query", argument_struct = "LeastRecentlyReviewedPullRequestsArguments")]
     pub struct LeastRecentlyReviewedPullRequests {
-        #[arguments(owner = "rust-lang", name = "rust")]
+        #[arguments(owner = &args.repository_owner, name = &args.repository_name)]
         pub repository: Option<Repository>,
     }
 
@@ -36,6 +41,8 @@ mod queries {
     pub struct PullRequest {
         pub number: i32,
         pub created_at: DateTime,
+        pub url: Uri,
+        pub title: String,
         #[arguments(first = 100)]
         pub labels: Option<LabelConnection>,
         pub is_draft: bool,
@@ -56,6 +63,7 @@ mod queries {
     #[derive(cynic::QueryFragment, Debug)]
     pub struct PullRequestReview {
         pub author: Option<Actor>,
+        pub created_at: DateTime,
     }
 
     #[derive(cynic::QueryFragment, Debug)]
@@ -93,6 +101,7 @@ mod queries {
     #[derive(cynic::QueryFragment, Debug)]
     pub struct IssueComment {
         pub author: Option<Actor>,
+        pub created_at: DateTime,
     }
 
     #[derive(cynic::Enum, Clone, Copy, Debug)]
@@ -136,8 +145,153 @@ mod queries {
     pub struct Actor {
         pub login: String,
     }
+
+    #[derive(cynic::Scalar, Debug, Clone)]
+    pub struct Uri(pub String);
 }
 
 mod schema {
     cynic::use_schema!("src/github/github.graphql");
 }
+
+#[async_trait]
+pub trait IssuesQuery {
+    async fn query<'a>(&'a self, repo: &'a super::Repository, client: &'a super::GithubClient) -> anyhow::Result<Vec<crate::actions::IssueDecorator>>;
+}
+
+pub struct LeastRecentlyReviewedPullRequests;
+#[async_trait]
+impl IssuesQuery for LeastRecentlyReviewedPullRequests {
+    async fn query<'a>(&'a self, repo: &'a super::Repository, client: &'a super::GithubClient) -> anyhow::Result<Vec<crate::actions::IssueDecorator>> {
+        use cynic::QueryBuilder;
+
+        let repo_name = repo.name.clone();
+        let repository_owner = repo.owner.clone();
+        let repository_name = repo.name.clone();
+
+        let mut prs = vec![];
+
+        let mut args = queries::LeastRecentlyReviewedPullRequestsArguments {
+            repository_owner,
+            repository_name,
+            after: None,
+        };
+        loop {
+            let query = queries::LeastRecentlyReviewedPullRequests::build(&args);
+            let req = client.post(super::Repository::GITHUB_GRAPHQL_API_URL);
+            let req = req.json(&query);
+
+            let (resp, req_dbg) = client._send_req(req).await?;
+            let response = resp.json().await.context(req_dbg)?;
+            let data: cynic::GraphQlResponse<queries::LeastRecentlyReviewedPullRequests> = query.decode_response(response).with_context(|| format!("failed to parse response for `LeastRecentlyReviewedPullRequests`"))?;
+            if let Some(errors) = data.errors {
+                anyhow::bail!("There were graphql errors. {:?}", errors);
+            }
+            let repository = data.data.ok_or_else(|| anyhow::anyhow!("No data returned."))?.repository.ok_or_else(|| anyhow::anyhow!("No repository."))?;
+            prs.extend(repository.pull_requests.nodes.unwrap_or_default().into_iter());
+            let page_info = repository.pull_requests.page_info;
+            if !page_info.has_next_page || page_info.end_cursor.is_none() {
+                break;
+            }
+            args.after = page_info.end_cursor;
+        }
+
+        let mut prs: Vec<_> = prs
+            .into_iter()
+            .filter_map(|pr| pr)
+            .filter_map(|pr| {
+                if pr.is_draft {
+                    return None;
+                }
+                let labels = pr
+                    .labels
+                    .map(|labels|
+                        labels
+                            .nodes
+                            .map(|nodes|
+                                nodes
+                                    .into_iter()
+                                    .filter_map(|node| node)
+                                    .map(|node| node.name)
+                                    .collect::<Vec<_>>()
+                            )
+                            .unwrap_or_default()
+                    )
+                    .unwrap_or_default();
+                if !labels.iter().any(|label| label == "T-compiler") {
+                    return None;
+                }
+                let labels = labels.join(", ");
+
+                let assignees: Vec<_> = pr
+                    .assignees
+                    .nodes
+                    .unwrap_or_default()
+                    .into_iter()
+                    .filter_map(|user| user)
+                    .map(|user| user.login)
+                    .collect();
+
+                let mut reviews = pr
+                    .reviews
+                    .map(|reviews|
+                        reviews
+                        .nodes
+                        .map(|nodes|
+                            nodes
+                            .into_iter()
+                            .filter_map(|n| n)
+                            .map(|review| (review.author.map(|a| a.login).unwrap_or("N/A".to_string()), review.created_at))
+                            .collect::<Vec<_>>()
+                        )
+                        .unwrap_or_default()
+                    )
+                    .unwrap_or_default();
+                reviews.sort_by_key(|r| r.1);
+
+                let comments = pr
+                    .comments
+                    .nodes
+                    .map(|nodes|
+                        nodes
+                            .into_iter()
+                            .filter_map(|n| n)
+                            .map(|comment| (comment.author.map(|a| a.login).unwrap_or("N/A".to_string()), comment.created_at))
+                            .collect::<Vec<_>>()
+                    )
+                    .unwrap_or_default();
+                let mut comments: Vec<_> = comments.into_iter().filter(|comment| assignees.contains(&comment.0)).collect();
+                comments.sort_by_key(|c| c.1);
+
+                let updated_at = std::cmp::max(
+                    reviews.last().map(|t| t.1).unwrap_or(pr.created_at),
+                    comments.last().map(|t| t.1).unwrap_or(pr.created_at),
+                );
+                let assignees = assignees.join(", ");
+
+                Some((updated_at, pr.number as u64, pr.title, pr.url.0, repo_name.clone(), labels, assignees))
+            })
+            .collect();
+        prs.sort_by_key(|pr| pr.0);
+
+        let prs: Vec<_> = prs
+            .into_iter()
+            .take(5)
+            .map(|(updated_at, number, title, html_url, repo_name, labels, assignees)| {
+                let updated_at = crate::actions::to_human(updated_at);
+
+                crate::actions::IssueDecorator {
+                    number,
+                    title,
+                    html_url,
+                    repo_name,
+                    labels,
+                    assignees,
+                    updated_at,
+                }
+            })
+            .collect();
+
+        Ok(prs)
+    }
+}

+ 1 - 1
src/handlers.rs

@@ -41,7 +41,7 @@ mod rustc_commits;
 mod shortcut;
 
 pub async fn handle(ctx: &Context, event: &Event) -> Vec<HandlerError> {
-    let config = config::get(&ctx.github, event.repo_name()).await;
+    let config = config::get(&ctx.github, &event.repo_name()).await;
     let mut errors = Vec::new();
 
     if let (Ok(config), Event::Issue(event)) = (config.as_ref(), event) {

+ 1 - 1
src/handlers/github_releases.rs

@@ -115,7 +115,7 @@ async fn load_changelog(
     let resp = ctx
         .github
         .raw_file(
-            event.repo_name(),
+            &event.repo_name(),
             &config.changelog_branch,
             &config.changelog_path,
         )