Add support for entering eliminations matches on the scouting app
This patch plumbs through the "round" and "compLevel" fields where
necessary in order to let folks scout eliminations matches.
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: Idf20d77ed36b79f7dffb598f98e382260e6b81c9
diff --git a/scouting/scouting_test.ts b/scouting/scouting_test.ts
index 98e1d00..5740cc8 100644
--- a/scouting/scouting_test.ts
+++ b/scouting/scouting_test.ts
@@ -41,6 +41,12 @@
return element(by.css('.error_message')).getText();
}
+// Returns the currently displayed error message on the screen. This only
+// exists on screens where the web page interacts with the web server.
+function getValueOfInputById(id: string) {
+ return element(by.id(id)).getAttribute('value');
+}
+
// Asserts that the field on the "Submit and Review" screen has a specific
// value.
function expectReviewFieldToBe(fieldName: string, expectedValue: string) {
@@ -120,14 +126,39 @@
it('should: show matches in chronological order.', async () => {
await loadPage();
- expect(await getNthMatchLabel(0)).toEqual('Quals 1');
- expect(await getNthMatchLabel(1)).toEqual('Quals 2');
- expect(await getNthMatchLabel(2)).toEqual('Quals 3');
- expect(await getNthMatchLabel(9)).toEqual('Quals 10');
- // TODO(phil): Validate quarter finals and friends. Right now we don't
- // distinguish between "sets". I.e. we display 4 "Quarter Final 1" matches
- // without being able to distinguish between them.
- expect(await getNthMatchLabel(87)).toEqual('Final 1');
+ expect(await getNthMatchLabel(0)).toEqual('Quals Match 1');
+ expect(await getNthMatchLabel(1)).toEqual('Quals Match 2');
+ expect(await getNthMatchLabel(2)).toEqual('Quals Match 3');
+ expect(await getNthMatchLabel(9)).toEqual('Quals Match 10');
+ expect(await getNthMatchLabel(72)).toEqual('Quarter Final 1 Match 1');
+ expect(await getNthMatchLabel(73)).toEqual('Quarter Final 2 Match 1');
+ expect(await getNthMatchLabel(74)).toEqual('Quarter Final 3 Match 1');
+ expect(await getNthMatchLabel(75)).toEqual('Quarter Final 4 Match 1');
+ expect(await getNthMatchLabel(76)).toEqual('Quarter Final 1 Match 2');
+ expect(await getNthMatchLabel(82)).toEqual('Semi Final 1 Match 1');
+ expect(await getNthMatchLabel(83)).toEqual('Semi Final 2 Match 1');
+ expect(await getNthMatchLabel(84)).toEqual('Semi Final 1 Match 2');
+ expect(await getNthMatchLabel(85)).toEqual('Semi Final 2 Match 2');
+ expect(await getNthMatchLabel(89)).toEqual('Final 1 Match 3');
+ });
+
+ it('should: prefill the match information.', async () => {
+ await loadPage();
+
+ expect(await getHeadingText()).toEqual('Matches');
+
+ // On the 87th row of matches (index 86) click on the second team
+ // (index 1) which resolves to team 5254 in semi final 2 match 3.
+ await element
+ .all(by.css('button.match-item'))
+ .get(86 * 6 + 1)
+ .click();
+
+ expect(await getHeadingText()).toEqual('Team Selection');
+ expect(await getValueOfInputById('match_number')).toEqual('3');
+ expect(await getValueOfInputById('team_number')).toEqual('5254');
+ expect(await getValueOfInputById('round')).toEqual('2');
+ expect(await getValueOfInputById('comp_level')).toEqual('3: sf');
});
it('should: error on unknown match.', async () => {
@@ -197,6 +228,8 @@
expect(await getHeadingText()).toEqual('Team Selection');
await setTextboxByIdTo('match_number', '2');
await setTextboxByIdTo('team_number', '5254');
+ await setTextboxByIdTo('round', '42');
+ await element(by.cssContainingText('option', 'Semi Finals')).click();
await element(by.buttonText('Next')).click();
expect(await getHeadingText()).toEqual('Auto');
@@ -224,6 +257,8 @@
// Validate Team Selection.
await expectReviewFieldToBe('Match number', '2');
await expectReviewFieldToBe('Team number', '5254');
+ await expectReviewFieldToBe('Round', '42');
+ await expectReviewFieldToBe('Comp Level', 'Semi Finals');
// Validate Auto.
await expectNthReviewFieldToBe('Upper Shots Made', 0, '0');
@@ -237,7 +272,7 @@
await expectNthReviewFieldToBe('Missed Shots', 1, '0');
// Validate Climb.
- await expectReviewFieldToBe('Level', 'High');
+ await expectReviewFieldToBe('Climb Level', 'High');
await expectReviewFieldToBe('Comments', 'A very useful comment here.');
// Validate Other.
diff --git a/scouting/www/app.ng.html b/scouting/www/app.ng.html
index 3c9c2c2..7bf7e63 100644
--- a/scouting/www/app.ng.html
+++ b/scouting/www/app.ng.html
@@ -75,6 +75,8 @@
(switchTabsEvent)="switchTabTo($event)"
[teamNumber]="selectedTeamInMatch.teamNumber"
[matchNumber]="selectedTeamInMatch.matchNumber"
+ [round]="selectedTeamInMatch.round"
+ [compLevel]="selectedTeamInMatch.compLevel"
*ngSwitchCase="'Entry'"
></app-entry>
<frc971-notes *ngSwitchCase="'Notes'"></frc971-notes>
diff --git a/scouting/www/app.ts b/scouting/www/app.ts
index 9d6c539..5180218 100644
--- a/scouting/www/app.ts
+++ b/scouting/www/app.ts
@@ -14,6 +14,7 @@
type TeamInMatch = {
teamNumber: number;
matchNumber: number;
+ round: number;
compLevel: string;
};
@@ -26,6 +27,7 @@
selectedTeamInMatch: TeamInMatch = {
teamNumber: 1,
matchNumber: 1,
+ round: 1,
compLevel: 'qm',
};
tab: Tab = 'MatchList';
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index c90e3d0..9637e8c 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -25,6 +25,19 @@
| 'Review and Submit'
| 'Success';
+// TODO(phil): Deduplicate with match_list.component.ts.
+const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
+type CompLevel = typeof COMP_LEVELS[number];
+
+// TODO(phil): Deduplicate with match_list.component.ts.
+const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
+ qm: 'Qualifications',
+ ef: 'Eighth Finals',
+ qf: 'Quarter Finals',
+ sf: 'Semi Finals',
+ f: 'Finals',
+};
+
@Component({
selector: 'app-entry',
templateUrl: './entry.ng.html',
@@ -34,11 +47,15 @@
// Re-export the type here so that we can use it in the `[value]` attribute
// of radio buttons.
readonly ClimbLevel = ClimbLevel;
+ readonly COMP_LEVELS = COMP_LEVELS;
+ readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
section: Section = 'Team Selection';
@Output() switchTabsEvent = new EventEmitter<string>();
@Input() matchNumber: number = 1;
@Input() teamNumber: number = 1;
+ @Input() round: number = 1;
+ @Input() compLevel: CompLevel = 'qm';
autoUpperShotsMade: number = 0;
autoLowerShotsMade: number = 0;
autoShotsMissed: number = 0;
@@ -113,10 +130,13 @@
this.errorMessage = '';
const builder = new Builder();
+ const compLevel = builder.createString(this.compLevel);
const comment = builder.createString(this.comment);
SubmitDataScouting.startSubmitDataScouting(builder);
SubmitDataScouting.addTeam(builder, this.teamNumber);
SubmitDataScouting.addMatch(builder, this.matchNumber);
+ SubmitDataScouting.addRound(builder, this.round);
+ SubmitDataScouting.addCompLevel(builder, compLevel);
SubmitDataScouting.addMissedShotsAuto(builder, this.autoShotsMissed);
SubmitDataScouting.addUpperGoalAuto(builder, this.autoUpperShotsMade);
SubmitDataScouting.addLowerGoalAuto(builder, this.autoLowerShotsMade);
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index b8eaa78..46e5989 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -28,6 +28,18 @@
max="9999"
/>
</div>
+ <div class="row">
+ <label for="round">Round</label>
+ <input [(ngModel)]="round" type="number" id="round" min="1" max="10" />
+ </div>
+ <div class="row">
+ <label for="comp_level">Round</label>
+ <select [(ngModel)]="compLevel" type="number" id="comp_level">
+ <option *ngFor="let level of COMP_LEVELS" [ngValue]="level">
+ {{COMP_LEVEL_LABELS[level]}}
+ </option>
+ </select>
+ </div>
<div class="buttons">
<!-- hack to right align the next button -->
<div></div>
@@ -348,6 +360,8 @@
<ul>
<li>Match number: {{matchNumber}}</li>
<li>Team number: {{teamNumber}}</li>
+ <li>Round: {{round}}</li>
+ <li>Comp Level: {{COMP_LEVEL_LABELS[compLevel]}}</li>
</ul>
<h4>Auto</h4>
@@ -372,7 +386,7 @@
<h4>Climb</h4>
<ul>
- <li>Level: {{level | levelToString}}</li>
+ <li>Climb Level: {{level | levelToString}}</li>
<li>Comments: {{comment}}</li>
</ul>
diff --git a/scouting/www/match_list/match_list.component.ts b/scouting/www/match_list/match_list.component.ts
index e311664..1dc1f49 100644
--- a/scouting/www/match_list/match_list.component.ts
+++ b/scouting/www/match_list/match_list.component.ts
@@ -12,6 +12,7 @@
type TeamInMatch = {
teamNumber: number;
matchNumber: number;
+ round: number;
compLevel: string;
};
@@ -61,7 +62,9 @@
}
displayMatchNumber(match: Match): string {
- return `${this.matchType(match)} ${match.matchNumber()}`;
+ // Only display the set number ("round") for eliminations matches.
+ const round = match.compLevel() == 'qm' ? '' : `${match.round()}`;
+ return `${this.matchType(match)} ${round} Match ${match.matchNumber()}`;
}
ngOnInit() {
diff --git a/scouting/www/match_list/match_list.ng.html b/scouting/www/match_list/match_list.ng.html
index 2f77198..b2e66f8 100644
--- a/scouting/www/match_list/match_list.ng.html
+++ b/scouting/www/match_list/match_list.ng.html
@@ -13,6 +13,7 @@
(click)="setTeamInMatch({
teamNumber: team.teamNumber,
matchNumber: match.matchNumber(),
+ round: match.round(),
compLevel: match.compLevel()
})"
class="match-item"
diff --git a/scouting/www/rpc/match_list_requestor.ts b/scouting/www/rpc/match_list_requestor.ts
index 917a9b3..ddbb221 100644
--- a/scouting/www/rpc/match_list_requestor.ts
+++ b/scouting/www/rpc/match_list_requestor.ts
@@ -36,6 +36,7 @@
// Sort the list so it is in chronological order.
matchList.sort((a, b) => {
+ // First sort by match type. E.g. finals are last.
const aMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(a.compLevel());
const bMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(b.compLevel());
if (aMatchTypeIndex < bMatchTypeIndex) {
@@ -44,6 +45,9 @@
if (aMatchTypeIndex > bMatchTypeIndex) {
return 1;
}
+ // Then sort by match number. E.g. in semi finals, all match 1 rounds
+ // are done first. Then come match 2 rounds. And then, if necessary,
+ // the match 3 rounds.
const aMatchNumber = a.matchNumber();
const bMatchNumber = b.matchNumber();
if (aMatchNumber < bMatchNumber) {
@@ -52,6 +56,17 @@
if (aMatchNumber > bMatchNumber) {
return 1;
}
+ // Lastly, sort by round. I.e. Semi Final 1 Match 1 happens first. Then
+ // comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
+ // Semi Final 2 Match 2.
+ const aRound = a.round();
+ const bRound = b.round();
+ if (aRound < bRound) {
+ return -1;
+ }
+ if (aRound > bRound) {
+ return 1;
+ }
return 0;
});