2026-05-06 01:42:38 +08:00
|
|
|
use std::ops::Deref;
|
|
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
use graphql_client::{GraphQLQuery, Response};
|
2026-05-06 01:42:38 +08:00
|
|
|
use reqwest::Method;
|
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
|
|
|
|
use crate::{
|
2026-05-08 02:23:28 +08:00
|
|
|
api::{
|
|
|
|
|
self,
|
|
|
|
|
issues::pull_request_query::{PullRequestQuerySearchEdgesNode, PullRequestState},
|
|
|
|
|
},
|
2026-05-06 01:42:38 +08:00
|
|
|
query,
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize)]
|
2026-05-06 01:42:38 +08:00
|
|
|
#[serde(transparent)]
|
|
|
|
|
#[repr(transparent)]
|
2026-05-08 02:23:28 +08:00
|
|
|
pub(crate) struct Id(String);
|
2026-05-06 01:42:38 +08:00
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
impl Deref for Id {
|
|
|
|
|
type Target = String;
|
2026-05-06 01:42:38 +08:00
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
2026-05-06 01:42:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
2026-05-08 02:23:28 +08:00
|
|
|
pub(crate) struct PullRequestPaginatedResponse {
|
|
|
|
|
pub(crate) items: Vec<PullRequest>,
|
|
|
|
|
pub(crate) start_cursor: Option<String>,
|
|
|
|
|
pub(crate) end_cursor: Option<String>,
|
2026-05-06 01:42:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
pub(crate) struct PullRequest {
|
2026-05-08 02:23:28 +08:00
|
|
|
pub(crate) title: String,
|
|
|
|
|
pub(crate) state: IssueState,
|
|
|
|
|
pub(crate) is_draft: bool,
|
|
|
|
|
pub(crate) repo_slug: String,
|
2026-05-06 01:42:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
#[derive(Debug, Clone, Copy, Deserialize)]
|
|
|
|
|
pub(crate) enum IssueState {
|
|
|
|
|
Open,
|
|
|
|
|
Closed,
|
|
|
|
|
Merged,
|
|
|
|
|
Unknown,
|
2026-05-06 01:42:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for Id {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
self.0.fmt(f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-06 01:42:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
#[derive(graphql_client::GraphQLQuery)]
|
|
|
|
|
#[graphql(
|
|
|
|
|
schema_path = "src/api/graphql/schema.json",
|
|
|
|
|
query_path = "src/api/graphql/list_pull_requests.graphql"
|
|
|
|
|
)]
|
|
|
|
|
struct PullRequestQuery;
|
|
|
|
|
|
2026-05-06 01:42:38 +08:00
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub(crate) struct ListPullRequests {
|
|
|
|
|
pub filter: Option<&'static str>,
|
|
|
|
|
pub page: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl query::QueryFn for ListPullRequests {
|
2026-05-08 02:23:28 +08:00
|
|
|
type Data = PullRequestPaginatedResponse;
|
2026-05-06 01:42:38 +08:00
|
|
|
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<Self::Data, Self::Error> {
|
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
|
if c.should_use_fixtures {
|
|
|
|
|
return super::mock::list_pull_requests(self.filter, self.page);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
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,
|
|
|
|
|
});
|
2026-05-06 01:42:38 +08:00
|
|
|
|
|
|
|
|
let res = c
|
2026-05-08 02:23:28 +08:00
|
|
|
.github_request(Method::POST, "/graphql")?
|
|
|
|
|
.json(&gql)
|
2026-05-06 01:42:38 +08:00
|
|
|
.send()
|
|
|
|
|
.await?;
|
|
|
|
|
|
2026-05-08 02:23:28 +08:00
|
|
|
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,
|
|
|
|
|
})
|
2026-05-06 01:42:38 +08:00
|
|
|
}
|
|
|
|
|
}
|