Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 0 additions & 40 deletions src/bin/project_goals.rs

This file was deleted.

1 change: 1 addition & 0 deletions src/github/queries/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub(crate) mod issue_with_comments;
pub(crate) mod least_recently_reviewed;
pub(crate) mod open_goal_issues;
pub(crate) mod recent_commits;
pub(crate) mod user_comments_in_org;
pub(crate) mod user_contributions;
Expand Down
197 changes: 197 additions & 0 deletions src/github/queries/open_goal_issues.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use anyhow::Context;

use crate::github::GithubClient;

const ORG: &str = "rust-lang";
const REPO: &str = "rust-project-goals";
const LABEL: &str = "C-tracking-issue";

pub struct GoalIssue {
pub number: u64,
pub title: String,
pub assignees: Vec<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub labels: Vec<String>,
pub last_comment: Option<LastGoalComment>,
}

pub struct LastGoalComment {
pub created_at: chrono::DateTime<chrono::Utc>,
}

#[derive(serde::Deserialize)]
struct GraphQlConnection {
nodes: Vec<Option<GraphQlIssue>>,
#[serde(rename = "pageInfo")]
page_info: GraphQlPageInfo,
}

#[derive(serde::Deserialize)]
struct GraphQlPageInfo {
#[serde(rename = "hasNextPage")]
has_next_page: bool,
#[serde(rename = "endCursor")]
end_cursor: Option<String>,
}

#[derive(serde::Deserialize)]
struct GraphQlIssue {
number: u64,
title: String,
#[serde(rename = "createdAt")]
created_at: chrono::DateTime<chrono::Utc>,
assignees: GraphQlNodes<GraphQlUser>,
labels: Option<GraphQlNodes<GraphQlLabel>>,
comments: GraphQlNodes<GraphQlComment>,
}

#[derive(serde::Deserialize)]
struct GraphQlNodes<T> {
nodes: Vec<Option<T>>,
}

#[derive(serde::Deserialize)]
struct GraphQlUser {
login: String,
}

#[derive(serde::Deserialize)]
struct GraphQlLabel {
name: String,
}

#[derive(serde::Deserialize)]
struct GraphQlComment {
#[serde(rename = "createdAt")]
created_at: chrono::DateTime<chrono::Utc>,
}

impl From<GraphQlIssue> for GoalIssue {
fn from(issue: GraphQlIssue) -> Self {
let assignees = issue
.assignees
.nodes
.into_iter()
.flatten()
.map(|assignee| assignee.login)
.collect();

let labels = issue
.labels
.map(|labels| {
labels
.nodes
.into_iter()
.flatten()
.map(|label| label.name)
.collect()
})
.unwrap_or_default();

let last_comment = issue
.comments
.nodes
.into_iter()
.flatten()
.next()
.map(|comment| LastGoalComment {
created_at: comment.created_at,
});

Self {
number: issue.number,
title: issue.title,
assignees,
created_at: issue.created_at,
labels,
last_comment,
}
}
}

impl GithubClient {
/// Get every open tracking issue in `rust-lang/rust-project-goals`,
/// including the latest comment's date.
pub async fn open_goal_issues(&self) -> anyhow::Result<Vec<GoalIssue>> {
let mut cursor = None::<String>;
let mut issues = Vec::new();

loop {
let mut response = self
.graphql_query(
r#"
query (
$owner: String!
$repo: String!
$label: String!
$cursor: String
) {
repository(owner: $owner, name: $repo) {
issues(
first: 100
after: $cursor
states: [OPEN]
labels: [$label]
orderBy: {
field: CREATED_AT
direction: ASC
}
) {
nodes {
number
title
createdAt
assignees(first: 100) {
nodes {
login
}
}
labels(first: 100) {
nodes {
name
}
}
comments(last: 1) {
nodes {
createdAt
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
"#,
serde_json::json!({
"owner": ORG,
"repo": REPO,
"label": LABEL,
"cursor": cursor.as_deref(),
}),
)
.await
.context("failed to fetch goal issues")?;

let page = response
.pointer_mut("/data/repository/issues")
Comment thread
nxsaken marked this conversation as resolved.
.context("data.repository.issues is missing from response")?
.take();

let page: GraphQlConnection =
serde_json::from_value(page).context("failed to deserialize page")?;

issues.extend(page.nodes.into_iter().flatten().map(GoalIssue::from));

if !page.page_info.has_next_page {
break;
}

cursor = page.page_info.end_cursor;
}

Ok(issues)
}
}
Loading