@@ -143,7 +143,10 @@ fn parse_issue(issue: &Issue) -> Result<Option<(NonZeroUsize, Option<Assignment>
143
143
} = issue;
144
144
145
145
let mut sprint = None ;
146
- let mut assignment = None ;
146
+
147
+ let mut submit_label = None ;
148
+ let mut optionality = None ;
149
+
147
150
for label in labels {
148
151
if let Some ( sprint_number) = label. name . strip_prefix ( "📅 Sprint " ) {
149
152
if sprint. is_some ( ) {
@@ -164,47 +167,81 @@ fn parse_issue(issue: &Issue) -> Result<Option<(NonZeroUsize, Option<Assignment>
164
167
}
165
168
}
166
169
}
167
- if let Some ( submit_label ) = label. name . strip_prefix ( "Submit:" ) {
168
- if assignment . is_some ( ) {
170
+ if let Some ( label ) = label. name . strip_prefix ( "Submit:" ) {
171
+ if submit_label . is_some ( ) {
169
172
return Err ( Error :: UserFacing ( format ! (
170
173
"Failed to parse issue {} - duplicate submit labels" ,
171
174
html_url
172
175
) ) ) ;
173
176
}
174
- match submit_label {
175
- "None" => {
176
- assignment = Some ( None ) ;
177
- }
178
- "PR" => {
179
- assignment = Some ( Some ( Assignment :: ExpectedPullRequest {
180
- title : title. clone ( ) ,
181
- } ) ) ;
182
- }
183
- "Issue" => {
184
- // TODO: Handle these.
185
- assignment = Some ( None ) ;
186
- }
187
- other => {
188
- return Err ( Error :: UserFacing ( format ! (
189
- "Failed to parse issue {} - submit label wasn't recognised: {}" ,
190
- html_url, other
191
- ) ) ) ;
192
- }
177
+ submit_label = Some ( label) ;
178
+ }
179
+
180
+ if label. name == "🏕 Priority Mandatory" {
181
+ if optionality. is_some ( ) {
182
+ return Err ( Error :: UserFacing ( format ! (
183
+ "Failed to parse issue {} - duplicate priority labels" ,
184
+ html_url
185
+ ) ) ) ;
186
+ }
187
+ optionality = Some ( AssignmentOptionality :: Mandatory )
188
+ } else if label. name == "🏝️ Priority Stretch" {
189
+ if optionality. is_some ( ) {
190
+ return Err ( Error :: UserFacing ( format ! (
191
+ "Failed to parse issue {} - duplicate priority labels" ,
192
+ html_url
193
+ ) ) ) ;
193
194
}
195
+ optionality = Some ( AssignmentOptionality :: Stretch )
194
196
}
195
197
}
198
+
196
199
let sprint = sprint. ok_or_else ( || {
197
200
Error :: UserFacing ( format ! (
198
- "Failed to parse issue {} - no sprint label.\n \n If this issue was made my a curriculum team member it should be given a sprint label.\n If this issue was created by a trainee for step submission, it should probably be closed (and they should create the issue in their fork)." ,
199
- html_url
201
+ "Failed to parse issue {} - no sprint label.{}" ,
202
+ html_url, BAD_LABEL_SUFFIX ,
203
+ ) )
204
+ } ) ?;
205
+
206
+ let submit_label = match submit_label {
207
+ Some ( submit_label) => submit_label,
208
+ // TODO: For now we treat a missing Submit label as an issue to ignore.
209
+ // When all issues have Submit labels, we should error if they're missing.
210
+ None => {
211
+ return Ok ( Some ( ( sprint, None ) ) ) ;
212
+ }
213
+ } ;
214
+
215
+ let optionality = optionality. ok_or_else ( || {
216
+ Error :: UserFacing ( format ! (
217
+ "Failed to parse issue {} - no priority label.{}" ,
218
+ html_url, BAD_LABEL_SUFFIX
200
219
) )
201
220
} ) ?;
202
- // TODO
203
- // let assignment = assignment.ok_or_else(|| Error::UserFacing(format!("Failed to parse issue {} - no submit label", html_url)))?;
204
- let assignment = assignment. unwrap_or ( None ) ;
221
+
222
+ let assignment = match submit_label {
223
+ "None" => None ,
224
+ "PR" => Some ( Assignment :: ExpectedPullRequest {
225
+ title : title. clone ( ) ,
226
+ optionality,
227
+ } ) ,
228
+ "Issue" => {
229
+ // TODO: Handle these.
230
+ None
231
+ }
232
+ other => {
233
+ return Err ( Error :: UserFacing ( format ! (
234
+ "Failed to parse issue {} - submit label wasn't recognised: {}" ,
235
+ html_url, other
236
+ ) ) ) ;
237
+ }
238
+ } ;
239
+
205
240
Ok ( Some ( ( sprint, assignment) ) )
206
241
}
207
242
243
+ const BAD_LABEL_SUFFIX : & str = "\n \n If this issue was made my a curriculum team member it should be given a sprint label.\n If this issue was created by a trainee for step submission, it should probably be closed (and they should create the issue in their fork)." ;
244
+
208
245
#[ derive( Serialize ) ]
209
246
pub struct Course {
210
247
pub name : String ,
@@ -263,20 +300,34 @@ pub enum Assignment {
263
300
} ,
264
301
ExpectedPullRequest {
265
302
title : String ,
303
+ optionality : AssignmentOptionality ,
266
304
} ,
267
305
}
268
306
269
307
impl Assignment {
308
+ pub fn optionality ( & self ) -> AssignmentOptionality {
309
+ match self {
310
+ Assignment :: Attendance { .. } => AssignmentOptionality :: Mandatory ,
311
+ Assignment :: ExpectedPullRequest { optionality, .. } => optionality. clone ( ) ,
312
+ }
313
+ }
314
+
270
315
pub fn heading ( & self ) -> String {
271
316
match self {
272
317
Assignment :: Attendance {
273
318
class_dates : _class_dates,
274
319
} => "Attendance" . to_owned ( ) ,
275
- Assignment :: ExpectedPullRequest { title } => format ! ( "PR: {title}" ) ,
320
+ Assignment :: ExpectedPullRequest { title, .. } => format ! ( "PR: {title}" ) ,
276
321
}
277
322
}
278
323
}
279
324
325
+ #[ derive( Clone , Copy , Debug , PartialEq , Eq , Serialize ) ]
326
+ pub enum AssignmentOptionality {
327
+ Mandatory ,
328
+ Stretch ,
329
+ }
330
+
280
331
pub struct BatchMembers {
281
332
pub name : String ,
282
333
pub trainees : BTreeMap < GithubLogin , Trainee > ,
@@ -363,11 +414,18 @@ impl TraineeWithSubmissions {
363
414
Attendance :: Absent { .. } => { }
364
415
}
365
416
}
366
- SubmissionState :: Some ( Submission :: PullRequest { pull_request } ) => {
367
- denominator += 10 ;
417
+ SubmissionState :: Some ( Submission :: PullRequest {
418
+ pull_request,
419
+ optionality,
420
+ } ) => {
421
+ let max = match optionality {
422
+ AssignmentOptionality :: Mandatory => 10 ,
423
+ AssignmentOptionality :: Stretch => 12 ,
424
+ } ;
425
+ denominator += max;
368
426
match pull_request. state {
369
427
PrState :: Complete => {
370
- numerator += 10 ;
428
+ numerator += max ;
371
429
}
372
430
PrState :: NeedsReview | PrState :: Reviewed => {
373
431
numerator += 6 ;
@@ -381,6 +439,9 @@ impl TraineeWithSubmissions {
381
439
Assignment :: Attendance { .. } => denominator += 20 ,
382
440
Assignment :: ExpectedPullRequest { .. } => denominator += 10 ,
383
441
} ,
442
+ SubmissionState :: MissingStretch ( _) => {
443
+ denominator += 2 ;
444
+ }
384
445
SubmissionState :: MissingButNotExpected ( _) => { }
385
446
}
386
447
}
@@ -436,23 +497,28 @@ pub struct SprintWithSubmissions {
436
497
pub enum SubmissionState {
437
498
Some ( Submission ) ,
438
499
MissingButExpected ( Assignment ) ,
500
+ MissingStretch ( Assignment ) ,
439
501
MissingButNotExpected ( Assignment ) ,
440
502
}
441
503
442
504
impl SubmissionState {
443
- fn is_missing ( & self ) -> bool {
505
+ fn is_submitted ( & self ) -> bool {
444
506
match self {
445
- Self :: Some ( _) => false ,
446
- Self :: MissingButExpected ( _) => true ,
447
- Self :: MissingButNotExpected ( _) => true ,
507
+ Self :: Some ( _) => true ,
508
+ Self :: MissingButExpected ( _) => false ,
509
+ Self :: MissingStretch ( _) => false ,
510
+ Self :: MissingButNotExpected ( _) => false ,
448
511
}
449
512
}
450
513
}
451
514
452
515
#[ derive( Clone , Debug , PartialEq , Eq ) ]
453
516
pub enum Submission {
454
517
Attendance ( Attendance ) ,
455
- PullRequest { pull_request : Pr } ,
518
+ PullRequest {
519
+ pull_request : Pr ,
520
+ optionality : AssignmentOptionality ,
521
+ } ,
456
522
}
457
523
458
524
impl Submission {
@@ -462,14 +528,14 @@ impl Submission {
462
528
Self :: Attendance ( Attendance :: OnTime { .. } ) => String :: from ( "On time" ) ,
463
529
Self :: Attendance ( Attendance :: Late { .. } ) => String :: from ( "Late" ) ,
464
530
Self :: Attendance ( Attendance :: WrongDay { .. } ) => String :: from ( "Wrong day" ) ,
465
- Self :: PullRequest { pull_request } => format ! ( "#{}" , pull_request. number) ,
531
+ Self :: PullRequest { pull_request, .. } => format ! ( "#{}" , pull_request. number) ,
466
532
}
467
533
}
468
534
469
535
pub fn link ( & self ) -> String {
470
536
match self {
471
537
Self :: Attendance ( attendance) => attendance. register_url ( ) . to_owned ( ) ,
472
- Self :: PullRequest { pull_request } => pull_request. url . clone ( ) ,
538
+ Self :: PullRequest { pull_request, .. } => pull_request. url . clone ( ) ,
473
539
}
474
540
}
475
541
}
@@ -766,20 +832,21 @@ pub fn match_prs_to_assignments(
766
832
) -> Result < ModuleWithSubmissions , Error > {
767
833
let mut sprints = Vec :: with_capacity ( module. sprints . len ( ) ) ;
768
834
for ( sprint_index, sprint) in module. sprints . iter ( ) . enumerate ( ) {
769
- sprints. push ( SprintWithSubmissions {
770
- submissions : vec ! [
771
- if sprint. is_in_past( region) {
772
- SubmissionState :: MissingButExpected ( Assignment :: Attendance {
773
- class_dates: sprint. dates. clone( ) ,
774
- } )
775
- } else {
776
- SubmissionState :: MissingButNotExpected ( Assignment :: Attendance {
777
- class_dates: sprint. dates. clone( ) ,
778
- } )
779
- } ;
780
- sprint. assignment_count( )
781
- ] ,
782
- } ) ;
835
+ let mut submissions = Vec :: with_capacity ( sprint. assignment_count ( ) ) ;
836
+ for assignment in sprint. assignments . iter ( ) . cloned ( ) {
837
+ let submission = if sprint. is_in_past ( region) {
838
+ match assignment. optionality ( ) {
839
+ AssignmentOptionality :: Mandatory => {
840
+ SubmissionState :: MissingButExpected ( assignment)
841
+ }
842
+ AssignmentOptionality :: Stretch => SubmissionState :: MissingStretch ( assignment) ,
843
+ }
844
+ } else {
845
+ SubmissionState :: MissingButNotExpected ( assignment)
846
+ } ;
847
+ submissions. push ( submission) ;
848
+ }
849
+ sprints. push ( SprintWithSubmissions { submissions } ) ;
783
850
784
851
for ( assignment_index, assignment) in sprint. assignments . iter ( ) . enumerate ( ) {
785
852
if let Assignment :: Attendance {
@@ -847,6 +914,7 @@ fn match_pr_to_assignment(
847
914
match_count : usize ,
848
915
sprint_index : usize ,
849
916
assignment_index : usize ,
917
+ optionality : AssignmentOptionality ,
850
918
}
851
919
852
920
let mut best_match: Option < Match > = None ;
@@ -866,6 +934,7 @@ fn match_pr_to_assignment(
866
934
match assignment {
867
935
Assignment :: ExpectedPullRequest {
868
936
title : expected_title,
937
+ optionality,
869
938
} => {
870
939
let mut assignment_title_words = make_title_more_matchable ( expected_title) ;
871
940
if let Some ( claimed_sprint_index) = claimed_sprint_index {
@@ -877,7 +946,7 @@ fn match_pr_to_assignment(
877
946
}
878
947
}
879
948
let match_count = assignment_title_words. intersection ( & pr_title_words) . count ( ) ;
880
- if submissions[ sprint_index] . submissions [ assignment_index] . is_missing ( )
949
+ if ! submissions[ sprint_index] . submissions [ assignment_index] . is_submitted ( )
881
950
&& match_count
882
951
> best_match
883
952
. as_ref ( )
@@ -888,6 +957,7 @@ fn match_pr_to_assignment(
888
957
match_count,
889
958
sprint_index,
890
959
assignment_index,
960
+ optionality : optionality. clone ( ) ,
891
961
} ) ;
892
962
}
893
963
}
@@ -898,11 +968,15 @@ fn match_pr_to_assignment(
898
968
if let Some ( Match {
899
969
sprint_index,
900
970
assignment_index,
971
+ optionality,
901
972
..
902
973
} ) = best_match
903
974
{
904
975
submissions[ sprint_index] . submissions [ assignment_index] =
905
- SubmissionState :: Some ( Submission :: PullRequest { pull_request : pr } ) ;
976
+ SubmissionState :: Some ( Submission :: PullRequest {
977
+ pull_request : pr,
978
+ optionality,
979
+ } ) ;
906
980
} else if !pr. is_closed {
907
981
unknown_prs. push ( pr) ;
908
982
}
0 commit comments