refactor: migrate to github gql api
This commit is contained in:
@@ -33,7 +33,7 @@ impl query::QueryFn for CreateDeviceCode {
|
||||
let res = c
|
||||
.http
|
||||
.post(format!(
|
||||
"https://github.com/login/device/code?client_id={}",
|
||||
"https://github.com/login/device/code?client_id={}&scope=repo,read:user",
|
||||
c.github.client_id
|
||||
))
|
||||
.header("Accept", "application/json")
|
||||
|
||||
28
src/api/graphql/list_pull_requests.graphql
Normal file
28
src/api/graphql/list_pull_requests.graphql
Normal file
@@ -0,0 +1,28 @@
|
||||
query PullRequestQuery($query: String!) {
|
||||
search(query: $query, first: 10, type: ISSUE) {
|
||||
issueCount
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
... on PullRequest {
|
||||
isDraft
|
||||
title
|
||||
state
|
||||
repository {
|
||||
name
|
||||
owner {
|
||||
__typename
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
177175
src/api/graphql/schema.json
Normal file
177175
src/api/graphql/schema.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,168 +1,51 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use graphql_client::{GraphQLQuery, Response};
|
||||
use reqwest::Method;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
api::{self, user},
|
||||
api::{
|
||||
self,
|
||||
issues::pull_request_query::{PullRequestQuerySearchEdgesNode, PullRequestState},
|
||||
},
|
||||
query,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct Id(u64);
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Issue {
|
||||
pub(crate) id: Id,
|
||||
pub(crate) node_id: String,
|
||||
pub(crate) url: String,
|
||||
pub(crate) repository_url: String,
|
||||
pub(crate) labels_url: String,
|
||||
pub(crate) comments_url: String,
|
||||
pub(crate) events_url: String,
|
||||
pub(crate) html_url: String,
|
||||
pub(crate) number: u64,
|
||||
pub(crate) state: String,
|
||||
pub(crate) state_reason: Option<String>,
|
||||
pub(crate) title: String,
|
||||
pub(crate) body: Option<String>,
|
||||
pub(crate) body_text: Option<String>,
|
||||
pub(crate) body_html: Option<String>,
|
||||
pub(crate) user: Option<user::User>,
|
||||
pub(crate) labels: Vec<Label>,
|
||||
pub(crate) assignee: Option<user::User>,
|
||||
#[serde(default)]
|
||||
pub(crate) assignees: Vec<user::User>,
|
||||
pub(crate) milestone: Option<Milestone>,
|
||||
pub(crate) locked: bool,
|
||||
pub(crate) active_lock_reason: Option<String>,
|
||||
pub(crate) comments: u64,
|
||||
pub(crate) pull_request: Option<PullRequest>,
|
||||
pub(crate) closed_at: Option<String>,
|
||||
pub(crate) created_at: String,
|
||||
pub(crate) updated_at: String,
|
||||
pub(crate) closed_by: Option<user::User>,
|
||||
pub(crate) author_association: String,
|
||||
pub(crate) draft: Option<bool>,
|
||||
pub(crate) timeline_url: Option<String>,
|
||||
pub(crate) repository: Option<Repository>,
|
||||
pub(crate) performed_via_github_app: Option<serde_json::Value>,
|
||||
pub(crate) reactions: Option<Reactions>,
|
||||
pub(crate) pinned_comment: Option<serde_json::Value>,
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) issue_type: Option<IssueType>,
|
||||
pub(crate) sub_issues_summary: Option<SubIssuesSummary>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum Label {
|
||||
Name(String),
|
||||
Detail(LabelDetail),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct LabelDetail {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) node_id: String,
|
||||
pub(crate) url: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) color: String,
|
||||
pub(crate) default: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Milestone {
|
||||
pub(crate) url: String,
|
||||
pub(crate) html_url: String,
|
||||
pub(crate) labels_url: String,
|
||||
pub(crate) id: u64,
|
||||
pub(crate) node_id: String,
|
||||
pub(crate) number: u64,
|
||||
pub(crate) state: String,
|
||||
pub(crate) title: String,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) creator: Option<user::User>,
|
||||
pub(crate) open_issues: u64,
|
||||
pub(crate) closed_issues: u64,
|
||||
pub(crate) created_at: String,
|
||||
pub(crate) updated_at: String,
|
||||
pub(crate) closed_at: Option<String>,
|
||||
pub(crate) due_on: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct PullRequest {
|
||||
pub(crate) url: String,
|
||||
pub(crate) html_url: String,
|
||||
pub(crate) diff_url: String,
|
||||
pub(crate) patch_url: String,
|
||||
pub(crate) merged_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Repository {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) node_id: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) full_name: String,
|
||||
pub(crate) owner: user::User,
|
||||
pub(crate) private: bool,
|
||||
pub(crate) html_url: String,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) fork: bool,
|
||||
pub(crate) url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Reactions {
|
||||
pub(crate) url: String,
|
||||
pub(crate) total_count: u64,
|
||||
#[serde(rename = "+1")]
|
||||
pub(crate) plus_one: u64,
|
||||
#[serde(rename = "-1")]
|
||||
pub(crate) minus_one: u64,
|
||||
pub(crate) laugh: u64,
|
||||
pub(crate) confused: u64,
|
||||
pub(crate) heart: u64,
|
||||
pub(crate) hooray: u64,
|
||||
pub(crate) rocket: u64,
|
||||
pub(crate) eyes: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct IssueType {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) node_id: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) color: Option<String>,
|
||||
pub(crate) created_at: Option<String>,
|
||||
pub(crate) updated_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct SubIssuesSummary {
|
||||
pub(crate) total: u64,
|
||||
pub(crate) completed: u64,
|
||||
pub(crate) percent_completed: f64,
|
||||
}
|
||||
pub(crate) struct Id(String);
|
||||
|
||||
impl Deref for Id {
|
||||
type Target = u64;
|
||||
type Target = String;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Id {
|
||||
fn from(id: u64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct PullRequestPaginatedResponse {
|
||||
pub(crate) items: Vec<PullRequest>,
|
||||
pub(crate) start_cursor: Option<String>,
|
||||
pub(crate) end_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct PullRequest {
|
||||
pub(crate) title: String,
|
||||
pub(crate) state: IssueState,
|
||||
pub(crate) is_draft: bool,
|
||||
pub(crate) repo_slug: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
pub(crate) enum IssueState {
|
||||
Open,
|
||||
Closed,
|
||||
Merged,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Id {
|
||||
@@ -171,15 +54,24 @@ impl std::fmt::Display for Id {
|
||||
}
|
||||
}
|
||||
|
||||
impl Issue {
|
||||
pub(crate) const FILTER_ASSIGNED: &'static str = "assigned";
|
||||
pub(crate) const FILTER_CREATED: &'static str = "created";
|
||||
pub(crate) const FILTER_MENTIONED: &'static str = "mentioned";
|
||||
pub(crate) const FILTER_SUBSCRIBED: &'static str = "subscribed";
|
||||
pub(crate) const FILTER_REPOS: &'static str = "repos";
|
||||
pub(crate) const FILTER_ALL: &'static str = "all";
|
||||
impl From<PullRequestState> for IssueState {
|
||||
fn from(state: PullRequestState) -> Self {
|
||||
match state {
|
||||
PullRequestState::OPEN => Self::Open,
|
||||
PullRequestState::CLOSED => Self::Closed,
|
||||
PullRequestState::MERGED => Self::Merged,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(graphql_client::GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "src/api/graphql/schema.json",
|
||||
query_path = "src/api/graphql/list_pull_requests.graphql"
|
||||
)]
|
||||
struct PullRequestQuery;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ListPullRequests {
|
||||
pub filter: Option<&'static str>,
|
||||
@@ -187,7 +79,7 @@ pub(crate) struct ListPullRequests {
|
||||
}
|
||||
|
||||
impl query::QueryFn for ListPullRequests {
|
||||
type Data = Vec<Issue>;
|
||||
type Data = PullRequestPaginatedResponse;
|
||||
type Error = api::Error;
|
||||
type Context = api::QueryContext;
|
||||
|
||||
@@ -206,21 +98,54 @@ impl query::QueryFn for ListPullRequests {
|
||||
return super::mock::list_pull_requests(self.filter, self.page);
|
||||
}
|
||||
|
||||
let page_string = self.page.to_string();
|
||||
let mut params: Vec<(&str, &str)> = Vec::with_capacity(4);
|
||||
params.push(("pulls", "true"));
|
||||
params.push(("page", &page_string));
|
||||
params.push(("direction", "desc"));
|
||||
if let Some(filter) = self.filter {
|
||||
params.push(("filter", filter))
|
||||
}
|
||||
let query_string = match self.filter {
|
||||
Some(filter) => format!("is:pr archived:false sort:updated-desc {}", filter),
|
||||
None => "is:pr archived:false sort:updated-desc".into(),
|
||||
};
|
||||
|
||||
let gql = PullRequestQuery::build_query(pull_request_query::Variables {
|
||||
query: query_string,
|
||||
});
|
||||
|
||||
let res = c
|
||||
.github_request(Method::GET, "/issues")?
|
||||
.query(¶ms)
|
||||
.github_request(Method::POST, "/graphql")?
|
||||
.json(&gql)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
api::parse_response::<Vec<Issue>>(res).await
|
||||
let data = api::parse_graphql_response::<pull_request_query::ResponseData>(res)
|
||||
.await?
|
||||
.data
|
||||
.unwrap();
|
||||
|
||||
Ok(PullRequestPaginatedResponse {
|
||||
items: data
|
||||
.search
|
||||
.edges
|
||||
.map(|it| {
|
||||
it.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|edge| {
|
||||
edge.node.and_then(|n| match n {
|
||||
PullRequestQuerySearchEdgesNode::PullRequest(p) => {
|
||||
Some(PullRequest {
|
||||
title: p.title,
|
||||
state: p.state.into(),
|
||||
is_draft: p.is_draft,
|
||||
repo_slug: format!(
|
||||
"{}/{}",
|
||||
p.repository.owner.login, p.repository.name
|
||||
),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
start_cursor: data.search.page_info.start_cursor,
|
||||
end_cursor: data.search.page_info.end_cursor,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub(crate) fn list_repos() -> Result<Vec<repo::Repository>, api::Error> {
|
||||
pub(crate) fn list_pull_requests(
|
||||
filter: Option<&str>,
|
||||
page: u32,
|
||||
) -> Result<Vec<issues::Issue>, api::Error> {
|
||||
) -> Result<issues::PullRequestPaginatedResponse, api::Error> {
|
||||
let filter = filter.unwrap_or_default();
|
||||
let json = issues_pull_requests(filter, page).ok_or_else(|| {
|
||||
api::Error::MissingMockFixture(format!("issues.pull_requests filter={filter} page={page}"))
|
||||
|
||||
Reference in New Issue
Block a user