Skip to content
Merged
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
267 changes: 251 additions & 16 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dotenv = "0.15.0"
email_address = { git = "https://github.com/illicitonion/rust-email_address.git", rev = "12cd9762a166b79a227beaa90b2f60a768d7c55c" }
futures = "0.3.31"
google-drive = "0.7.0"
google-sheets4 = "6.0.0"
gsuite-api = "0.7.0"
http = "1.3.1"
http-serde = "2.1.1"
Expand Down
4 changes: 2 additions & 2 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use axum::{
};
use http::Uri;
use serde::Deserialize;
use sheets::Client;
use tower_sessions::Session;
use uuid::Uuid;

Expand Down Expand Up @@ -112,7 +111,8 @@ pub async fn handle_google_oauth_callback(
server_state.config.public_base_url
);

let mut client = Client::new(
// TODO: Replace this with some other way of getting the token (either a request ourselves, or using another library) so we can drop the sheets dep.
let mut client = ::sheets::Client::new(
server_state.config.google_apis_client_id.clone(),
(*server_state.config.google_apis_client_secret).clone(),
redirect_uri,
Expand Down
70 changes: 23 additions & 47 deletions src/github_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use std::collections::BTreeMap;
use anyhow::Context;
use email_address::EmailAddress;
use serde::{Deserialize, Serialize};
use sheets::types::Sheet;

use crate::{
Error,
newtypes::{GithubLogin, Region, new_case_insensitive_email_address},
sheets::{SheetsClient, cell_string},
sheets::{Sheet, SheetsClient, cell_string},
};

// TODO: Replace this with a serde implementation from a Google Sheet.
Expand All @@ -17,32 +16,19 @@ pub(crate) async fn get_trainees(
sheet_id: &str,
) -> Result<BTreeMap<GithubLogin, Trainee>, Error> {
const EXPECTED_SHEET_NAME: &str = "Form responses 1";
let data = client.get(sheet_id, true, &[]).await.map_err(|err| {
let data = client.get(sheet_id).await.map_err(|err| {
err.with_context(|| {
format!(
"Failed to get trainees github accounts sheet with id {}",
sheet_id
)
})
})?;
let sheet = data.body.sheets.into_iter().find(|sheet| {
if let Some(properties) = &sheet.properties {
properties.title == EXPECTED_SHEET_NAME
} else {
false
}
});
let sheet = data.get(EXPECTED_SHEET_NAME);
if let Some(sheet) = sheet {
let data = trainees_from_sheet(&sheet).map_err(|err| {
err.with_context(|| {
format!(
"Failed to read trainees from sheet {}",
sheet
.properties
.map(|properties| properties.title)
.as_deref()
.unwrap_or("<unknown>")
)
format!("Failed to read trainees from sheet {}", EXPECTED_SHEET_NAME,)
})
})?;
Ok(data)
Expand All @@ -65,41 +51,31 @@ pub struct Trainee {

fn trainees_from_sheet(sheet: &Sheet) -> Result<BTreeMap<GithubLogin, Trainee>, Error> {
let mut trainees = BTreeMap::new();
for data in &sheet.data {
if data.start_column != 0 || data.start_row != 0 {
for (row_index, cells) in sheet.rows.iter().enumerate() {
if row_index == 0 {
continue;
}
if cells.len() < 5 {
return Err(Error::Fatal(anyhow::anyhow!(
"Reading data from Google Sheets API - got data chunk that didn't start at row=0,column=0 - got row={},column={}",
data.start_row,
data.start_column
"Reading trainee data from Google Sheets API, row {} didn't have at least 5 columns",
row_index
)));
}
for (row_index, row) in data.row_data.iter().enumerate() {
if row_index == 0 {
continue;
}
let cells = &row.values;
if cells.len() < 5 {
return Err(Error::Fatal(anyhow::anyhow!(
"Reading trainee data from Google Sheets API, row {} didn't have at least 5 columns",
row_index
)));
}

let github_login = GithubLogin::from(cell_string(&cells[3]));
let github_login = GithubLogin::from(cell_string(&cells[3]));

let email = cell_string(&cells[4]);
let email = cell_string(&cells[4]);

trainees.insert(
github_login.clone(),
Trainee {
name: cell_string(&cells[1]),
region: Region(cell_string(&cells[2])),
github_login,
email: new_case_insensitive_email_address(&email)
.with_context(|| format!("Failed to parse trainee email {}", email))?,
},
);
}
trainees.insert(
github_login.clone(),
Trainee {
name: cell_string(&cells[1]),
region: Region(cell_string(&cells[2])),
github_login,
email: new_case_insensitive_email_address(&email)
.with_context(|| format!("Failed to parse trainee email {}", email))?,
},
);
}

Ok(trainees)
Expand Down
117 changes: 50 additions & 67 deletions src/mentoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::collections::{BTreeMap, btree_map::Entry};

use anyhow::Context;
use chrono::{NaiveDate, Utc};
use google_sheets4::api::CellData;
use serde::Serialize;
use sheets::types::GridData;
use tracing::warn;

use crate::{
Expand Down Expand Up @@ -44,55 +44,49 @@ pub async fn get_mentoring_records(
records: BTreeMap::new(),
};

for sheet_data in sheet_data {
if sheet_data.start_column != 0 || sheet_data.start_row != 0 {
return Err(Error::Fatal(anyhow::anyhow!(
"Start column and row were {} and {}, expected 0 and 0",
sheet_data.start_column,
sheet_data.start_row
)));
for (row_number, cells) in sheet_data.into_iter().enumerate() {
if cells.is_empty() {
continue;
}

for (row_number, row) in sheet_data.row_data.into_iter().enumerate() {
let cells = row.values;
if cells.is_empty() {
continue;
if cells.len() < 6 && !cell_string(&cells[0]).is_empty() {
warn!(
"Parsing mentoring data from Google Sheet with ID {}: Not enough columns for row {} - expected at least 6, got {} containing: {}",
mentoring_records_sheet_id,
row_number,
cells.len(),
format!("{:#?}", cells),
);
continue;
}
if row_number == 0 {
let headings = cells.iter().take(6).map(cell_string).collect::<Vec<_>>();
if headings != ["Name", "Region", "Date", "Staff", "Status", "Notes"] {
return Err(Error::Fatal(anyhow::anyhow!(
"Mentoring data sheet contained wrong headings: {}",
headings.join(", ")
)));
}
if cells.len() < 6 && !cell_string(&cells[0]).is_empty() {
warn!(
"Parsing mentoring data from Google Sheet with ID {}: Not enough columns for row {} - expected at least 6, got {} containing: {}",
mentoring_records_sheet_id,
row_number,
cells.len(),
format!("{:#?}", cells),
);
continue;
} else {
if cells[0].effective_value.is_none() {
break;
}
if row_number == 0 {
let headings = cells.iter().take(6).map(cell_string).collect::<Vec<_>>();
if headings != ["Name", "Region", "Date", "Staff", "Status", "Notes"] {
return Err(Error::Fatal(anyhow::anyhow!(
"Mentoring data sheet contained wrong headings: {}",
headings.join(", ")
)));
}
} else {
if cells[0].effective_value.is_none() {
break;
let name = cell_string(&cells[0]);
let date = cell_date(&cells[2]).with_context(|| {
format!(
"Failed to parse date from row {} in sheet ID {}",
row_number + 1,
mentoring_records_sheet_id
)
})?;
let entry = mentoring_records.records.entry(name);
match entry {
Entry::Vacant(entry) => {
entry.insert(MentoringRecord { last_date: date });
}
let name = cell_string(&cells[0]);
let date = cell_date(&cells[2])
.with_context(|| format!("Failed to parse date from row {}", row_number + 1))?;
let entry = mentoring_records.records.entry(name);
match entry {
Entry::Vacant(entry) => {
Entry::Occupied(mut entry) => {
if entry.get().last_date < date {
entry.insert(MentoringRecord { last_date: date });
}
Entry::Occupied(mut entry) => {
if entry.get().last_date < date {
entry.insert(MentoringRecord { last_date: date });
}
}
}
}
}
Expand All @@ -103,9 +97,10 @@ pub async fn get_mentoring_records(
async fn get_mentoring_records_grid_data(
client: SheetsClient,
mentoring_records_sheet_id: &str,
) -> Result<Vec<GridData>, Error> {
let data_result = client.get(mentoring_records_sheet_id, true, &[]).await;
let data = match data_result {
) -> Result<Vec<Vec<CellData>>, Error> {
let expected_sheet_title = "Feedback";
let data_result = client.get(mentoring_records_sheet_id).await;
let mut data = match data_result {
Ok(data) => data,
Err(Error::PotentiallyIgnorablePermissions(_)) => {
return Ok(Vec::new());
Expand All @@ -120,24 +115,12 @@ async fn get_mentoring_records_grid_data(
return Err(err);
}
};
let expected_sheet_title = "Feedback";
let sheet = data
.body
.sheets
.into_iter()
.find(|sheet| {
sheet
.properties
.as_ref()
.map(|properties| properties.title.as_str())
== Some(expected_sheet_title)
})
.ok_or_else(|| {
Error::Fatal(anyhow::anyhow!(
"Couldn't find sheet '{}' in spreadsheet with ID {}",
expected_sheet_title,
mentoring_records_sheet_id
))
})?;
Ok(sheet.data)
let sheet = data.remove(expected_sheet_title).ok_or_else(|| {
Error::Fatal(anyhow::anyhow!(
"Couldn't find sheet '{}' in spreadsheet with ID {}",
expected_sheet_title,
mentoring_records_sheet_id
))
})?;
Ok(sheet.rows)
}
4 changes: 2 additions & 2 deletions src/prs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,14 @@ pub(crate) async fn fill_in_reviewers(
.collect())
}

#[derive(PartialEq, Eq, Serialize)]
#[derive(Debug, PartialEq, Eq, Serialize)]
pub(crate) enum CheckStatus {
CheckedAndOk,
CheckedAndCheckAgain,
Unchecked,
}

#[derive(PartialEq, Eq, Serialize)]
#[derive(Debug, PartialEq, Eq, Serialize)]
pub(crate) struct ReviewerStaffOnlyDetails {
pub(crate) name: String,
pub(crate) attended_training: bool,
Expand Down
Loading