Skip to content

Commit

Permalink
Merge pull request #6 from Alokit-Innovations/tr/coverage
Browse files Browse the repository at this point in the history
Implement adding comment and auto assigning to relevant reviewers
  • Loading branch information
tapishr authored Oct 25, 2023
2 parents a9a807d + 425430e commit 5883a31
Show file tree
Hide file tree
Showing 14 changed files with 577 additions and 80 deletions.
19 changes: 19 additions & 0 deletions vibi-dpu/src/bitbucket/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use reqwest::Client;
use super::config::get_client;
use crate::db::auth::{save_auth_info_to_db, auth_info};
use crate::utils::auth::AuthInfo;
use crate::utils::review::Review;

pub async fn get_access_token_from_bitbucket(code: &str) -> Option<AuthInfo> {
let client = get_client();
Expand Down Expand Up @@ -154,4 +155,22 @@ fn set_git_remote_url(git_url: &str, directory: &str, access_token: &str) {
Err(e) => eprintln!("set_git_url stdout error: {}", e),
};
println!("git pull output = {:?}, {:?}", &output.stdout, &output.stderr);
}

pub async fn get_access_token_review(review: &Review) -> Option<String> {
let authinfo_opt = auth_info();
if authinfo_opt.is_none() {
return None;
}
let auth_info = authinfo_opt.expect("Uncaught error in authinfo_opt");
let mut access_token = auth_info.access_token().clone();
let clone_url = review.clone_url();
let directory = review.clone_dir();
let new_auth_opt = update_access_token(&auth_info, &clone_url, &directory).await;
if new_auth_opt.is_none() {
return None;
}
let new_auth = new_auth_opt.expect("empty new_auth_opt");
access_token = new_auth.access_token().to_string();
return Some(access_token);
}
62 changes: 62 additions & 0 deletions vibi-dpu/src/bitbucket/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::{env, collections::HashMap};

use reqwest::{Response, header::{HeaderMap, HeaderValue}};
use serde::Serialize;
use serde_json::Value;

use crate::db::auth::auth_info;
use crate::db::user::get_workspace_user_from_db;
use crate::utils::review::Review;
use crate::utils::user::BitbucketUser;

use super::{config::{bitbucket_base_url, get_client, prepare_headers}};

#[derive(Serialize)]
struct Comment {
content: Content,
}

#[derive(Serialize)]
struct Content {
raw: String,
}
pub async fn add_comment(comment_text: &str, review: &Review, access_token: &str) {
let url = prepare_add_comment_url(review);
let comment_payload = prepare_body(comment_text);
let client = get_client();
let headers_opt = prepare_headers(&access_token);
if headers_opt.is_none() {
eprintln!("Unable to prepare_headers_comment, empty headers_opt");
return;
}
let headers = headers_opt.expect("Empty headers_opt");
let response_res = client.post(&url).
headers(headers).json(&comment_payload).send().await;
if response_res.is_err() {
let e = response_res.expect_err("No error in response_res");
eprintln!("Error in post request for adding comment - {:?}", e);
return;
}
let response = response_res.expect("Error in getting response");
println!("response from comment post request = {:?}", &response);
}

fn prepare_add_comment_url(review: &Review) -> String {
let url = format!(
"{}/repositories/{}/{}/pullrequests/{}/comments",
bitbucket_base_url(),
review.repo_owner(),
review.repo_name(),
review.id()
);
println!("comment url = {}", &url);
return url;
}
fn prepare_body(comment_text: &str) -> Comment {
let comment_payload = Comment {
content: Content {
raw: comment_text.to_string(),
},
};
return comment_payload;
}
61 changes: 43 additions & 18 deletions vibi-dpu/src/bitbucket/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{env, collections::HashMap};
use std::error::Error;

use reqwest::{Response, header::{HeaderMap, HeaderValue}};
use serde_json::Value;
Expand All @@ -19,29 +20,28 @@ pub fn bitbucket_base_url() -> String {
}

pub async fn get_api_values(url: &str, access_token: &str, params: Option<HashMap<&str, &str>> ) -> Vec<Value> {
let response_opt = get_api(url, access_token, &params).await;
let response_opt = get_api_response(url, None, access_token, &params).await;
println!("response of get_api = {:?}", &response_opt);
let (mut response_values, next_url) = deserialize_response(response_opt).await;
let (mut response_values, next_url) = deserialize_paginated_response(response_opt).await;
if next_url.is_some() {
let mut page_values = get_all_pages(next_url, access_token, &params).await;
response_values.append(&mut page_values);
}
return response_values;
}

pub async fn get_api(url: &str, access_token: &str, params: &Option<HashMap<&str, &str>> ) -> Option<Response>{
println!("GET api url = {}", url);
let client = get_client();
let mut headers = reqwest::header::HeaderMap::new();
headers.insert( reqwest::header::AUTHORIZATION,
format!("Bearer {}", access_token).parse().expect("Invalid auth header"), );
headers.insert("Accept",
"application/json".parse().expect("Invalid Accept header"));
let res_opt = get_api_response(url, headers, access_token, params).await;
return res_opt;
}

async fn get_api_response(url: &str, headers: reqwest::header::HeaderMap, access_token: &str, params: &Option<HashMap<&str, &str>>) -> Option<Response>{
pub async fn get_api_response(url: &str, headers_opt: Option<reqwest::header::HeaderMap>, access_token: &str, params: &Option<HashMap<&str, &str>>) -> Option<Response>{
let mut headers;
if headers_opt.is_none() {
let headers_opt_new = prepare_headers(&access_token);
if headers_opt_new.is_none() {
eprintln!("Unable to prepare_headers, empty headers_opt");
return None;
}
headers = headers_opt_new.expect("Empty headers_opt");
} else {
headers = headers_opt.expect("Empty headers_opt");
}
let client = get_client();
let get_res = client.get(url).headers(headers).send().await;
if get_res.is_err() {
Expand All @@ -57,7 +57,7 @@ async fn get_api_response(url: &str, headers: reqwest::header::HeaderMap, access
return Some(response);
}

async fn deserialize_response(response_opt: Option<Response>) -> (Vec<Value>, Option<String>) {
async fn deserialize_paginated_response(response_opt: Option<Response>) -> (Vec<Value>, Option<String>) {
let mut values_vec = Vec::new();
if response_opt.is_none() {
eprintln!("Response is None, can't deserialize");
Expand Down Expand Up @@ -97,8 +97,8 @@ async fn get_all_pages(next_url: Option<String>, access_token: &str, params: &Op
if url == "null" {
break;
}
let response_opt = get_api(url, access_token, params).await;
let (mut response_values, url_opt) = deserialize_response(response_opt).await;
let response_opt = get_api_response(url, None, access_token, params).await;
let (mut response_values, url_opt) = deserialize_paginated_response(response_opt).await;
next_url_mut = url_opt.clone();
values_vec.append(&mut response_values);
}
Expand All @@ -117,4 +117,29 @@ pub fn prepare_auth_headers(access_token: &str) -> Option<HeaderMap>{
let headerval = headervalres.expect("Empty headervalres");
headers_map.insert("Authorization", headerval);
return Some(headers_map);
}

pub fn prepare_headers(access_token: &str) -> Option<HeaderMap> {
let mut headers = reqwest::header::HeaderMap::new();
let auth_header_res = format!("Bearer {}", access_token).parse();
if auth_header_res.is_err() {
let e = auth_header_res
.expect_err("Empty error in auth_header_res");
eprintln!("Invalid auth header: {:?}", e);
return None;
}
let header_authval = auth_header_res
.expect("Uncaught error in auth_header_res");
headers.insert( reqwest::header::AUTHORIZATION, header_authval);
let header_accept_res = "application/json".parse();
if header_accept_res.is_err() {
let e = header_accept_res
.expect_err("No error in header_accept_res");
eprintln!("Invalide accept header val, error: {:?}", e);
return None;
}
let header_acceptval = header_accept_res
.expect("Uncaught error in header_accept_res");
headers.insert("Accept", header_acceptval);
return Some(headers);
}
4 changes: 3 additions & 1 deletion vibi-dpu/src/bitbucket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ pub mod workspace;
pub mod repo;
pub mod config;
pub mod webhook;
pub mod user;
pub mod user;
pub mod comment;
pub mod reviewer;
8 changes: 7 additions & 1 deletion vibi-dpu/src/bitbucket/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ pub async fn get_workspace_repos(workspace: &str, access_token: &str) -> Option<
let response_json = get_api_values(&repos_url, access_token, None).await;
let mut repos_data = Vec::new();
for repo_json in response_json {
let is_private_res = repo_json["is_private"].as_bool();
let mut is_private = true;
if is_private_res.is_none() {
eprintln!("Error in deserializing is_private_res: {:?}", &repo_json);
}
is_private = is_private_res.expect("Uncaught error in is_private_res");
let val = Repository::new(
repo_json["name"].to_string().trim_matches('"').to_string(),
repo_json["uuid"].to_string().trim_matches('"').to_string(),
repo_json["owner"]["username"].to_string().trim_matches('"').to_string(),
repo_json["is_private"].as_bool().unwrap_or(false),
is_private,
repo_json["links"]["clone"].as_array()
.expect("Unable to convert clone to array").iter().filter(|clone_val| {
clone_val["name".to_string()].as_str() == Some("ssh")
Expand Down
159 changes: 159 additions & 0 deletions vibi-dpu/src/bitbucket/reviewer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use reqwest::{Response, header::{HeaderMap, HeaderValue}};
use serde::Serialize;
use serde_json::Value;
use reqwest::Error;
use std::io;

use crate::db::user::get_workspace_user_from_db;
use crate::utils::review::Review;
use crate::utils::user::BitbucketUser;

use super::{config::{bitbucket_base_url, get_client, prepare_headers}};

pub async fn add_reviewers(user_key: &str, review: &Review, access_token: &str) {
let url = prepare_get_prinfo_url(review.repo_owner(), review.repo_name(), review.id());
let get_response = get_pr_info(&url, access_token).await;
let reviewers_opt = add_user_to_reviewers(get_response, user_key).await;
if reviewers_opt.is_none() {
eprintln!("[add_reviewers] Unable to add reviewers for review: {}", review.id());
}
let (reviewers, pr_info_json) = reviewers_opt.expect("Empty reviewers_opt");
let put_payload = prepare_put_body(&reviewers, &pr_info_json);
put_reviewers(&url, access_token, &put_payload).await;
}

async fn add_user_to_reviewers(response_res: Option<Response>, user_key: &str) -> Option<(Vec<BitbucketUser>, Value)> {
let reviewers_opt = parse_reviewers_from_prinfo(response_res).await;
if reviewers_opt.is_none() {
eprintln!("Unable to parse and add reviewers");
return None;
}
let (mut reviewers, get_response_json) = reviewers_opt.expect("Empty reviewers_opt");
println!("reviewers = {:?}", reviewers);
// Get user from db who needs to be added to reviewers
let user_from_db_opt = get_workspace_user_from_db(&user_key);
if user_from_db_opt.is_none() {
eprintln!("Empty user_from_db_opt");
return None;
}
let user_from_db = user_from_db_opt.expect("empty user_from_db_opt");
println!("user_from_db = {:?}", &user_from_db);
// For each user in user_from_db.users()...
for user in user_from_db.users().iter() {
// If the reviewers vector doesn't contain the user...
if !reviewers.contains(user) {
// Add the user to reviewers
reviewers.push(user.clone());
}
}
println!("Updated reviewers = {:?}", reviewers);
return Some((reviewers, get_response_json));
}

fn prepare_put_body(updated_reviewers: &Vec<BitbucketUser>, pr_info_json: &Value) -> Option<Value> {
// Serialize and add updated reviewers to response json
let reviewers_obj_res = serde_json::to_value(updated_reviewers);
if reviewers_obj_res.is_err() {
let e = reviewers_obj_res.expect_err("No error in reviewers_obj_res");
eprintln!("Unable to serialize users: {:?}", e);
return None;
}
let reviewers_obj = reviewers_obj_res.expect("Uncaught error in reviewers_obj_res");
let mut response_json = pr_info_json.to_owned();
let obj_opt = response_json.as_object_mut();
if obj_opt.is_none() {
eprintln!("Unable to get mutable reviewer response obj");
return None;
}
// Update obj
let obj = obj_opt.expect("empty obj_opt");
obj.insert("reviewers".to_string(), reviewers_obj);
obj.remove("summary"); // API gives error if not removed
return Some(response_json);
}

async fn parse_reviewers_from_prinfo(response_res: Option<Response>) -> Option<(Vec<BitbucketUser>, Value)>{
if response_res.is_none() {
eprintln!("Empty get response for pr_info");
return None;
}
let get_response = response_res.expect("Error in getting response");
println!("get API status: {}", get_response.status());
let response_json_res = get_response.json::<Value>().await;
if response_json_res.is_err() {
let e = response_json_res.expect_err("No error in response_json_res");
eprintln!("Unable to deserialize response_json: {:?}", e);
return None;
}
let response_json = response_json_res.expect("Uncaught error in response_json_res");
let reviewers_opt = response_json.get("reviewers");
if reviewers_opt.is_none() {
eprintln!("No reviewers found in response: {:?}", &response_json);
return None;
}
let reviewers_value = reviewers_opt.expect("Empty reviewers_opt").to_owned();
let reviewers_res = serde_json::from_value(reviewers_value);
if reviewers_res.is_err() {
let e = reviewers_res.expect_err("No error in reviewers_res");
eprintln!("Failed to serialize reviewers: {:?}", e);
return None;
}
let reviewers: Vec<BitbucketUser> = reviewers_res.expect("Uncaught error in response_res");
return Some((reviewers, response_json));
}

async fn put_reviewers(url: &str, access_token: &str,put_body_opt: &Option<Value>) {
if put_body_opt.is_none() {
eprintln!("Empty put request body, not adding reviewers");
return;
}
let put_body = put_body_opt.to_owned().expect("Empty put_body_opt");
// Make the PUT API call
let client = get_client();
let response_res = client
.put(url)
.bearer_auth(&access_token)
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.json(&put_body)
.send().await;

// Handle the response_res as necessary
println!("response_res = {:?}", &response_res);
// for debugging
match response_res {
Ok(v) => println!("response v = {:?}", v.text().await),
Err(e) => println!("response err = {:?}", e)
};
}

async fn get_pr_info(url: &str, access_token: &str) -> Option<Response> {
let client = get_client();
let headers_opt = prepare_headers(&access_token);
if headers_opt.is_none() {
eprintln!("Unable to prepare_headers, empty headers_opt");
return None;
}
let headers = headers_opt.expect("Empty headers_opt");
let get_res = client.get(url).headers(headers).send().await;
if get_res.is_err() {
let e = get_res.expect_err("No error in response_res");
eprintln!("Error in get request for adding reviewer - {:?}", e);
return None;
}
let get_response = get_res.expect("Uncaught error in get_res");
return Some(get_response);
}

fn prepare_get_prinfo_url(repo_owner: &str,
repo_name: &str, review_id: &str) -> String {
let url = format!(
"{}/repositories/{}/{}/pullrequests/{}",
"https://api.bitbucket.org/2.0".to_string(),
repo_owner,
repo_name,
review_id
);
println!("add reviews url = {}", &url);
return url;
}
Loading

0 comments on commit 5883a31

Please sign in to comment.