Skip to content

Commit

Permalink
ghd: add PR details offcanvas
Browse files Browse the repository at this point in the history
Signed-off-by: Joao Eduardo Luis <joao@abysmo.io>
  • Loading branch information
jecluis committed May 7, 2023
1 parent 56e8c58 commit dc4b059
Show file tree
Hide file tree
Showing 19 changed files with 951 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src-tauri/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ pub enum GHDError {
UnknownError,
NotFoundError,
DBVersionInTheFuture,
RepositoryNotFoundError,
PullRequestNotFoundError,
}
39 changes: 38 additions & 1 deletion src-tauri/src/gh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use sqlx::Row;

use crate::{db::DB, errors::GHDError};

use self::types::{GithubUser, PullRequestTableEntry};
use self::types::{GithubUser, PullRequestInfo, PullRequestTableEntry};

pub mod api;
pub mod gql;
Expand Down Expand Up @@ -470,6 +470,43 @@ impl Github {
prs::get_involved_prs(&db, &login).await
}

/// Obtain a specific Pull Request's information.
///
/// # Arguments
///
/// * `db` - A GHD Database handle.
/// * `prid` - A Pull Request database ID.
///
pub async fn get_pull_request_info(
self: &Self,
db: &DB,
prid: &i64,
) -> Result<PullRequestInfo, GHDError> {
let entry = match prs::get_issue_by_id(&db, &prid).await {
Err(err) => return Err(err),
Ok(res) => res,
};

let token = match self.get_token(&db).await {
Ok(t) => t.clone(),
Err(err) => return Err(err),
};

let res = match gql::get_pull_request_info(
&token,
&entry.repo_owner,
&entry.repo_name,
&entry.number,
)
.await
{
Ok(r) => r,
Err(err) => return Err(err),
};

Ok(res)
}

/// Marks a specified Pull Request as having been viewed.
///
/// # Arguments
Expand Down
229 changes: 219 additions & 10 deletions src-tauri/src/gh/gql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,27 @@ mod queries;
use graphql_client::GraphQLQuery;
use queries::{user_info, UserInfo};

use crate::errors::GHDError;
use crate::{
errors::GHDError,
gh::types::{Label, UserReview},
};

use self::queries::{
get_pull_request_info::{
self, GetPullRequestInfoRepositoryPullRequestAuthor,
},
search_issues::{
self, IssueState, PullRequestReviewDecision, PullRequestState,
SearchIssuesSearchNodes, SearchIssuesSearchNodesOnIssue,
SearchIssuesSearchNodesOnIssueAuthor,
self, IssueState, PullRequestReviewDecision, SearchIssuesSearchNodes,
SearchIssuesSearchNodesOnIssue, SearchIssuesSearchNodesOnIssueAuthor,
SearchIssuesSearchNodesOnPullRequest,
SearchIssuesSearchNodesOnPullRequestAuthor, UserFragment,
},
SearchIssues,
GetPullRequestInfo, SearchIssues,
};

use super::types::{Issue, PullRequest, UserUpdate};
use super::types::{
GithubUser, Issue, Milestone, PullRequest, PullRequestInfo, UserUpdate,
};

#[derive(serde::Deserialize, Debug)]
struct GQLResData<T> {
Expand Down Expand Up @@ -215,6 +222,43 @@ impl GithubGQLRequest {

Ok(response_data)
}

/// Obtain a given Pull Request's information.
///
/// # Arguments
///
/// * `repo_owner` - String containing the Pull Request's repository owner.
/// * `repo_name` - String containing the Pull Request's repository name.
/// * `pr_number` - The Pull Request's number.
///
async fn get_pull_request_info(
self: &Self,
repo_owner: &String,
repo_name: &String,
pr_number: &i64,
) -> Result<get_pull_request_info::ResponseData, GHDError> {
let vars = get_pull_request_info::Variables {
owner: repo_owner.clone(),
repo: repo_name.clone(),
prid: pr_number.clone(),
};
let response_data: get_pull_request_info::ResponseData = match self
.execute::<GetPullRequestInfo, get_pull_request_info::ResponseData>(
vars,
)
.await
{
Ok(res) => res,
Err(GHDError::UnknownError) => {
panic!("error: unknown error");
}
Err(err) => {
return Err(err);
}
};

Ok(response_data)
}
}

/// Obtain all open issues for the provided `login`. This includes Pull
Expand Down Expand Up @@ -392,10 +436,10 @@ fn get_issue_from_pull_request(
repo_name: node.repository.name.clone(),
repo_owner: node.repository.owner.login.clone(),
state: match &node.state {
PullRequestState::OPEN => String::from("open"),
PullRequestState::CLOSED => String::from("closed"),
PullRequestState::MERGED => String::from("merged"),
PullRequestState::Other(v) => v.clone(),
search_issues::PullRequestState::OPEN => String::from("open"),
search_issues::PullRequestState::CLOSED => String::from("closed"),
search_issues::PullRequestState::MERGED => String::from("merged"),
search_issues::PullRequestState::Other(v) => v.clone(),
},
created_at: node.created_at,
updated_at: node.updated_at,
Expand All @@ -405,6 +449,171 @@ fn get_issue_from_pull_request(
}
}

/// Obtain a given Pull Request's information.
///
pub async fn get_pull_request_info(
token: &String,
repo_owner: &String,
repo_name: &String,
pr_number: &i64,
) -> Result<PullRequestInfo, GHDError> {
// helper types
type PRInfoAuthor = GetPullRequestInfoRepositoryPullRequestAuthor;
type PRState = get_pull_request_info::PullRequestState;
type PRMilestoneState = get_pull_request_info::MilestoneState;
type ReviewAuthor = get_pull_request_info::GetPullRequestInfoRepositoryPullRequestReviewsNodesAuthor;
type ReviewState = get_pull_request_info::PullRequestReviewState;

let res = match GithubGQLRequest::new(&token)
.get_pull_request_info(&repo_owner, &repo_name, &pr_number)
.await
{
Ok(v) => v,
Err(err) => return Err(err),
};

let repo = match res.repository {
None => return Err(GHDError::RepositoryNotFoundError),
Some(v) => v,
};
let pr = match repo.pull_request {
None => return Err(GHDError::PullRequestNotFoundError),
Some(v) => v,
};

let unknown_user = GithubUser {
id: -1,
login: String::from("unknown"),
name: String::from("unknown"),
avatar_url: String::new(),
};

let author: GithubUser = match pr.author {
None => unknown_user.clone(),
Some(v) => match v {
PRInfoAuthor::User(user) => GithubUser {
id: user.database_id.unwrap_or(-1),
login: user.login,
name: user.name.unwrap_or(String::from("unknown")),
avatar_url: user.avatar_url,
},
_ => unknown_user.clone(),
},
};

let mut labels: Vec<Label> = vec![];
if let Some(l) = &pr.labels {
if let Some(lst) = &l.nodes {
for entry in lst {
if let Some(label) = &entry {
labels.push(Label {
name: label.name.clone(),
color: label.color.clone(),
});
}
}
}
}

let mut participants: Vec<GithubUser> = vec![];
if let Some(lst) = &pr.participants.nodes {
for entry in lst {
if let Some(user) = &entry {
participants.push(GithubUser {
id: match &user.database_id {
None => -1,
Some(v) => *v,
},
login: user.login.clone(),
name: match &user.name {
None => String::from("unknown"),
Some(v) => v.clone(),
},
avatar_url: user.avatar_url.clone(),
});
}
}
}

let mut reviews: Vec<UserReview> = vec![];
if let Some(r) = &pr.reviews {
if let Some(lst) = &r.nodes {
for entry in lst {
if let Some(rev) = &entry {
reviews.push(UserReview {
author: match &rev.author {
None => unknown_user.clone(),
Some(ReviewAuthor::User(u)) => GithubUser {
id: u.database_id.unwrap_or(-1),
login: u.login.clone(),
name: match &u.name {
None => String::from("unknown"),
Some(v) => v.clone(),
},
avatar_url: u.avatar_url.clone(),
},
Some(_) => unknown_user.clone(),
},
state: match &rev.state {
ReviewState::APPROVED => String::from("approved"),
ReviewState::CHANGES_REQUESTED => {
String::from("changes_requested")
}
ReviewState::COMMENTED => String::from("commented"),
ReviewState::DISMISSED => String::from("dismissed"),
ReviewState::PENDING => String::from("pending"),
ReviewState::Other(v) => v.clone(),
},
});
}
}
}
}

Ok(PullRequestInfo {
number: pr.number,
title: pr.title,
body_html: pr.body_html,
author,
repo_owner: pr.repository.owner.login,
repo_name: pr.repository.name,
url: pr.url,
state: match &pr.state {
PRState::OPEN => String::from("open"),
PRState::CLOSED => String::from("closed"),
PRState::MERGED => String::from("merged"),
PRState::Other(v) => v.clone(),
},
is_draft: pr.is_draft,
milestone: match &pr.milestone {
None => None,
Some(m) => Some(Milestone {
title: m.title.clone(),
state: match &m.state {
PRMilestoneState::OPEN => String::from("open"),
PRMilestoneState::CLOSED => String::from("closed"),
PRMilestoneState::Other(v) => v.clone(),
},
due_on: match &m.due_on {
None => None,
Some(d) => Some(*d),
},
due_on_ts: match &m.due_on {
None => None,
Some(d) => Some(d.timestamp()),
},
}),
},
labels,
total_comments: match &pr.total_comments_count {
None => 0,
Some(v) => *v,
},
participants,
reviews,
})
}

/// Obtain a user `login` and `id` from a given GraphQL `User Fragment`.
///
fn get_username_and_id(user: &UserFragment) -> (String, i64) {
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/gh/gql/custom_types.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[allow(clippy::upper_case_acronyms)]
pub type URI = String;
pub type DateTime = chrono::DateTime<chrono::Utc>;
pub type HTML = String;
Loading

0 comments on commit dc4b059

Please sign in to comment.