From 110ed2f1ed5a6e599ba5af0ffbceabab718d0950 Mon Sep 17 00:00:00 2001 From: simon busch Date: Sun, 2 Apr 2023 08:46:31 +0200 Subject: [PATCH 1/5] [ADD] api call to get the PR to review, update module, render home and WIP on main --- src/api/fetch_github_data.rs | 2 +- src/api/fetch_github_pr_review.rs | 37 +++++++++++++++++++ src/api/mod.rs | 14 +++++-- src/main.rs | 61 ++++++++++++++++++++++++------- src/render_items/render_home.rs | 7 +++- 5 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 src/api/fetch_github_pr_review.rs diff --git a/src/api/fetch_github_data.rs b/src/api/fetch_github_data.rs index 800bbc7..ebd6f18 100644 --- a/src/api/fetch_github_data.rs +++ b/src/api/fetch_github_data.rs @@ -3,7 +3,7 @@ use reqwest::header; use crate::structs; use structs::{ApiResponse, IssueComments}; -use std::{error::Error}; +use std::error::Error; pub async fn get_github_response(username: &str, access_token: &str, status: &str) -> Result> { diff --git a/src/api/fetch_github_pr_review.rs b/src/api/fetch_github_pr_review.rs new file mode 100644 index 0000000..1671bca --- /dev/null +++ b/src/api/fetch_github_pr_review.rs @@ -0,0 +1,37 @@ +use reqwest::header::{HeaderValue, ACCEPT}; +use reqwest::header; + +use crate::structs; +use structs::ApiResponse; +use std::error::Error; + +pub async fn fetch_github_pr_review(username: &str, access_token: &str) -> Result> { + let mut headers = header::HeaderMap::new(); + headers.insert( + ACCEPT, + HeaderValue::from_static("application/vnd.github.v3+json"), + ); + headers.insert( + "Authorization", + HeaderValue::from_str(&format!("Bearer {}", access_token)).unwrap(), + ); + headers.insert("User-Agent", HeaderValue::from_static("my app")); + let client = reqwest::Client::builder() + .default_headers(headers) + .build()?; + let base_url = "https://api.github.com"; + let url = format!( + "{}/search/issues?q=type:pr+review-requested:{}+state:open + ", + base_url, username + ); + let github_response = client + .get(url) + .send() + .await? + .text() + .await?; + + let items: ApiResponse = serde_json::from_str(&github_response)?; + Ok(items) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 2c70145..ccd139c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2,8 +2,10 @@ use crate::structs; use structs::{ApiResponseItem}; use std::{error::Error}; mod fetch_github_data; +mod fetch_github_pr_review; mod update_issue_status; use fetch_github_data::get_github_response; +use fetch_github_pr_review::fetch_github_pr_review; use chrono::{DateTime, NaiveDateTime, Utc}; fn parse_date_string(date_string: &str) -> DateTime { @@ -11,7 +13,7 @@ fn parse_date_string(date_string: &str) -> DateTime { DateTime::from_utc(naive_date, Utc) } -pub async fn init_gh_data(username: &str, access_token: &str) -> Result<(Vec, Vec, i32, i32), Box> { +pub async fn init_gh_data(username: &str, access_token: &str) -> Result<(Vec, Vec, Vec, i32, i32, i32), Box> { // Get list of open issues let issues_list_response_open = get_github_response(username, access_token, "open").await?; let mut issues_list_open = issues_list_response_open.items.to_owned(); @@ -22,12 +24,18 @@ pub async fn init_gh_data(username: &str, access_token: &str) -> Result<(Vec for usize { @@ -49,6 +50,7 @@ impl From for usize { MenuItem::Assignments => 1, MenuItem::Closed => 2, MenuItem::Refresh => 3, + MenuItem::ToReview => 4, } } } @@ -94,9 +96,9 @@ async fn main() -> Result<(), Box> { // Render the loading screen render_waiting_screen(&mut terminal)?; - let (mut issues_list_open, mut issues_list_closed, mut issues_list_open_len, mut issues_list_closed_len) = init_gh_data(&username, &access_token).await?; + let (mut issues_list_open, mut issues_list_closed, mut assigned_pr_list, mut issues_list_open_len, mut issues_list_closed_len, mut assigned_pr_list_len) = init_gh_data(&username, &access_token).await?; - let menu_titles = vec!["Home","Assignments", "Closed", "Refresh" , "Quit"]; + let menu_titles = vec!["Home","Assignments", "Closed", "Refresh", "To Review", "Quit"]; let mut active_menu_item = MenuItem::Home; let mut issue_list_state_open = ListState::default(); @@ -105,6 +107,9 @@ async fn main() -> Result<(), Box> { let mut issue_list_state_closed = ListState::default(); issue_list_state_closed.select(Some(0)); + let mut issue_list_state_to_review = ListState::default(); + issue_list_state_to_review.select(Some(0)); + let mut action_list_state = ListState::default(); action_list_state.select(Some(0)); @@ -167,7 +172,7 @@ async fn main() -> Result<(), Box> { rect.render_widget(tabs, chunks[0]); match active_menu_item { - MenuItem::Home => rect.render_widget(render_home(&issues_list_open_len, &issues_list_closed_len, &username), chunks[1]), + MenuItem::Home => rect.render_widget(render_home(&issues_list_open_len, &issues_list_closed_len, &assigned_pr_list_len, &username), chunks[1]), MenuItem::Assignments => { let data_chunck = Layout::default() .direction(Direction::Horizontal) @@ -209,17 +214,41 @@ async fn main() -> Result<(), Box> { } }, MenuItem::Refresh => { - let data_chunck = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(), - ) - .split(chunks[1]); - let selected_issue_index = issue_list_state_open.selected(); - let (left, right) = render_issues(&issues_list_open, selected_issue_index, show_comment); - rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_open); - rect.render_widget(right, data_chunck[1]); + // let data_chunck = Layout::default() + // .direction(Direction::Horizontal) + // .constraints( + // [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(), + // ) + // .split(chunks[1]); + // let selected_issue_index = issue_list_state_open.selected(); + // let (left, right) = render_issues(&issues_list_open, selected_issue_index, show_comment); + // rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_open); + // rect.render_widget(right, data_chunck[1]); }, + + MenuItem::ToReview => { + let data_chunck = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(), + ) + .split(chunks[1]); + let selected_issue_index = issue_list_state_to_review.selected(); + let (left, right) = render_issues(&assigned_pr_list, selected_issue_index, show_comment); + rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_to_review); + rect.render_widget(right, data_chunck[1]); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + println!("triggered"); + } } rect.render_widget(copyright, chunks[2]); })?; @@ -319,7 +348,11 @@ async fn main() -> Result<(), Box> { }, KeyCode::Char('r') => { - (issues_list_open, issues_list_closed, issues_list_open_len, issues_list_closed_len) = init_gh_data(&username, &access_token).await.unwrap(); + (issues_list_open, issues_list_closed, assigned_pr_list, issues_list_open_len, issues_list_closed_len, assigned_pr_list_len) = init_gh_data(&username, &access_token).await.unwrap(); + } + + KeyCode::Char('t') => { + MenuItem::ToReview; } _ => {} diff --git a/src/render_items/render_home.rs b/src/render_items/render_home.rs index 0db0770..cca02fa 100644 --- a/src/render_items/render_home.rs +++ b/src/render_items/render_home.rs @@ -5,7 +5,7 @@ use tui::{ widgets::{Block, BorderType, Borders, Paragraph} }; -pub fn render_home<'a>(opened: &i32, closed: &i32, username: &String) -> Paragraph<'a> { +pub fn render_home<'a>(opened: &i32, closed: &i32, review: &i32, username: &String) -> Paragraph<'a> { let home = Paragraph::new(vec![ Spans::from(vec![Span::raw("")]), Spans::from(vec![Span::raw("")]), @@ -28,6 +28,11 @@ pub fn render_home<'a>(opened: &i32, closed: &i32, username: &String) -> Paragra closed, ))]), Spans::from(vec![Span::raw("")]), + Spans::from(vec![Span::raw(format!( + "{} To review", + review, + ))]), + Spans::from(vec![Span::raw("")]), Spans::from(vec![Span::raw("")]), Spans::from(vec![Span::raw("")]), Spans::from(vec![Span::raw("")]), From d116a4d2b13d5d93112e8efe68823461333cb84b Mon Sep 17 00:00:00 2001 From: simon busch Date: Sun, 2 Apr 2023 08:57:24 +0200 Subject: [PATCH 2/5] fix PR to review list and rendering --- src/api/fetch_github_pr_review.rs | 58 +++++++++++++++++-------------- src/api/mod.rs | 2 +- src/main.rs | 23 ++++-------- 3 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/api/fetch_github_pr_review.rs b/src/api/fetch_github_pr_review.rs index 1671bca..eeddc8e 100644 --- a/src/api/fetch_github_pr_review.rs +++ b/src/api/fetch_github_pr_review.rs @@ -6,32 +6,38 @@ use structs::ApiResponse; use std::error::Error; pub async fn fetch_github_pr_review(username: &str, access_token: &str) -> Result> { - let mut headers = header::HeaderMap::new(); - headers.insert( - ACCEPT, - HeaderValue::from_static("application/vnd.github.v3+json"), - ); - headers.insert( - "Authorization", - HeaderValue::from_str(&format!("Bearer {}", access_token)).unwrap(), - ); - headers.insert("User-Agent", HeaderValue::from_static("my app")); - let client = reqwest::Client::builder() - .default_headers(headers) - .build()?; - let base_url = "https://api.github.com"; - let url = format!( - "{}/search/issues?q=type:pr+review-requested:{}+state:open - ", - base_url, username - ); - let github_response = client - .get(url) - .send() - .await? - .text() - .await?; + let mut headers = header::HeaderMap::new(); + headers.insert( + ACCEPT, + HeaderValue::from_static("application/vnd.github.v3+json"), + ); + headers.insert( + "Authorization", + HeaderValue::from_str(&format!("Bearer {}", access_token)).unwrap(), + ); + headers.insert("User-Agent", HeaderValue::from_static("my app")); + let client = reqwest::Client::builder() + .default_headers(headers) + .build()?; + let base_url = "https://api.github.com"; + let url = format!( + "{}/search/issues?q=type:pr+review-requested:{}+state:open + ", + base_url, username + ); + let github_response = client + .get(url) + .send() + .await? + .text() + .await?; - let items: ApiResponse = serde_json::from_str(&github_response)?; + let mut items: ApiResponse = serde_json::from_str(&github_response)?; + for item in items.items.iter_mut() { + let url_parts: Vec<&str> = item.url.split("/").collect(); + item.repository = Some(url_parts[url_parts.len() - 3].to_string()); + item.organization = Some(url_parts[url_parts.len() - 4].to_string()); + item.is_pr = url_parts.contains(&"pull"); + } Ok(items) } diff --git a/src/api/mod.rs b/src/api/mod.rs index ccd139c..a63f693 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -26,7 +26,7 @@ pub async fn init_gh_data(username: &str, access_token: &str) -> Result<(Vec Result<(), Box> { // rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_open); // rect.render_widget(right, data_chunck[1]); }, - MenuItem::ToReview => { let data_chunck = Layout::default() .direction(Direction::Horizontal) @@ -233,21 +232,11 @@ async fn main() -> Result<(), Box> { [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(), ) .split(chunks[1]); - let selected_issue_index = issue_list_state_to_review.selected(); - let (left, right) = render_issues(&assigned_pr_list, selected_issue_index, show_comment); - rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_to_review); - rect.render_widget(right, data_chunck[1]); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); - println!("triggered"); + let selected_issue_index = issue_list_state_to_review.selected(); + // println!("assigned_pr_list: {:?}", assigned_pr_list ); + let (left, right) = render_issues(&assigned_pr_list, selected_issue_index, show_comment); + rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_to_review); + rect.render_widget(right, data_chunck[1]); } } rect.render_widget(copyright, chunks[2]); @@ -352,7 +341,7 @@ async fn main() -> Result<(), Box> { } KeyCode::Char('t') => { - MenuItem::ToReview; + active_menu_item = MenuItem::ToReview; } _ => {} From 624b082d9a180258c81b21a389c1ed5f465a4ff7 Mon Sep 17 00:00:00 2001 From: simon busch Date: Sat, 22 Apr 2023 07:47:19 +0200 Subject: [PATCH 3/5] handle state for PR to review --- src/main.rs | 82 ++++++++++++++++++++++++++++++---------------------- src/utils.rs | 7 ++++- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6cb3b5b..47af900 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use dotenv::dotenv; use tokio; use std::{error::Error, sync::mpsc}; use tui::{ - backend::{CrosstermBackend}, + backend::CrosstermBackend, layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Modifier, Style}, text::{Span, Spans}, @@ -115,6 +115,7 @@ async fn main() -> Result<(), Box> { let mut active_open = true; let mut show_comment = false; + let mut to_review_open = false; // Create a flag to keep track of whether the prompt window is open let mut prompt_open = false; @@ -252,47 +253,58 @@ async fn main() -> Result<(), Box> { KeyCode::Char('h') => active_menu_item = MenuItem::Home, KeyCode::Char('a') => { active_open = true; - active_menu_item = MenuItem::Assignments + to_review_open = false; + active_menu_item = MenuItem::Assignments; }, KeyCode::Char('c') => { active_open = false; + to_review_open = false; active_menu_item = MenuItem::Closed }, KeyCode::Down => { let (state, items) = get_current_state_and_list( active_open, + to_review_open, &mut issue_list_state_open, &mut issue_list_state_closed, + &mut issue_list_state_to_review, &issues_list_open, &issues_list_closed, + &assigned_pr_list ); move_selection(state, items, 1); } - KeyCode::Up => { - let (state, _) = get_current_state_and_list( - active_open, - &mut issue_list_state_open, - &mut issue_list_state_closed, - &issues_list_open, - &issues_list_closed, - ); - move_selection(state, &issues_list_open, -1); - } - KeyCode::Enter => { - let (state, list) = get_current_state_and_list( - active_open, - &mut issue_list_state_open, - &mut issue_list_state_closed, - &issues_list_open, - &issues_list_closed, - ); - if let Some(selected) = state.selected() { - let url = &list[selected].url; - if let Err(e) = open::that(url) { - eprintln!("Failed to open URL '{}': {}", url, e); - } - } - } + KeyCode::Up => { + let (state, _) = get_current_state_and_list( + active_open, + to_review_open, + &mut issue_list_state_open, + &mut issue_list_state_closed, + &mut issue_list_state_to_review, + &issues_list_open, + &issues_list_closed, + &assigned_pr_list + ); + move_selection(state, &issues_list_open, -1); + } + KeyCode::Enter => { + let (state, list) = get_current_state_and_list( + active_open, + to_review_open, + &mut issue_list_state_open, + &mut issue_list_state_closed, + &mut issue_list_state_to_review, + &issues_list_open, + &issues_list_closed, + &assigned_pr_list + ); + if let Some(selected) = state.selected() { + let url = &list[selected].url; + if let Err(e) = open::that(url) { + eprintln!("Failed to open URL '{}': {}", url, e); + } + } + } KeyCode::Right => { if active_open == true { show_comment = true; @@ -335,18 +347,18 @@ async fn main() -> Result<(), Box> { prompt_open = !prompt_open; } }, - KeyCode::Char('r') => { (issues_list_open, issues_list_closed, assigned_pr_list, issues_list_open_len, issues_list_closed_len, assigned_pr_list_len) = init_gh_data(&username, &access_token).await.unwrap(); - } - + }, KeyCode::Char('t') => { - active_menu_item = MenuItem::ToReview; - } - + if to_review_open == false { + to_review_open = true; + active_menu_item = MenuItem::ToReview; + } + }, _ => {} - }, - Event::Tick => {} + }, + Event::Tick => {} } } Ok(()) diff --git a/src/utils.rs b/src/utils.rs index d145f8c..d5de50b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,12 +14,17 @@ pub fn centered_rect(width: u16, height: u16, parent: Rect) -> Rect { pub fn get_current_state_and_list<'a>( active_open: bool, + is_pr_review: bool, issue_list_state_open: &'a mut ListState, issue_list_state_closed: &'a mut ListState, + issue_list_state_to_review: &'a mut ListState, issues_list_open: &'a Vec, issues_list_closed: &'a Vec, + assigned_pr_list: &'a Vec, ) -> (&'a mut ListState, &'a Vec) { - if active_open { + if is_pr_review { + (issue_list_state_to_review, assigned_pr_list) + } else if active_open { (issue_list_state_open, issues_list_open) } else { (issue_list_state_closed, issues_list_closed) From 087e96fb871f21688256d9a7a5837752866a53bf Mon Sep 17 00:00:00 2001 From: simon busch Date: Sat, 22 Apr 2023 08:52:28 +0200 Subject: [PATCH 4/5] Update indentation --- src/main.rs | 60 ++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/main.rs b/src/main.rs index 47af900..9a5d1d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -188,10 +188,10 @@ async fn main() -> Result<(), Box> { rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_open); rect.render_widget(right, data_chunck[1]); if prompt_open == true { - let items = vec![ - ListItem::new(" 1 - Close issue"), - ]; - render_popup(rect, items); + let items = vec![ + ListItem::new(" 1 - Close issue"), + ]; + render_popup(rect, items); } } else if active_open == true && show_comment == true { let selected_issue_index = issue_list_state_open.selected(); @@ -201,18 +201,18 @@ async fn main() -> Result<(), Box> { } }, MenuItem::Closed => { - let data_chunck = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(), - ) - .split(chunks[1]); - if active_open == false { - let selected_issue_index = issue_list_state_closed.selected(); - let (left, right) = render_issues(&issues_list_closed, selected_issue_index, show_comment); - rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_closed); - rect.render_widget(right, data_chunck[1]); - } + let data_chunck = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(), + ) + .split(chunks[1]); + if active_open == false { + let selected_issue_index = issue_list_state_closed.selected(); + let (left, right) = render_issues(&issues_list_closed, selected_issue_index, show_comment); + rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_closed); + rect.render_widget(right, data_chunck[1]); + } }, MenuItem::Refresh => { // let data_chunck = Layout::default() @@ -273,7 +273,7 @@ async fn main() -> Result<(), Box> { &assigned_pr_list ); move_selection(state, items, 1); - } + } KeyCode::Up => { let (state, _) = get_current_state_and_list( active_open, @@ -319,13 +319,13 @@ async fn main() -> Result<(), Box> { // close issue let state; let list: &Vec; - if active_open == true { - state = &mut issue_list_state_open; - list = &issues_list_open; - } else { - state = &mut issue_list_state_closed; - list = &issues_list_closed; - } + if active_open == true { + state = &mut issue_list_state_open; + list = &issues_list_open; + } else { + state = &mut issue_list_state_closed; + list = &issues_list_closed; + } if let Some(selected) = state.selected() { let number = list[selected].number; let repo_owner = list[selected].organization.as_ref().unwrap().to_owned(); @@ -348,18 +348,18 @@ async fn main() -> Result<(), Box> { } }, KeyCode::Char('r') => { - (issues_list_open, issues_list_closed, assigned_pr_list, issues_list_open_len, issues_list_closed_len, assigned_pr_list_len) = init_gh_data(&username, &access_token).await.unwrap(); + (issues_list_open, issues_list_closed, assigned_pr_list, issues_list_open_len, issues_list_closed_len, assigned_pr_list_len) = init_gh_data(&username, &access_token).await.unwrap(); }, KeyCode::Char('t') => { - if to_review_open == false { - to_review_open = true; - active_menu_item = MenuItem::ToReview; - } + if to_review_open == false { + to_review_open = true; + active_menu_item = MenuItem::ToReview; + } }, _ => {} }, Event::Tick => {} - } + } } Ok(()) } From d70a16721b1b316388722de93d4dc0317e11f0b9 Mon Sep 17 00:00:00 2001 From: simon busch Date: Sat, 22 Apr 2023 08:53:49 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Clean=20up=20code=20=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9a5d1d5..5acb357 100644 --- a/src/main.rs +++ b/src/main.rs @@ -215,16 +215,6 @@ async fn main() -> Result<(), Box> { } }, MenuItem::Refresh => { - // let data_chunck = Layout::default() - // .direction(Direction::Horizontal) - // .constraints( - // [Constraint::Percentage(30), Constraint::Percentage(70)].as_ref(), - // ) - // .split(chunks[1]); - // let selected_issue_index = issue_list_state_open.selected(); - // let (left, right) = render_issues(&issues_list_open, selected_issue_index, show_comment); - // rect.render_stateful_widget(left, data_chunck[0], &mut issue_list_state_open); - // rect.render_widget(right, data_chunck[1]); }, MenuItem::ToReview => { let data_chunck = Layout::default()