scouting: Add support /requests/request/all_matches
This patch adds a new endpoint that accepts the `RequestAllMatches`
message. It simply returns the full list of matches that the database
knows about.
I decided to change public `int` members in the `db` module to `int32`
so they match the flatbuffer definition. This makes comparison
simpler.
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I9bb2eed020e2889644f5a122105a232a68f2f4bd
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 12f4153..549dfae 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -12,9 +12,9 @@
}
type Match struct {
- MatchNumber, Round int
+ MatchNumber, Round int32
CompLevel string
- R1, R2, R3, B1, B2, B3 int
+ R1, R2, R3, B1, B2, B3 int32
// Each of these variables holds the matchID of the corresponding Stats row
r1ID, r2ID, r3ID, b1ID, b2ID, b3ID int
}
@@ -74,7 +74,7 @@
return (error_)
}
var rowIds [6]int64
- for i, teamNumber := range []int{m.R1, m.R2, m.R3, m.B1, m.B2, m.B3} {
+ for i, teamNumber := range []int32{m.R1, m.R2, m.R3, m.B1, m.B2, m.B3} {
result, error_ := statement.Exec(teamNumber, m.MatchNumber, 0, 0, 0, 0, 0, 0, 0, 0)
if error_ != nil {
fmt.Println("failed to execute statement 2:", error_)
diff --git a/scouting/webserver/requests/BUILD b/scouting/webserver/requests/BUILD
index 15e4c05..14cf97f 100644
--- a/scouting/webserver/requests/BUILD
+++ b/scouting/webserver/requests/BUILD
@@ -9,6 +9,8 @@
deps = [
"//scouting/db",
"//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:submit_data_scouting_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
"//scouting/webserver/server",
@@ -23,7 +25,10 @@
target_compatible_with = ["@platforms//cpu:x86_64"],
deps = [
"//scouting/db",
+ "//scouting/webserver/requests/debug",
"//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: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 cfeee2f..189296d 100644
--- a/scouting/webserver/requests/debug/BUILD
+++ b/scouting/webserver/requests/debug/BUILD
@@ -8,6 +8,7 @@
visibility = ["//visibility:public"],
deps = [
"//scouting/webserver/requests/messages:error_response_go_fbs",
+ "//scouting/webserver/requests/messages:request_all_matches_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 87c22c0..8020c64 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -1,3 +1,5 @@
+# TODO(phil): Rewrite this in Go.
+
import json
import os
from pathlib import Path
@@ -67,5 +69,14 @@
self.assertEqual(exit_code, 1)
self.assertIn("/requests/submit/data_scouting returned 501 Not Implemented", stderr)
+ def test_request_all_matches(self):
+ # RequestAllMatches has no fields.
+ json_path = write_json({})
+ exit_code, _stdout, stderr = run_debug_cli(["-requestAllMatches", 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 ac24b25..204e541 100644
--- a/scouting/webserver/requests/debug/cli/main.go
+++ b/scouting/webserver/requests/debug/cli/main.go
@@ -50,9 +50,9 @@
// Execute the `flatc` command.
flatcCommand := exec.Command(absFlatcPath, "--binary", absFbsPath, jsonSymlink)
flatcCommand.Dir = dir
- err = flatcCommand.Run()
+ output, err := flatcCommand.CombinedOutput()
if err != nil {
- log.Fatal("Failed to execute flatc: ", err)
+ log.Fatal("Failed to execute flatc: ", err, ": ", string(output))
}
// Read the serialized flatbuffer and return it.
@@ -70,6 +70,8 @@
"The end point where the server is listening.")
submitDataScoutingPtr := flag.String("submitDataScouting", "",
"If specified, parse the file as a SubmitDataScouting JSON request.")
+ requestAllMatchesPtr := flag.String("requestAllMatches", "",
+ "If specified, parse the file as a RequestAllMatches JSON request.")
flag.Parse()
// Handle the actual arguments.
@@ -82,6 +84,17 @@
if err != nil {
log.Fatal("Failed SubmitDataScouting: ", err)
}
- log.Printf("%+v", response)
+ log.Printf("%+v", *response)
+ }
+ if *requestAllMatchesPtr != "" {
+ log.Printf("Sending RequestAllMatches to %s", *addressPtr)
+ binaryRequest := parseJson(
+ "scouting/webserver/requests/messages/request_all_matches.fbs",
+ *requestAllMatchesPtr)
+ response, err := debug.RequestAllMatches(*addressPtr, binaryRequest)
+ if err != nil {
+ log.Fatal("Failed RequestAllMatches: ", err)
+ }
+ log.Printf("%+v", *response)
}
}
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index dd70c1b..5984c7a 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -9,11 +9,13 @@
"net/http"
"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/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
// 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.
@@ -85,3 +87,15 @@
response := submit_data_scouting_response.GetRootAsSubmitDataScoutingResponse(responseBytes, 0)
return response.UnPack(), nil
}
+
+// Sends a `RequestAllMatches` message to the server and returns the
+// deserialized response.
+func RequestAllMatches(server string, requestBytes []byte) (*RequestAllMatchesResponseT, error) {
+ responseBytes, err := performPost(server+"/requests/request/all_matches", requestBytes)
+ if err != nil {
+ return nil, err
+ }
+ log.Printf("Parsing RequestAllMatchesResponse")
+ response := request_all_matches_response.GetRootAsRequestAllMatchesResponse(responseBytes, 0)
+ return response.UnPack(), nil
+}
diff --git a/scouting/webserver/requests/messages/request_all_matches.fbs b/scouting/webserver/requests/messages/request_all_matches.fbs
index 1d4acc2..2ec9aa1 100644
--- a/scouting/webserver/requests/messages/request_all_matches.fbs
+++ b/scouting/webserver/requests/messages/request_all_matches.fbs
@@ -1,6 +1,6 @@
namespace scouting.webserver.requests;
-table RequestMatchList {
+table RequestAllMatches {
}
-root_type RequestMatchList;
\ No newline at end of file
+root_type RequestAllMatches;
diff --git a/scouting/webserver/requests/messages/request_all_matches_response.fbs b/scouting/webserver/requests/messages/request_all_matches_response.fbs
index d4a1658..90401e3 100644
--- a/scouting/webserver/requests/messages/request_all_matches_response.fbs
+++ b/scouting/webserver/requests/messages/request_all_matches_response.fbs
@@ -12,8 +12,8 @@
b3:int (id: 8);
}
-table RequestMatchListResponse {
+table RequestAllMatchesResponse {
match_list:[Match] (id:0);
}
-root_type RequestMatchListResponse;
\ No newline at end of file
+root_type RequestAllMatchesResponse;
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 2462ccb..0a28ca0 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -7,12 +7,18 @@
"github.com/frc971/971-Robot-Code/scouting/db"
"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/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"
flatbuffers "github.com/google/flatbuffers/go"
)
+type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
+type RequestAllMatches = request_all_matches.RequestAllMatches
+type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
+
// The interface we expect the database abstraction to conform to.
// We use an interface here because it makes unit testing easier.
type Database interface {
@@ -43,7 +49,7 @@
}
// TODO(phil): Can we turn this into a generic?
-func parseSubmitDataScouting(w http.ResponseWriter, buf []byte) (*submit_data_scouting.SubmitDataScouting, bool) {
+func parseSubmitDataScouting(w http.ResponseWriter, buf []byte) (*SubmitDataScouting, bool) {
success := true
defer func() {
if r := recover(); r != nil {
@@ -79,7 +85,63 @@
respondNotImplemented(w)
}
+// TODO(phil): Can we turn this into a generic?
+func parseRequestAllMatches(w http.ResponseWriter, buf []byte) (*RequestAllMatches, 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_all_matches.GetRootAsRequestAllMatches(buf, 0)
+ return result, success
+}
+
+// Handles a RequestAllMaches request.
+type requestAllMatchesHandler struct {
+ db Database
+}
+
+func (handler requestAllMatchesHandler) 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
+ }
+
+ _, success := parseRequestAllMatches(w, requestBytes)
+ if !success {
+ return
+ }
+
+ matches, err := handler.db.ReturnMatches()
+ 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})
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index 0125da5..26256be 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -4,10 +4,14 @@
"bytes"
"io"
"net/http"
+ "reflect"
"testing"
"github.com/frc971/971-Robot-Code/scouting/db"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/debug"
"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/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"
@@ -92,10 +96,71 @@
// TODO(phil): Can we use scouting/webserver/requests/debug here?
}
+// Validates that we can request the full match list.
+func TestRequestAllMatches(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,
+ },
+ {
+ MatchNumber: 3, Round: 1, CompLevel: "qual",
+ R1: 7, R2: 44, R3: 602, B1: 973, B2: 402, B3: 202,
+ },
+ },
+ }
+ scoutingServer := server.NewScoutingServer()
+ HandleRequests(&db, scoutingServer)
+ scoutingServer.Start(8080)
+ defer scoutingServer.Stop()
+
+ builder := flatbuffers.NewBuilder(1024)
+ builder.Finish((&request_all_matches.RequestAllMatchesT{}).Pack(builder))
+
+ response, err := debug.RequestAllMatches("http://localhost:8080", builder.FinishedBytes())
+ if err != nil {
+ t.Fatal("Failed to request all matches: ", err)
+ }
+
+ expected := request_all_matches_response.RequestAllMatchesResponseT{
+ MatchList: []*request_all_matches_response.MatchT{
+ // MatchNumber, Round, CompLevel
+ // R1, R2, R3, B1, B2, B3
+ {
+ 1, 1, "qual",
+ 5, 42, 600, 971, 400, 200,
+ },
+ {
+ 2, 1, "qual",
+ 6, 43, 601, 972, 401, 201,
+ },
+ {
+ 3, 1, "qual",
+ 7, 44, 602, 973, 402, 202,
+ },
+ },
+ }
+ 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.
-type MockDatabase struct{}
+type MockDatabase struct {
+ matches []db.Match
+}
func (database *MockDatabase) AddToMatch(db.Match) error {
return nil
@@ -106,7 +171,7 @@
}
func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
- return []db.Match{}, nil
+ return database.matches, nil
}
func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {