scouting: Add support for /requests/request/matches_for_team
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I00bf80f2a8ee45db896adf025290e4dad4ba4273
diff --git a/scouting/webserver/requests/BUILD b/scouting/webserver/requests/BUILD
index 14cf97f..2d363fd 100644
--- a/scouting/webserver/requests/BUILD
+++ b/scouting/webserver/requests/BUILD
@@ -11,6 +11,8 @@
"//scouting/webserver/requests/messages:error_response_go_fbs",
"//scouting/webserver/requests/messages:request_all_matches_go_fbs",
"//scouting/webserver/requests/messages:request_all_matches_response_go_fbs",
+ "//scouting/webserver/requests/messages:request_matches_for_team_go_fbs",
+ "//scouting/webserver/requests/messages:request_matches_for_team_response_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
"//scouting/webserver/server",
@@ -29,6 +31,8 @@
"//scouting/webserver/requests/messages:error_response_go_fbs",
"//scouting/webserver/requests/messages:request_all_matches_go_fbs",
"//scouting/webserver/requests/messages:request_all_matches_response_go_fbs",
+ "//scouting/webserver/requests/messages:request_matches_for_team_go_fbs",
+ "//scouting/webserver/requests/messages:request_matches_for_team_response_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
"//scouting/webserver/server",
diff --git a/scouting/webserver/requests/debug/BUILD b/scouting/webserver/requests/debug/BUILD
index 189296d..d56c03e 100644
--- a/scouting/webserver/requests/debug/BUILD
+++ b/scouting/webserver/requests/debug/BUILD
@@ -9,6 +9,7 @@
deps = [
"//scouting/webserver/requests/messages:error_response_go_fbs",
"//scouting/webserver/requests/messages:request_all_matches_response_go_fbs",
+ "//scouting/webserver/requests/messages:request_matches_for_team_response_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
],
)
diff --git a/scouting/webserver/requests/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index 8020c64..6b47ae9 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -78,5 +78,15 @@
self.assertEqual(exit_code, 0)
self.assertIn("{MatchList:[]}", stderr)
+ def test_request_matches_for_team(self):
+ json_path = write_json({
+ "team": 971,
+ })
+ exit_code, _stdout, stderr = run_debug_cli(["-requestMatchesForTeam", json_path])
+
+ # TODO(phil): Actually add some matches here.
+ self.assertEqual(exit_code, 0)
+ self.assertIn("{MatchList:[]}", stderr)
+
if __name__ == "__main__":
unittest.main()
diff --git a/scouting/webserver/requests/debug/cli/main.go b/scouting/webserver/requests/debug/cli/main.go
index 204e541..44ae761 100644
--- a/scouting/webserver/requests/debug/cli/main.go
+++ b/scouting/webserver/requests/debug/cli/main.go
@@ -72,6 +72,8 @@
"If specified, parse the file as a SubmitDataScouting JSON request.")
requestAllMatchesPtr := flag.String("requestAllMatches", "",
"If specified, parse the file as a RequestAllMatches JSON request.")
+ requestMatchesForTeamPtr := flag.String("requestMatchesForTeam", "",
+ "If specified, parse the file as a RequestMatchesForTeam JSON request.")
flag.Parse()
// Handle the actual arguments.
@@ -97,4 +99,15 @@
}
log.Printf("%+v", *response)
}
+ if *requestMatchesForTeamPtr != "" {
+ log.Printf("Sending RequestMatchesForTeam to %s", *addressPtr)
+ binaryRequest := parseJson(
+ "scouting/webserver/requests/messages/request_matches_for_team.fbs",
+ *requestMatchesForTeamPtr)
+ response, err := debug.RequestMatchesForTeam(*addressPtr, binaryRequest)
+ if err != nil {
+ log.Fatal("Failed RequestMatchesForTeam: ", err)
+ }
+ log.Printf("%+v", *response)
+ }
}
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index 5984c7a..6add662 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -10,12 +10,14 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
)
// Use aliases to make the rest of the code more readable.
type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
+type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
// A struct that can be used as an `error`. It contains information about the
// why the server was unhappy and what the corresponding request was.
@@ -99,3 +101,15 @@
response := request_all_matches_response.GetRootAsRequestAllMatchesResponse(responseBytes, 0)
return response.UnPack(), nil
}
+
+// Sends a `RequestMatchesForTeam` message to the server and returns the
+// deserialized response.
+func RequestMatchesForTeam(server string, requestBytes []byte) (*RequestMatchesForTeamResponseT, error) {
+ responseBytes, err := performPost(server+"/requests/request/matches_for_team", requestBytes)
+ if err != nil {
+ return nil, err
+ }
+ log.Printf("Parsing RequestMatchesForTeamResponse")
+ response := request_matches_for_team_response.GetRootAsRequestMatchesForTeamResponse(responseBytes, 0)
+ return response.UnPack(), nil
+}
diff --git a/scouting/webserver/requests/messages/request_matches_for_team.fbs b/scouting/webserver/requests/messages/request_matches_for_team.fbs
index 388533e..dd1b217 100644
--- a/scouting/webserver/requests/messages/request_matches_for_team.fbs
+++ b/scouting/webserver/requests/messages/request_matches_for_team.fbs
@@ -1,7 +1,7 @@
namespace scouting.webserver.requests;
-table QueryMatchList {
+table RequestMatchesForTeam {
team:int (id: 0);
}
-root_type QueryMatchList;
\ No newline at end of file
+root_type RequestMatchesForTeam;
diff --git a/scouting/webserver/requests/messages/request_matches_for_team_response.fbs b/scouting/webserver/requests/messages/request_matches_for_team_response.fbs
index 7345520..cbb1895 100644
--- a/scouting/webserver/requests/messages/request_matches_for_team_response.fbs
+++ b/scouting/webserver/requests/messages/request_matches_for_team_response.fbs
@@ -13,8 +13,8 @@
}
//TODO(Sabina): de-duplicate the Match struct in request_all_matches
-table QueryMatchListResponse {
+table RequestMatchesForTeamResponse {
match_list:[Match] (id:0);
}
-root_type QueryMatchListResponse;
\ No newline at end of file
+root_type RequestMatchesForTeamResponse;
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 0a28ca0..44c0b60 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -9,6 +9,8 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
_ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/server"
@@ -18,6 +20,8 @@
type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
type RequestAllMatches = request_all_matches.RequestAllMatches
type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
+type RequestMatchesForTeam = request_matches_for_team.RequestMatchesForTeam
+type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
// The interface we expect the database abstraction to conform to.
// We use an interface here because it makes unit testing easier.
@@ -26,7 +30,7 @@
AddToStats(db.Stats) error
ReturnMatches() ([]db.Match, error)
ReturnStats() ([]db.Stats, error)
- QueryMatches(int) ([]db.Match, error)
+ QueryMatches(int32) ([]db.Match, error)
QueryStats(int) ([]db.Stats, error)
}
@@ -140,8 +144,64 @@
w.Write(builder.FinishedBytes())
}
+// TODO(phil): Can we turn this into a generic?
+func parseRequestMatchesForTeam(w http.ResponseWriter, buf []byte) (*RequestMatchesForTeam, bool) {
+ success := true
+ defer func() {
+ if r := recover(); r != nil {
+ respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
+ success = false
+ }
+ }()
+ result := request_matches_for_team.GetRootAsRequestMatchesForTeam(buf, 0)
+ return result, success
+}
+
+// Handles a RequestMatchesForTeam request.
+type requestMatchesForTeamHandler struct {
+ db Database
+}
+
+func (handler requestMatchesForTeamHandler) 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 := parseRequestMatchesForTeam(w, requestBytes)
+ if !success {
+ return
+ }
+
+ matches, err := handler.db.QueryMatches(request.Team())
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
+ }
+
+ var response RequestAllMatchesResponseT
+ for _, match := range matches {
+ response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
+ MatchNumber: match.MatchNumber,
+ Round: match.Round,
+ CompLevel: match.CompLevel,
+ R1: match.R1,
+ R2: match.R2,
+ R3: match.R3,
+ B1: match.B1,
+ B2: match.B2,
+ B3: match.B3,
+ })
+ }
+
+ builder := flatbuffers.NewBuilder(50 * 1024)
+ builder.Finish((&response).Pack(builder))
+ w.Write(builder.FinishedBytes())
+}
+
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/matches_for_team", requestMatchesForTeamHandler{db})
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index 26256be..f6a06b9 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -12,6 +12,8 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
_ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/server"
@@ -155,6 +157,55 @@
}
}
+// Validates that we can request the full match list.
+func TestRequestMatchesForTeam(t *testing.T) {
+ db := MockDatabase{
+ matches: []db.Match{
+ {
+ MatchNumber: 1, Round: 1, CompLevel: "qual",
+ R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
+ },
+ {
+ MatchNumber: 2, Round: 1, CompLevel: "qual",
+ R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
+ },
+ },
+ }
+ scoutingServer := server.NewScoutingServer()
+ HandleRequests(&db, scoutingServer)
+ scoutingServer.Start(8080)
+ defer scoutingServer.Stop()
+
+ builder := flatbuffers.NewBuilder(1024)
+ builder.Finish((&request_matches_for_team.RequestMatchesForTeamT{
+ Team: 971,
+ }).Pack(builder))
+
+ response, err := debug.RequestMatchesForTeam("http://localhost:8080", builder.FinishedBytes())
+ if err != nil {
+ t.Fatal("Failed to request all matches: ", err)
+ }
+
+ expected := request_matches_for_team_response.RequestMatchesForTeamResponseT{
+ MatchList: []*request_matches_for_team_response.MatchT{
+ // MatchNumber, Round, CompLevel
+ // R1, R2, R3, B1, B2, B3
+ {
+ 1, 1, "qual",
+ 5, 42, 600, 971, 400, 200,
+ },
+ },
+ }
+ if len(expected.MatchList) != len(response.MatchList) {
+ t.Fatal("Expected ", expected, ", but got ", *response)
+ }
+ for i, match := range expected.MatchList {
+ if !reflect.DeepEqual(*match, *response.MatchList[i]) {
+ t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
+ }
+ }
+}
+
// A mocked database we can use for testing. Add functionality to this as
// needed for your tests.
@@ -178,8 +229,17 @@
return []db.Stats{}, nil
}
-func (database *MockDatabase) QueryMatches(int) ([]db.Match, error) {
- return []db.Match{}, nil
+func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
+ var matches []db.Match
+ for _, match := range database.matches {
+ for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
+ if team == requestedTeam {
+ matches = append(matches, match)
+ break
+ }
+ }
+ }
+ return matches, nil
}
func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {