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..eeddc8e --- /dev/null +++ b/src/api/fetch_github_pr_review.rs @@ -0,0 +1,43 @@ +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 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 2c70145..a63f693 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,11 +107,15 @@ 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)); 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; @@ -167,7 +173,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) @@ -182,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(); @@ -195,31 +201,34 @@ 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() - .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(); + // 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]); })?; @@ -234,47 +243,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; @@ -289,13 +309,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(); @@ -317,15 +337,19 @@ async fn main() -> Result<(), Box> { prompt_open = !prompt_open; } }, - 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') => { + if to_review_open == false { + to_review_open = true; + active_menu_item = MenuItem::ToReview; + } + }, _ => {} - }, - Event::Tick => {} - } + }, + Event::Tick => {} + } } Ok(()) } 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("")]), 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)