Skip to content

Commit dcb9af4

Browse files
committed
Complain if PRs don't reference regions correctly
We want to be able to rely on regions for filtering, e.g. when showing things in dashboards. Ideally we would have a source of truth of which region each trainee is in, but we don't, so in the mean time check that the title claims to be in some region. Hard-code the known regions for now - ideally an API will provide these in the future.
1 parent fa320b6 commit dcb9af4

File tree

1 file changed

+62
-7
lines changed

1 file changed

+62
-7
lines changed

src/bin/pr-metadata-validator.rs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::process::exit;
1+
use std::{collections::BTreeMap, process::exit};
22

33
use chrono::NaiveDate;
44
use indexmap::IndexMap;
@@ -35,6 +35,16 @@ async fn main() {
3535
}
3636
};
3737

38+
// TODO: Fetch this from classplanner or somewhere when we have access to a useful API.
39+
let known_region_aliases = KnownRegions(btreemap! {
40+
"Cape Town" => vec!["South Africa", "SouthAfrica", "ZA", "ZA Cape Town"],
41+
"Glasgow" => vec!["Scotland"],
42+
"London" => vec![],
43+
"North West" => vec!["NW", "Manchester"],
44+
"Sheffield" => vec![],
45+
"West Midlands" => vec!["WM", "WestMidlands", "West-Midlands", "Birmingham"],
46+
});
47+
3848
let github_token =
3949
std::env::var("GH_TOKEN").expect("GH_TOKEN wasn't set - must be set to a GitHub API token");
4050
let octocrab = octocrab_for_token(github_token).expect("Failed to get octocrab");
@@ -46,9 +56,16 @@ async fn main() {
4656
register_sheet_id: "".to_owned(),
4757
course_schedule,
4858
};
49-
let result = validate_pr(&octocrab, course, &module_name, &github_org_name, pr_number)
50-
.await
51-
.expect("Failed to validate PR");
59+
let result = validate_pr(
60+
&octocrab,
61+
course,
62+
&module_name,
63+
&github_org_name,
64+
pr_number,
65+
&known_region_aliases,
66+
)
67+
.await
68+
.expect("Failed to validate PR");
5269
match result {
5370
ValidationResult::Ok => {}
5471
ValidationResult::CouldNotMatch => {
@@ -69,6 +86,15 @@ async fn main() {
6986
.expect("Failed to create comment with validation error");
7087
exit(2);
7188
}
89+
ValidationResult::UnknownRegion => {
90+
eprintln!("Validation error: Could not find region in PR title");
91+
octocrab
92+
.issues(github_org_name, module_name.clone())
93+
.create_comment(pr_number, UNKNOWN_REGION_COMMENT)
94+
.await
95+
.expect("Failed to create comment with validation error");
96+
exit(2);
97+
}
7298
}
7399
}
74100

@@ -80,14 +106,19 @@ If this PR is not coursework, please add the NotCoursework label (and message on
80106

81107
const BAD_TITLE_COMMENT_PREFIX: &str = r#"Your PR's title isn't in the expected format.
82108
83-
Please check its title is in the correct format, and update it.
109+
Please check the expected title format, and update yours to match.
84110
85111
Reason: "#;
86112

113+
const UNKNOWN_REGION_COMMENT: &str = r#"Your PR's title didn't contain a known region.
114+
115+
Please check the expected title format, and make sure your region is in the correct place and spelled correctly."#;
116+
87117
enum ValidationResult {
88118
Ok,
89119
CouldNotMatch,
90120
BadTitleFormat { reason: String },
121+
UnknownRegion,
91122
}
92123

93124
async fn validate_pr(
@@ -96,6 +127,7 @@ async fn validate_pr(
96127
module_name: &str,
97128
github_org_name: &str,
98129
pr_number: u64,
130+
known_region_aliases: &KnownRegions,
99131
) -> Result<ValidationResult, Error> {
100132
let course = course_schedule
101133
.with_assignments(octocrab, github_org_name)
@@ -124,7 +156,7 @@ async fn validate_pr(
124156
&course.modules[module_name],
125157
user_prs,
126158
Vec::new(),
127-
&Region("London".to_owned()),
159+
&ARBITRARY_REGION,
128160
)
129161
.map_err(|err| err.context("Failed to match PRs to assignments"))?;
130162

@@ -140,7 +172,11 @@ async fn validate_pr(
140172
reason: "Wrong number of parts separated by |s".to_owned(),
141173
});
142174
}
143-
// TODO: Validate Regions when they're known (0)
175+
176+
if !known_region_aliases.is_known_ignoring_case(title_sections[0].trim()) {
177+
return Ok(ValidationResult::UnknownRegion);
178+
}
179+
144180
// TODO: Validate cohorts when they're known (1)
145181
let sprint_regex = Regex::new(r"^(S|s)print \d+$").unwrap();
146182
let sprint_section = title_sections[3].trim();
@@ -157,6 +193,25 @@ async fn validate_pr(
157193
Ok(ValidationResult::Ok)
158194
}
159195

196+
struct KnownRegions(BTreeMap<&'static str, Vec<&'static str>>);
197+
198+
impl KnownRegions {
199+
fn is_known_ignoring_case(&self, possible_region: &str) -> bool {
200+
let possible_region_lower = possible_region.to_ascii_lowercase();
201+
for (known_region, known_region_aliases) in &self.0 {
202+
if known_region.to_ascii_lowercase() == possible_region_lower {
203+
return true;
204+
}
205+
for known_region_alias in known_region_aliases {
206+
if known_region_alias.to_ascii_lowercase() == possible_region_lower {
207+
return true;
208+
}
209+
}
210+
}
211+
false
212+
}
213+
}
214+
160215
fn make_fake_course_schedule(module_name: String) -> CourseSchedule {
161216
let fixed_date = NaiveDate::from_ymd_opt(2030, 1, 1).unwrap();
162217
let mut sprints = IndexMap::new();

0 commit comments

Comments
 (0)