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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "trainee-tracker"
version = "0.1.0"
edition = "2021"
edition = "2024"
default-run = "trainee-tracker"

[dependencies]
Expand Down
18 changes: 11 additions & 7 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{anyhow, Context};
use anyhow::{Context, anyhow};
use askama::Template;
use axum::{
extract::{Query, State},
Expand All @@ -11,8 +11,8 @@ use tower_sessions::Session;
use uuid::Uuid;

use crate::{
slack::{make_slack_redirect_uri, SLACK_ACCESS_TOKEN_SESSION_KEY},
Config, Error, ServerState,
slack::{SLACK_ACCESS_TOKEN_SESSION_KEY, make_slack_redirect_uri},
};

#[derive(Deserialize)]
Expand Down Expand Up @@ -56,7 +56,10 @@ pub(crate) async fn github_auth_redirect_url(
original_uri: Uri,
) -> Result<Uri, Error> {
let uuid = Uuid::new_v4();
let redirect_url = format!("https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}/api/oauth-callbacks/github&scope=read:user%20read:org&state={}", server_state.config.github_client_id, server_state.config.public_base_url, uuid);
let redirect_url = format!(
"https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}/api/oauth-callbacks/github&scope=read:user%20read:org&state={}",
server_state.config.github_client_id, server_state.config.public_base_url, uuid
);
server_state
.github_auth_state_cache
.insert(uuid, original_uri)
Expand Down Expand Up @@ -93,14 +96,15 @@ pub async fn handle_google_oauth_callback(
session: Session,
params: Query<OauthCallbackParams>,
) -> Result<Html<String>, Error> {
let auth_state = if let Some(auth_state) = server_state
let auth_state = match server_state
.google_auth_state_cache
.remove(&params.state)
.await
{
auth_state
} else {
return Err(Error::Fatal(anyhow!("Unrecognised state")));
Some(auth_state) => auth_state,
None => {
return Err(Error::Fatal(anyhow!("Unrecognised state")));
}
};

let redirect_uri = format!(
Expand Down
13 changes: 11 additions & 2 deletions src/bin/match-pr-to-assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ async fn main() {

let octocrab = octocrab_for_token(github_token.to_owned()).expect("Failed to get octocrab");

let Ok([_https, _scheme, _githubdotcom, org_name, module_name, _pull, pr_number_str]) =
<[_; 7]>::try_from(pr_link.split('/').collect::<Vec<_>>())
let Ok(
[
_https,
_scheme,
_githubdotcom,
org_name,
module_name,
_pull,
pr_number_str,
],
) = <[_; 7]>::try_from(pr_link.split('/').collect::<Vec<_>>())
else {
panic!("Couldn't parse GitHub PR link {}", pr_link);
};
Expand Down
23 changes: 19 additions & 4 deletions src/bin/pr-metadata-validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ use maplit::btreemap;
use octocrab::Octocrab;
use regex::Regex;
use trainee_tracker::{
Error,
config::{CourseSchedule, CourseScheduleWithRegisterSheetId},
course::match_prs_to_assignments,
newtypes::Region,
octocrab::octocrab_for_token,
prs::get_prs,
Error,
};

const ARBITRARY_REGION: Region = Region(String::new());
Expand All @@ -24,7 +24,15 @@ async fn main() {
};
let pr_parts: Vec<_> = pr_url.split("/").collect();
let (github_org_name, module_name, pr_number) = match pr_parts.as_slice() {
[_http, _scheme, _domain, github_org_name, module_name, _pull, number] => (
[
_http,
_scheme,
_domain,
github_org_name,
module_name,
_pull,
number,
] => (
(*github_org_name).to_owned(),
(*module_name).to_owned(),
number.parse::<u64>().expect("Failed to parse PR number"),
Expand Down Expand Up @@ -78,7 +86,9 @@ async fn main() {
ValidationResult::UnknownRegion => UNKNOWN_REGION_COMMENT,
};

let full_message = format!("{message}\n\nIf this PR is not coursework, please add the NotCoursework label (and message on Slack in #cyf-curriculum or it will probably not be noticed).\n\nIf this PR needs reviewed, please add the 'Needs Review' label to this PR after you have resolved the issues listed above.");
let full_message = format!(
"{message}\n\nIf this PR is not coursework, please add the NotCoursework label (and message on Slack in #cyf-curriculum or it will probably not be noticed).\n\nIf this PR needs reviewed, please add the 'Needs Review' label to this PR after you have resolved the issues listed above."
);
eprintln!("{}", full_message);
octocrab
.issues(&github_org_name, &module_name)
Expand Down Expand Up @@ -201,7 +211,12 @@ async fn validate_pr(
let sprint_regex = Regex::new(r"^(S|s)print \d+$").unwrap();
let sprint_section = title_sections[3].trim();
if !sprint_regex.is_match(sprint_section) {
return Ok(ValidationResult::BadTitleFormat { reason: format!("Sprint part ({}) doesn't match expected format (example: 'Sprint 2', without quotes)", sprint_section) });
return Ok(ValidationResult::BadTitleFormat {
reason: format!(
"Sprint part ({}) doesn't match expected format (example: 'Sprint 2', without quotes)",
sprint_section
),
});
}

if pr_in_question.title.to_ascii_uppercase() == pr_in_question.title {
Expand Down
19 changes: 11 additions & 8 deletions src/course.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ use std::{
};

use crate::{
Error,
config::CourseScheduleWithRegisterSheetId,
github_accounts::{get_trainees, Trainee},
mentoring::{get_mentoring_records, MentoringRecord},
github_accounts::{Trainee, get_trainees},
mentoring::{MentoringRecord, get_mentoring_records},
newtypes::{GithubLogin, Region},
octocrab::all_pages,
prs::{get_prs, Pr, PrState},
register::{get_register, Register},
prs::{Pr, PrState, get_prs},
register::{Register, get_register},
sheets::SheetsClient,
Error,
};
use anyhow::Context;
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
Expand All @@ -23,8 +23,8 @@ use futures::future::join_all;
use indexmap::{IndexMap, IndexSet};
use maplit::btreemap;
use octocrab::{
models::{issues::Issue, teams::RequestedTeam, Author},
Octocrab,
models::{Author, issues::Issue, teams::RequestedTeam},
};
use regex::Regex;
use serde::Serialize;
Expand Down Expand Up @@ -165,7 +165,7 @@ fn parse_issue(issue: &Issue) -> Result<Option<(NonZeroUsize, Option<Assignment>
return Err(Error::UserFacing(format!(
"Failed to parse issue {} - sprint label wasn't (non-zero) number: {}",
html_url, label.name
)))
)));
}
}
}
Expand Down Expand Up @@ -901,7 +901,10 @@ pub fn match_prs_to_assignments(
let number = usize::from_str(number_str)
.with_context(|| format!("Failed to parse '{}' as number", number_str))?;
if number == 0 || number > 20 {
return Err(Error::Fatal(anyhow::anyhow!("Sprint number was impractical - expected something between 1 and 20 but was {}", number)));
return Err(Error::Fatal(anyhow::anyhow!(
"Sprint number was impractical - expected something between 1 and 20 but was {}",
number
)));
}

sprint_index = Some(number - 1);
Expand Down
10 changes: 5 additions & 5 deletions src/endpoints.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::collections::BTreeMap;

use ::octocrab::models::{teams::RequestedTeam, Author};
use ::octocrab::models::{Author, teams::RequestedTeam};
use anyhow::Context;
use axum::{
Json,
extract::{OriginalUri, Path, State},
response::IntoResponse,
Json,
};
use futures::future::join_all;
use http::HeaderMap;
Expand All @@ -14,13 +14,13 @@ use serde::Serialize;
use tower_sessions::Session;

use crate::{
Error, ServerState,
github_accounts::get_trainees,
newtypes::GithubLogin,
octocrab::{all_pages, octocrab},
prs::{fill_in_reviewers, get_prs, PrWithReviews},
register::{get_register, Attendance},
prs::{PrWithReviews, fill_in_reviewers, get_prs},
register::{Attendance, get_register},
sheets::sheets_client,
Error, ServerState,
};

pub async fn health_check() -> impl IntoResponse {
Expand Down
10 changes: 5 additions & 5 deletions src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ use axum::{
response::{Html, IntoResponse, Response},
};
use futures::future::join_all;
use http::{header::CONTENT_TYPE, HeaderMap, StatusCode, Uri};
use http::{HeaderMap, StatusCode, Uri, header::CONTENT_TYPE};
use serde::Deserialize;
use tower_sessions::Session;

use crate::{
Error, ServerState,
config::CourseScheduleWithRegisterSheetId,
course::{
fetch_batch_metadata, get_batch_with_submissions, Attendance, Batch, BatchMetadata, Course,
Submission, TraineeStatus,
Attendance, Batch, BatchMetadata, Course, Submission, TraineeStatus, fetch_batch_metadata,
get_batch_with_submissions,
},
google_groups::{get_groups, groups_client, GoogleGroup},
google_groups::{GoogleGroup, get_groups, groups_client},
octocrab::octocrab,
prs::{MaybeReviewerStaffOnlyDetails, PrState, ReviewerInfo},
reviewer_staff_info::get_reviewer_staff_info,
sheets::sheets_client,
slack::list_groups_with_members,
Error, ServerState,
};

pub async fn list_courses(
Expand Down
15 changes: 11 additions & 4 deletions src/github_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use serde::{Deserialize, Serialize};
use sheets::types::Sheet;

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

// TODO: Replace this with a serde implementation from a Google Sheet.
Expand Down Expand Up @@ -67,15 +67,22 @@ fn trainees_from_sheet(sheet: &Sheet) -> Result<BTreeMap<GithubLogin, Trainee>,
let mut trainees = BTreeMap::new();
for data in &sheet.data {
if data.start_column != 0 || data.start_row != 0 {
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)));
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
)));
}
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)));
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]));
Expand Down
6 changes: 3 additions & 3 deletions src/google_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ use anyhow::Context;
use email_address::EmailAddress;
use futures::future::join_all;
use gsuite_api::{
types::{Group, Member},
Client, Response,
types::{Group, Member},
};
use http::Uri;
use tower_sessions::Session;

use crate::{
google_auth::{make_redirect_uri, redirect_endpoint, GoogleScope},
newtypes::new_case_insensitive_email_address,
Error, ServerState,
google_auth::{GoogleScope, make_redirect_uri, redirect_endpoint},
newtypes::new_case_insensitive_email_address,
};

pub async fn groups_client(
Expand Down
4 changes: 2 additions & 2 deletions src/mentoring.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{btree_map::Entry, BTreeMap};
use std::collections::{BTreeMap, btree_map::Entry};

use anyhow::Context;
use chrono::{NaiveDate, Utc};
Expand All @@ -7,8 +7,8 @@ use sheets::types::GridData;
use tracing::warn;

use crate::{
sheets::{cell_date, cell_string, SheetsClient},
Error,
sheets::{SheetsClient, cell_date, cell_string},
};

pub struct MentoringRecords {
Expand Down
6 changes: 3 additions & 3 deletions src/octocrab.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
use std::{sync::Arc, time::Duration};

use anyhow::Context;
use http::{header::USER_AGENT, HeaderValue, Uri};
use http::{HeaderValue, Uri, header::USER_AGENT};
use hyper_rustls::HttpsConnectorBuilder;
use octocrab::{
AuthState, Octocrab, OctocrabBuilder,
service::middleware::{
auth_header::AuthHeaderLayer, base_uri::BaseUriLayer, extra_headers::ExtraHeadersLayer,
retry::RetryConfig,
},
AuthState, Octocrab, OctocrabBuilder,
};
use serde::de::DeserializeOwned;
use tower::retry::RetryLayer;
use tower_sessions::Session;

use crate::{
auth::{github_auth_redirect_url, GITHUB_ACCESS_TOKEN_SESSION_KEY},
Error, ServerState,
auth::{GITHUB_ACCESS_TOKEN_SESSION_KEY, github_auth_redirect_url},
};

pub(crate) async fn octocrab(
Expand Down
4 changes: 2 additions & 2 deletions src/prs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use std::collections::{BTreeMap, BTreeSet};
use anyhow::Context;
use chrono::{DateTime, TimeDelta};
use futures::future::join_all;
use octocrab::Octocrab;
use octocrab::models::pulls::{Comment, PullRequest, Review as OctoReview};
use octocrab::models::{Author, IssueState};
use octocrab::params::State;
use octocrab::Octocrab;
use serde::Serialize;

use crate::newtypes::GithubLogin;
use crate::Error;
use crate::newtypes::GithubLogin;

#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Pr {
Expand Down
4 changes: 2 additions & 2 deletions src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use sheets::types::{CellData, GridData};
use tracing::warn;

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

#[derive(Debug)]
Expand Down
10 changes: 7 additions & 3 deletions src/reviewer_staff_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use std::collections::BTreeMap;
use sheets::types::Sheet;

use crate::{
Error,
newtypes::GithubLogin,
prs::{CheckStatus, ReviewerStaffOnlyDetails},
sheets::{cell_bool, cell_string, SheetsClient},
Error,
sheets::{SheetsClient, cell_bool, cell_string},
};

pub(crate) async fn get_reviewer_staff_info(
Expand Down Expand Up @@ -58,7 +58,11 @@ fn reviewer_staff_detail_from_sheet(
let mut reviewers = BTreeMap::new();
for data in &sheet.data {
if data.start_column != 0 || data.start_row != 0 {
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)));
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
)));
}
for (row_index, row) in data.row_data.iter().enumerate() {
if row_index == 0 {
Expand Down
Loading