Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion devprofiler/src/bitbucket/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub async fn refresh_git_auth(clone_url: &str, directory: &str) -> String{
return access_token;
}

async fn update_access_token(auth_info: &AuthInfo) -> Option<AuthInfo> {
pub async fn update_access_token(auth_info: &AuthInfo) -> Option<AuthInfo> {

let now = SystemTime::now();
let now_secs = now.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs();
Expand Down
84 changes: 84 additions & 0 deletions devprofiler/src/bitbucket/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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 super::{config::bitbucket_base_url, auth::update_access_token};

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

#[derive(Serialize)]
struct Content {
raw: String,
}
pub async fn add_comment(workspace_name: &str, repo_name: &str, review_id: &str, comment_text: &str) {
let url = format!("{}/repositories/{workspace_name}/{repo_name}/pullrequests/{review_id}/comments", bitbucket_base_url());
println!("comment url = {}", &url);
let auth_info = auth_info();
let mut access_token = auth_info.access_token().clone();
let new_auth_opt = update_access_token(&auth_info).await;
if new_auth_opt.is_some() {
let new_auth = new_auth_opt.expect("new_auth_opt is empty");
access_token = new_auth.access_token().clone();
}
let comment_payload = Comment {
content: Content {
raw: comment_text.to_string(),
},
};
let client = reqwest::Client::new();
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 response_res = client.post(&url).
headers(headers).json(&comment_payload).send().await;
if response_res.is_err() {
eprintln!("Error in post request for adding comment - {:?}", response_res.as_ref().expect_err("response has no error"));
}
let response = response_res.expect("Error in getting response");
if response.status().is_success() {
eprintln!("Failed to call API {}, status: {}", url, response.status());
}
let response_json = response.json::<Value>().await;
println!("response from comment api = {:?}", &response_json);
}

pub async fn add_reviewers(repo_owner: &str, repo_name: &str, user_id: &str) {
let url = format!("{}/repositories/{repo_owner}/{repo_name}/default-reviewers/{user_id}", bitbucket_base_url());
println!("auto assign url = {}", &url);
let auth_info = auth_info();
let mut access_token = auth_info.access_token().clone();
let new_auth_opt = update_access_token(&auth_info).await;
if new_auth_opt.is_some() {
let new_auth = new_auth_opt.expect("new_auth_opt is empty");
access_token = new_auth.access_token().clone();
}
let client = reqwest::Client::new();
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 response_res = client
.put(&url)
.bearer_auth(access_token) // Use Bearer authentication with your personal access token
.header("Accept", "application/json")
.send().await;
if response_res.is_err() {
eprintln!("Error in post request for auto assign - {:?}", response_res.as_ref().expect_err("response has no error"));
}
let response = response_res.expect("Error in getting response");
if response.status().is_success() {
eprintln!("Failed to call API {}, status: {}", url, response.status());
}
let response_json = response.json::<Value>().await;
println!("response from auto assign api = {:?}", &response_json);
}
3 changes: 2 additions & 1 deletion devprofiler/src/bitbucket/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub fn bitbucket_base_url() -> String {

pub async fn get_api(url: &str, access_token: &str, params: Option<HashMap<&str, &str>> ) -> Vec<Value> {
let response_opt = call_get_api(url, access_token, &params).await;
println!("response of get_api = {:?}", &response_opt);
let (mut response_values, next_url) = deserialize_response(response_opt).await;
if next_url.is_some() {
let mut page_values = get_all_pages(next_url, access_token, &params).await;
Expand All @@ -17,7 +18,7 @@ pub async fn get_api(url: &str, access_token: &str, params: Option<HashMap<&str,
return response_values;
}

async fn call_get_api(url: &str, access_token: &str, params: &Option<HashMap<&str, &str>> ) -> Option<Response>{
pub async fn call_get_api(url: &str, access_token: &str, params: &Option<HashMap<&str, &str>> ) -> Option<Response>{
println!("GET api url = {}", url);
let client = reqwest::Client::new();
let mut headers = reqwest::header::HeaderMap::new();
Expand Down
3 changes: 2 additions & 1 deletion devprofiler/src/bitbucket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub mod workspace;
pub mod repo;
mod config;
pub mod webhook;
pub mod user;
pub mod user;
pub mod comment;
36 changes: 31 additions & 5 deletions devprofiler/src/bitbucket/user.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use chrono::{DateTime, Utc, FixedOffset};
use crate::db::auth::auth_info;
use crate::db::user::save_user_to_db;
use crate::db::user::{save_user_to_db, user_from_db};
use crate::utils::auth::AuthInfo;
use crate::utils::lineitem::LineItem;
use crate::utils::user::{User, Provider, ProviderEnum};
use super::config::{bitbucket_base_url, get_api};
use super::config::{bitbucket_base_url, get_api, call_get_api};

pub async fn get_and_save_workspace_users(workspace_id: &str, access_token: &str) {
let base_url = bitbucket_base_url();
Expand All @@ -21,12 +23,36 @@ pub async fn get_and_save_workspace_users(workspace_id: &str, access_token: &str
}
}

pub async fn get_commit_bb(commit: &str, repo_name: &str, repo_owner: &str) {
pub async fn get_commit_bb(commit: &str, repo_name: &str, repo_owner: &str) -> LineItem{
let base_url = bitbucket_base_url();
let commits_url = format!("{}/repositories/{repo_owner}/{repo_name}/commit/{commit}", &base_url);
println!("commits url = {}", &commits_url);
let authinfo: AuthInfo = auth_info();
let access_token = authinfo.access_token();
let response_json = get_api(&commits_url, access_token, None).await;
println!("response json for commits url = {:?}", &response_json);
let response = call_get_api(&commits_url, access_token, &None).await;
let response_json = response.expect("No response").json::<serde_json::Value>().await.expect("Error in deserializing json");
let timestamp_str = &response_json["date"].to_string().replace('"', "");
println!("timestamp_str = {}", timestamp_str);
// Explicitly specify the format
let datetime: DateTime<FixedOffset> = DateTime::parse_from_rfc3339(&timestamp_str)
.expect("Failed to parse timestamp");

// Convert to Utc
let datetime_utc = datetime.with_timezone(&Utc);

let unix_timestamp = datetime_utc.timestamp();
let unix_timestamp_str = unix_timestamp.to_string();
let author_id = response_json["author"]["user"]["uuid"].to_string().replace('"', "");
let author_name = response_json["author"]["user"]["display_name"].to_string().replace('"', "");
let user_opt = user_from_db(
&ProviderEnum::Bitbucket.to_string(),
repo_owner, &author_id);
if user_opt.is_none() {
let user = User::new(
Provider::new(author_id.clone(),
ProviderEnum::Bitbucket),
author_name, repo_owner.to_string(), None);
save_user_to_db(&user);
}
return LineItem::new(author_id, unix_timestamp_str);
}
85 changes: 80 additions & 5 deletions devprofiler/src/core/coverage.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,82 @@
use crate::utils::hunk::HunkMap;
use std::collections::HashMap;

use crate::{utils::hunk::{HunkMap, PrHunkItem}, db::user::user_from_db, bitbucket::comment::{add_comment, add_reviewers}};

pub async fn process_coverage(hunkmap: &HunkMap) {
// get current reviewers
// calculate coverage

}
for prhunk in hunkmap.prhunkvec() {
// calculate number of hunks for each userid
let coverage_map = calculate_coverage(&hunkmap.repo_owner(), prhunk);
if !coverage_map.is_empty() {
// get user for each user id
// create comment text
let comment = comment_text(coverage_map);
// add comment
add_comment(&hunkmap.repo_owner(),
&hunkmap.repo_name(),
prhunk.pr_number(),
&comment).await;
// add reviewers
for blame in prhunk.blamevec() {
let author_id = blame.author();
add_reviewers(&hunkmap.repo_owner(),
&hunkmap.repo_name(),
&author_id).await;
}
// TODO - implement settings
}
}
}

fn calculate_coverage(repo_owner: &str, prhunk: &PrHunkItem) -> HashMap<String, String>{
let mut coverage_map = HashMap::<String, String>::new();
let mut coverage_floatmap = HashMap::<String, f32>::new();
let mut total = 0.0;
for blame in prhunk.blamevec() {
let author_id = blame.author().to_owned();
let num_lines: f32 = blame.line_end().parse::<f32>().expect("lines_end invalid float")
- blame.line_start().parse::<f32>().expect("lines_end invalid float")
+ 1.0;
total += num_lines;
if coverage_floatmap.contains_key(&author_id) {
let coverage = coverage_floatmap.get(&author_id).expect("unable to find coverage for author")
+ num_lines;
coverage_floatmap.insert(author_id, coverage);
}
else {
coverage_floatmap.insert(author_id, num_lines);
}
}
if total <= 0.0 {
return coverage_map;
}
for (key, value) in coverage_floatmap.iter_mut() {
*value = *value / total * 100.0;
let formatted_value = format!("{:.2}", *value);
let user = user_from_db("bitbucket", repo_owner, key);
if user.is_none() {
eprintln!("No user name found for {}", key);
coverage_map.insert(key.to_string(), formatted_value);
continue;
}
let user_val = user.expect("user is empty");
let coverage_key = user_val.name();
coverage_map.insert(coverage_key.to_string(), formatted_value);
}
return coverage_map;
}

fn comment_text(coverage_map: HashMap<String, String>) -> String {
let mut comment = "Relevant users for this PR:\n\n".to_string(); // Added two newlines
comment += "| Contributor Name/Alias | Code Coverage |\n"; // Added a newline at the end
comment += "| -------------- | --------------- |\n"; // Added a newline at the end

for (key, value) in coverage_map.iter() {
comment += &format!("| {} | {}% |\n", key, value); // Added a newline at the end
}

comment += "\n\n";
comment += "Code coverage is calculated based on the git blame information of the PR. To know more, hit us up at contact@vibinex.com.\n\n"; // Added two newlines
comment += "To change comment and auto-assign settings, go to [your Vibinex settings page.](https://vibinex.com/settings)\n"; // Added a newline at the end

return comment;
}
14 changes: 14 additions & 0 deletions devprofiler/src/db/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,17 @@ pub fn save_user_to_db(user: &User) {
// Insert JSON into sled DB
db.insert(IVec::from(user_key.as_bytes()), json).expect("Failed to upsert user into sled DB");
}

pub fn user_from_db(repo_provider: &str, workspace: &str, user_id: &str, ) -> Option<User> {
let db = get_db();
let user_key = format!("{}/{}/{}",
repo_provider, workspace, user_id);
let user_opt = db.get(IVec::from(user_key.as_bytes())).expect("Unable to get repo from db");
if user_opt.is_none() {
return None;
}
let user_ivec = user_opt.expect("Empty value");
let user: User = serde_json::from_slice::<User>(&user_ivec).unwrap();
println!("user from db = {:?}", &user);
return Some(user);
}
50 changes: 6 additions & 44 deletions devprofiler/src/utils/gitops.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::error::Error;
use std::process::Command;
use std::str;
use serde::Deserialize;
Expand All @@ -11,6 +10,7 @@ use crate::bitbucket::user::get_commit_bb;

use super::hunk::BlameItem;
use super::review::Review;
use super::lineitem::LineItem;

#[derive(Debug, Serialize, Default, Deserialize)]
pub struct StatItem {
Expand Down Expand Up @@ -268,13 +268,13 @@ pub async fn generate_blame(review: &Review, linemap: &HashMap<String, Vec<Strin
for lidx in linenumint..(linenumint + blamelines.len()-1) {
if lineauthormap.contains_key(&lidx) && lineauthormap.contains_key(&(lidx+1)) {
let lineitem = lineauthormap.get(&lidx).expect("lidx checked");
if lineitem.author ==
lineauthormap.get(&(lidx+1)).expect("lidx+1 checked").author {
if lineitem.author_id() ==
lineauthormap.get(&(lidx+1)).expect("lidx+1 checked").author_id() {
continue;
}
else {
blamevec.push(BlameItem::new(
lineitem.author().to_string(),
lineitem.author_id().to_string(),
lineitem.timestamp().to_string(),
linebreak.to_string(),
lidx.to_string(),
Expand All @@ -287,7 +287,7 @@ pub async fn generate_blame(review: &Review, linemap: &HashMap<String, Vec<Strin
if lineauthormap.contains_key(&lastidx) {
let lineitem = lineauthormap.get(&lastidx).expect("lastidx checked");
blamevec.push(BlameItem::new(
lineitem.author().to_string(),
lineitem.author_id().to_string(),
lineitem.timestamp().to_string(),
linebreak.to_string(),
lastidx.to_string(),
Expand All @@ -308,21 +308,6 @@ pub async fn generate_blame(review: &Review, linemap: &HashMap<String, Vec<Strin
return blamevec;
}

struct LineItem {
author: String,
timestamp: String,
}

impl LineItem {
fn author(&self) -> &String {
&self.author
}

fn timestamp(&self) -> &String {
&self.timestamp
}
}

async fn process_blamelines(blamelines: &Vec<&str>, linenum: usize,
repo_name: &str, repo_owner: &str) -> HashMap<usize, LineItem> {
let mut linemap = HashMap::<usize, LineItem>::new();
Expand All @@ -331,32 +316,9 @@ async fn process_blamelines(blamelines: &Vec<&str>, linenum: usize,
let wordvec: Vec<&str> = ln.split(" ").collect();
let commit = wordvec[0];
let lineitem = get_commit_bb(commit, repo_name, repo_owner).await;
let mut author = wordvec[1];
let mut timestamp = wordvec[2];
let mut idx = 1;
if author == "" {
while idx < wordvec.len() && wordvec[idx] == "" {
idx = idx + 1;
}
if idx < wordvec.len() {
author = wordvec[idx];
}
}
let authorstr = author.replace("(", "")
.replace("<", "")
.replace(">", "");
if timestamp == "" {
idx = idx + 1;
while idx < wordvec.len() && wordvec[idx] == "" {
idx = idx + 1;
}
if idx < wordvec.len() {
timestamp = wordvec[idx];
}
}
linemap.insert(
linenum + lnum,
LineItem { author: authorstr.to_string(), timestamp: timestamp.to_string() }
lineitem
);
}
return linemap;
Expand Down
Loading