Skip to content

Commit 80c0e9d

Browse files
authored
Complain if PRs don't reference regions correctly (#3)
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 cfa2b01 commit 80c0e9d

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)