use crate::schema::schema;
use crate::api::client::{ApiError, GitHub};
use crate::api::model::{NodeId, RepoId};
use crate::api::notifications::ThreadAt;
use crate::api::queries::{
AddCommentPayload, AutoMergePayload, CloseIssuePayload, ClosePrPayload, DequeuePayload,
EnqueuePayload, GitObjectId, MergePayload, NoAutoMergePayload, ReadyPayload,
ReopenIssuePayload, ReopenPrPayload, StarPayload, UnstarPayload, UpdateBranchPayload,
};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Change {
Comment {
subject: NodeId,
body: String,
},
CommitComment {
repo: RepoId,
oid: String,
body: String,
},
Reply {
thread: NodeId,
body: String,
},
Star {
repo: RepoId,
id: NodeId,
starred: bool,
},
Merge {
pr: NodeId,
method: MergeMethod,
head: String,
},
EnableAutoMerge {
pr: NodeId,
method: MergeMethod,
head: String,
},
DisableAutoMerge {
pr: NodeId,
},
Enqueue {
pr: NodeId,
head: String,
},
Dequeue {
pr: NodeId,
},
SetPrOpen {
pr: NodeId,
open: bool,
},
CloseIssue {
issue: NodeId,
reason: CloseReason,
},
ReopenIssue {
issue: NodeId,
},
ReadyForReview {
pr: NodeId,
},
UpdateBranch {
pr: NodeId,
rebase: bool,
head: String,
},
Rerun {
repo: RepoId,
run: u64,
failed_only: bool,
},
RerunJob {
repo: RepoId,
job: u64,
},
CancelRun {
repo: RepoId,
run: u64,
},
MarkRead {
thread: String,
},
MarkDone {
thread: ThreadAt,
},
MarkAllRead {
repo: Option<RepoId>,
last_read_at: String,
},
MarkEach {
threads: Vec<ThreadAt>,
done: bool,
},
Edit(crate::api::edit::Edit),
Discussion(crate::api::discussion::Discuss),
Project(crate::api::project::ProjectChange),
SetProjectField {
project: NodeId,
item: NodeId,
field: NodeId,
value: Option<crate::api::project::FieldValue>,
said: String,
},
Alert(crate::api::security::AlertChange),
Manage(crate::api::manage::Manage),
Settings(crate::api::settings::SettingsChange),
}
#[derive(cynic::Enum, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cynic(graphql_type = "PullRequestMergeMethod")]
pub enum MergeMethod {
Merge,
Squash,
Rebase,
}
impl MergeMethod {
pub fn describe(self) -> &'static str {
match self {
Self::Merge => "Create a merge commit",
Self::Squash => "Squash and merge",
Self::Rebase => "Rebase and merge",
}
}
}
#[derive(cynic::Enum, Debug, Clone, Copy, PartialEq, Eq)]
#[cynic(graphql_type = "IssueClosedStateReason")]
pub enum CloseReason {
Completed,
NotPlanned,
Duplicate,
}
#[derive(cynic::Enum, Debug, Clone, Copy, PartialEq, Eq)]
#[cynic(graphql_type = "PullRequestBranchUpdateMethod")]
pub enum UpdateMethod {
Merge,
Rebase,
}
impl GitHub {
pub async fn change(&self, change: &Change) -> Result<(), ApiError> {
use cynic::MutationBuilder;
match change {
Change::Comment { subject, body } => {
let op = AddComment::build(AddCommentVariables {
subject: subject.gql(),
body: body.clone(),
});
self.mutate(op).await.map(drop)
}
Change::CommitComment { repo, oid, body } => {
self.comment_on_commit(repo, oid, body).await
}
Change::Reply { thread, body } => {
let op = crate::api::queries::Reply::build(crate::api::queries::ReplyVariables {
thread_id: thread.gql(),
body: body.clone(),
});
self.mutate(op).await.map(drop)
}
Change::Star { id, starred, .. } => {
let vars = IdVariables { id: id.gql() };
if *starred {
self.mutate(AddStar::build(vars)).await.map(drop)
} else {
self.mutate(RemoveStar::build(vars)).await.map(drop)
}
}
Change::Merge { pr, method, head } => {
let op = MergePr::build(MergeVariables {
pr: pr.gql(),
method: *method,
head: GitObjectId(head.clone()),
});
self.mutate(op).await.map(drop)
}
Change::EnableAutoMerge { pr, method, head } => {
let op = EnableAutoMerge::build(MergeVariables {
pr: pr.gql(),
method: *method,
head: GitObjectId(head.clone()),
});
self.mutate(op).await.map(drop)
}
Change::DisableAutoMerge { pr } => {
let op = DisableAutoMerge::build(IdVariables { id: pr.gql() });
self.mutate(op).await.map(drop)
}
Change::Enqueue { pr, head } => {
let op = Enqueue::build(EnqueueVariables {
pr: pr.gql(),
head: GitObjectId(head.clone()),
});
self.mutate(op).await.map(drop)
}
Change::Dequeue { pr } => {
let op = Dequeue::build(IdVariables { id: pr.gql() });
self.mutate(op).await.map(drop)
}
Change::SetPrOpen { pr, open } => {
let vars = IdVariables { id: pr.gql() };
if *open {
self.mutate(ReopenPr::build(vars)).await.map(drop)
} else {
self.mutate(ClosePr::build(vars)).await.map(drop)
}
}
Change::CloseIssue { issue, reason } => {
let op = CloseIssue::build(CloseIssueVariables {
issue: issue.gql(),
reason: *reason,
});
self.mutate(op).await.map(drop)
}
Change::ReopenIssue { issue } => {
let op = ReopenIssue::build(IdVariables { id: issue.gql() });
self.mutate(op).await.map(drop)
}
Change::ReadyForReview { pr } => {
let op = MarkReady::build(IdVariables { id: pr.gql() });
self.mutate(op).await.map(drop)
}
Change::UpdateBranch { pr, rebase, head } => {
let op = UpdateBranch::build(UpdateBranchVariables {
pr: pr.gql(),
method: if *rebase {
UpdateMethod::Rebase
} else {
UpdateMethod::Merge
},
head: GitObjectId(head.clone()),
});
self.mutate(op).await.map(drop)
}
Change::Rerun {
repo,
run,
failed_only,
} => {
let what = if *failed_only {
"rerun-failed-jobs"
} else {
"rerun"
};
self.rest_write(
http::Method::POST,
&format!("/repos/{repo}/actions/runs/{run}/{what}"),
None,
)
.await
}
Change::RerunJob { repo, job } => {
self.rest_write(
http::Method::POST,
&format!("/repos/{repo}/actions/jobs/{job}/rerun"),
None,
)
.await
}
Change::CancelRun { repo, run } => {
self.rest_write(
http::Method::POST,
&format!("/repos/{repo}/actions/runs/{run}/cancel"),
None,
)
.await
}
Change::MarkRead { thread } => self.mark(thread, false).await,
Change::MarkDone { thread } => {
self.mark(&thread.id, true).await?;
self.remember_done(std::slice::from_ref(thread)).await;
Ok(())
}
Change::MarkAllRead { repo, last_read_at } => {
let path = match repo {
Some(repo) => format!("/repos/{repo}/notifications"),
None => "/notifications".to_owned(),
};
let body = serde_json::json!({ "last_read_at": last_read_at, "read": true });
self.rest_write(http::Method::PUT, &path, Some(&body)).await
}
Change::MarkEach { threads, done } => {
let mut marked = 0;
let mut sent = Ok(());
for thread in threads {
if let Err(err) = self.mark(&thread.id, *done).await {
sent = Err(match err {
ApiError::Http { status, message } if marked > 0 => ApiError::Http {
status,
message: format!("{message} (after {marked} of {})", threads.len()),
},
err => err,
});
break;
}
marked += 1;
}
if *done {
self.remember_done(threads.get(..marked).unwrap_or_default())
.await;
}
sent
}
Change::Edit(edit) => self.edit(edit).await,
Change::Discussion(change) => self.discuss(change).await,
Change::Project(change) => self.change_project(change).await,
Change::SetProjectField {
project,
item,
field,
value,
..
} => {
self.set_project_field(project, item, field, value.as_ref())
.await
}
Change::Alert(change) => self.set_alert(change).await,
Change::Manage(change) => self.manage(change).await,
Change::Settings(change) => self.change_setting(change).await,
}
}
}
impl GitHub {
async fn mark(&self, thread: &str, done: bool) -> Result<(), ApiError> {
let path = format!("/notifications/threads/{thread}");
let method = if done {
http::Method::DELETE
} else {
http::Method::PATCH
};
self.rest_write(method, &path, None).await
}
}
#[derive(cynic::QueryVariables, Debug)]
pub struct IdVariables {
pub id: cynic::Id,
}
#[derive(cynic::QueryVariables, Debug)]
pub struct AddCommentVariables {
pub subject: cynic::Id,
pub body: String,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "AddCommentVariables")]
pub struct AddComment {
#[arguments(input: { subjectId: $subject, body: $body })]
pub add_comment: Option<AddCommentPayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct AddStar {
#[arguments(input: { starrableId: $id })]
pub add_star: Option<StarPayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct RemoveStar {
#[arguments(input: { starrableId: $id })]
pub remove_star: Option<UnstarPayload>,
}
#[derive(cynic::QueryVariables, Debug)]
pub struct MergeVariables {
pub pr: cynic::Id,
pub method: MergeMethod,
pub head: GitObjectId,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "MergeVariables")]
pub struct MergePr {
#[arguments(input: { pullRequestId: $pr, mergeMethod: $method, expectedHeadOid: $head })]
pub merge_pull_request: Option<MergePayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "MergeVariables")]
pub struct EnableAutoMerge {
#[arguments(input: { pullRequestId: $pr, mergeMethod: $method, expectedHeadOid: $head })]
pub enable_pull_request_auto_merge: Option<AutoMergePayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct DisableAutoMerge {
#[arguments(input: { pullRequestId: $id })]
pub disable_pull_request_auto_merge: Option<NoAutoMergePayload>,
}
#[derive(cynic::QueryVariables, Debug)]
pub struct EnqueueVariables {
pub pr: cynic::Id,
pub head: GitObjectId,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "EnqueueVariables")]
pub struct Enqueue {
#[arguments(input: { pullRequestId: $pr, expectedHeadOid: $head })]
pub enqueue_pull_request: Option<EnqueuePayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct Dequeue {
#[arguments(input: { id: $id })]
pub dequeue_pull_request: Option<DequeuePayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct ClosePr {
#[arguments(input: { pullRequestId: $id })]
pub close_pull_request: Option<ClosePrPayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct ReopenPr {
#[arguments(input: { pullRequestId: $id })]
pub reopen_pull_request: Option<ReopenPrPayload>,
}
#[derive(cynic::QueryVariables, Debug)]
pub struct CloseIssueVariables {
pub issue: cynic::Id,
pub reason: CloseReason,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "CloseIssueVariables")]
pub struct CloseIssue {
#[arguments(input: { issueId: $issue, stateReason: $reason })]
pub close_issue: Option<CloseIssuePayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct ReopenIssue {
#[arguments(input: { issueId: $id })]
pub reopen_issue: Option<ReopenIssuePayload>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "IdVariables")]
pub struct MarkReady {
#[arguments(input: { pullRequestId: $id })]
pub mark_pull_request_ready_for_review: Option<ReadyPayload>,
}
#[derive(cynic::QueryVariables, Debug)]
pub struct UpdateBranchVariables {
pub pr: cynic::Id,
pub method: UpdateMethod,
pub head: GitObjectId,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "UpdateBranchVariables")]
pub struct UpdateBranch {
#[arguments(input: { pullRequestId: $pr, updateMethod: $method, expectedHeadOid: $head })]
pub update_pull_request_branch: Option<UpdateBranchPayload>,
}