use std::ops::Deref; use graphql_client::{GraphQLQuery, Response}; use reqwest::Method; use serde::Deserialize; use crate::{ api::{ self, issues::pull_request_query::{PullRequestQuerySearchEdgesNode, PullRequestState}, }, query, }; #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize)] #[serde(transparent)] #[repr(transparent)] pub(crate) struct Id(String); impl Deref for Id { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug, Deserialize)] pub(crate) struct PullRequestPaginatedResponse { pub(crate) items: Vec, pub(crate) start_cursor: Option, pub(crate) end_cursor: Option, } #[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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl From 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>, pub page: u32, } impl query::QueryFn for ListPullRequests { type Data = PullRequestPaginatedResponse; type Error = api::Error; type Context = api::QueryContext; fn key(&self) -> query::Key { format!( "issues/list?pulls=true&page={}&filter={}", self.page, self.filter.unwrap_or_default() ) .into() } async fn run(&self, c: &Self::Context) -> Result { #[cfg(debug_assertions)] if c.should_use_fixtures { return super::mock::list_pull_requests(self.filter, self.page); } 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::POST, "/graphql")? .json(&gql) .send() .await?; let data = api::parse_graphql_response::(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::>() }) .unwrap_or_default(), start_cursor: data.search.page_info.start_cursor, end_cursor: data.search.page_info.end_cursor, }) } }