Periodically refresh the match list for scouting
I've had a few instances where students thought that we didn't support
eliminations matches because they don't show up when first importing
the match list. The eliminations matches are only importable after the
alliance selections.
To work around this, the webserver now periodically imports the latest
match list. This means we don't need anyone to manually use the
"Import Match List" tab on the app anymore. I deleted that tab as
part of this patch.
The new `scouting/webserver/match_list/match_list.go` file is largely
just the code that I deleted from
`scouting/webserver/requests/requests.go`. I.e. it's really just
moving code around. The main difference now is that instead of
`requests.go` handling match list imports,
`scouting/webserver/main.go` now starts a background task to scrape
the match list every 10 minutes or so.
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: Ic37df45261f03a323069c1b38c4fd9682a9c4a3e
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index d56f380..fab725c 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -12,10 +12,7 @@
"strings"
"github.com/frc971/971-Robot-Code/scouting/db"
- "github.com/frc971/971-Robot-Code/scouting/scraping"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
- "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list"
- "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
@@ -52,8 +49,6 @@
type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
type RequestDataScouting = request_data_scouting.RequestDataScouting
type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
-type RefreshMatchList = refresh_match_list.RefreshMatchList
-type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
type SubmitNotes = submit_notes.SubmitNotes
type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
@@ -85,8 +80,6 @@
AddDriverRanking(db.DriverRankingData) error
}
-type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
-
// Handles unknown requests. Just returns a 404.
func unknown(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusNotFound)
@@ -417,152 +410,6 @@
w.Write(builder.FinishedBytes())
}
-func parseTeamKey(teamKey string) (int, error) {
- // TBA prefixes teams with "frc". Not sure why. Get rid of that.
- teamKey = strings.TrimPrefix(teamKey, "frc")
- magnitude := 0
- if strings.HasSuffix(teamKey, "A") {
- magnitude = 0
- teamKey = strings.TrimSuffix(teamKey, "A")
- } else if strings.HasSuffix(teamKey, "B") {
- magnitude = 9
- teamKey = strings.TrimSuffix(teamKey, "B")
- } else if strings.HasSuffix(teamKey, "C") {
- magnitude = 8
- teamKey = strings.TrimSuffix(teamKey, "C")
- } else if strings.HasSuffix(teamKey, "D") {
- magnitude = 7
- teamKey = strings.TrimSuffix(teamKey, "D")
- } else if strings.HasSuffix(teamKey, "E") {
- magnitude = 6
- teamKey = strings.TrimSuffix(teamKey, "E")
- } else if strings.HasSuffix(teamKey, "F") {
- magnitude = 5
- teamKey = strings.TrimSuffix(teamKey, "F")
- }
-
- if magnitude != 0 {
- teamKey = strconv.Itoa(magnitude) + teamKey
- }
-
- result, err := strconv.Atoi(teamKey)
- return result, err
-}
-
-// Parses the alliance data from the specified match and returns the three red
-// teams and the three blue teams.
-func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
- redKeys := match.Alliances.Red.TeamKeys
- blueKeys := match.Alliances.Blue.TeamKeys
-
- if len(redKeys) != 3 || len(blueKeys) != 3 {
- return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
- "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
- }
-
- var red [3]int32
- for i, key := range redKeys {
- team, err := parseTeamKey(key)
- if err != nil {
- return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
- "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
- }
- red[i] = int32(team)
- }
- var blue [3]int32
- for i, key := range blueKeys {
- team, err := parseTeamKey(key)
- if err != nil {
- return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
- "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
- }
- blue[i] = int32(team)
- }
- return red, blue, nil
-}
-
-type refreshMatchListHandler struct {
- db Database
- scrape ScrapeMatchList
-}
-
-func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- requestBytes, err := io.ReadAll(req.Body)
- if err != nil {
- respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
- return
- }
-
- request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
- if !success {
- return
- }
-
- matches, err := handler.scrape(request.Year(), string(request.EventCode()))
- if err != nil {
- respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
- return
- }
-
- for _, match := range matches {
- // Make sure the data is valid.
- red, blue, err := parseTeamKeys(&match)
- if err != nil {
- respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
- "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
- return
- }
-
- team_matches := []db.TeamMatch{
- {
- MatchNumber: int32(match.MatchNumber),
- SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "R", AlliancePosition: 1, TeamNumber: red[0],
- },
- {
- MatchNumber: int32(match.MatchNumber),
- SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "R", AlliancePosition: 2, TeamNumber: red[1],
- },
- {
- MatchNumber: int32(match.MatchNumber),
- SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "R", AlliancePosition: 3, TeamNumber: red[2],
- },
- {
- MatchNumber: int32(match.MatchNumber),
- SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "B", AlliancePosition: 1, TeamNumber: blue[0],
- },
- {
- MatchNumber: int32(match.MatchNumber),
- SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "B", AlliancePosition: 2, TeamNumber: blue[1],
- },
- {
- MatchNumber: int32(match.MatchNumber),
- SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "B", AlliancePosition: 3, TeamNumber: blue[2],
- },
- }
-
- for _, match := range team_matches {
- // Iterate through matches to check they can be added to database.
- err = handler.db.AddToMatch(match)
- if err != nil {
- respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
- "Failed to add team %d from match %d to the database: %v", match.TeamNumber, match.MatchNumber, err))
- return
- }
- }
- }
-
- var response RefreshMatchListResponseT
- builder := flatbuffers.NewBuilder(1024)
- builder.Finish((&response).Pack(builder))
- w.Write(builder.FinishedBytes())
-}
-
type submitNoteScoutingHandler struct {
db Database
}
@@ -829,14 +676,13 @@
w.Write(builder.FinishedBytes())
}
-func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
+func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
scoutingServer.HandleFunc("/requests", unknown)
scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
- scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})