Add FTA variant of field monitor with ability to save notes (closes #58).

This commit is contained in:
Patrick Fairbank
2020-04-02 20:06:56 -07:00
parent 30b0679b6e
commit eb64939b20
9 changed files with 161 additions and 14 deletions

View File

@@ -8,12 +8,19 @@ package web
import (
"github.com/Team254/cheesy-arena/model"
"github.com/Team254/cheesy-arena/websocket"
"github.com/mitchellh/mapstructure"
"io"
"log"
"net/http"
)
// Renders the field monitor display.
func (web *Web) fieldMonitorDisplayHandler(w http.ResponseWriter, r *http.Request) {
if !web.enforceDisplayConfiguration(w, r, map[string]string{"reversed": "false"}) {
if r.URL.Query().Get("fta") == "true" && !web.userIsAdmin(w, r) {
return
}
if !web.enforceDisplayConfiguration(w, r, map[string]string{"reversed": "false", "fta": "false"}) {
return
}
@@ -34,6 +41,11 @@ func (web *Web) fieldMonitorDisplayHandler(w http.ResponseWriter, r *http.Reques
// The websocket endpoint for the field monitor display client to receive status updates.
func (web *Web) fieldMonitorDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
isFta := r.URL.Query().Get("fta") == "true"
if isFta && !web.userIsAdmin(w, r) {
return
}
display, err := web.registerDisplay(r)
if err != nil {
handleWebErr(w, err)
@@ -48,7 +60,50 @@ func (web *Web) fieldMonitorDisplayWebsocketHandler(w http.ResponseWriter, r *ht
}
defer ws.Close()
// Subscribe the websocket to the notifiers whose messages will be passed on to the client.
ws.HandleNotifiers(display.Notifier, web.arena.ArenaStatusNotifier, web.arena.EventStatusNotifier,
// Subscribe the websocket to the notifiers whose messages will be passed on to the client, in a separate goroutine.
go ws.HandleNotifiers(display.Notifier, web.arena.ArenaStatusNotifier, web.arena.EventStatusNotifier,
web.arena.ReloadDisplaysNotifier)
// Loop, waiting for commands and responding to them, until the client closes the connection.
for {
command, data, err := ws.Read()
if err != nil {
if err == io.EOF {
// Client has closed the connection; nothing to do here.
return
}
log.Println(err)
return
}
if command == "updateTeamNotes" {
if isFta {
args := struct {
Station string
Notes string
}{}
err = mapstructure.Decode(data, &args)
if err != nil {
ws.WriteError(err.Error())
continue
}
if allianceStation, ok := web.arena.AllianceStations[args.Station]; ok {
if allianceStation.Team != nil {
allianceStation.Team.FtaNotes = args.Notes
if err := web.arena.Database.SaveTeam(allianceStation.Team); err != nil {
ws.WriteError(err.Error())
}
web.arena.ArenaStatusNotifier.Notify()
} else {
ws.WriteError("No team present")
}
} else {
ws.WriteError("Invalid alliance station")
}
} else {
ws.WriteError("Must be in FTA mode to update team notes")
}
}
}
}

View File

@@ -13,17 +13,19 @@ import (
func TestFieldMonitorDisplay(t *testing.T) {
web := setupTestWeb(t)
recorder := web.getHttpResponse("/displays/field_monitor?displayId=1&reversed=false")
recorder := web.getHttpResponse("/displays/field_monitor?displayId=1&fta=true&reversed=false")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "Field Monitor - Untitled Event - Cheesy Arena")
}
func TestFieldMonitorDisplayWebsocket(t *testing.T) {
web := setupTestWeb(t)
assert.Nil(t, web.arena.SubstituteTeam(254, "B1"))
server, wsUrl := web.startTestServer()
defer server.Close()
conn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/displays/field_monitor/websocket?displayId=1", nil)
conn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/displays/field_monitor/websocket?displayId=1&fta=false",
nil)
assert.Nil(t, err)
defer conn.Close()
ws := websocket.NewTestWebsocket(conn)
@@ -32,4 +34,38 @@ func TestFieldMonitorDisplayWebsocket(t *testing.T) {
readWebsocketType(t, ws, "displayConfiguration")
readWebsocketType(t, ws, "arenaStatus")
readWebsocketType(t, ws, "eventStatus")
// Should not be able to update team notes.
ws.Write("updateTeamNotes", map[string]interface{}{"station": "B1", "notes": "Bypassed in M1"})
assert.Contains(t, readWebsocketError(t, ws), "Must be in FTA mode to update team notes")
assert.Equal(t, "", web.arena.AllianceStations["B1"].Team.FtaNotes)
}
func TestFieldMonitorFtaDisplayWebsocket(t *testing.T) {
web := setupTestWeb(t)
assert.Nil(t, web.arena.SubstituteTeam(254, "B1"))
server, wsUrl := web.startTestServer()
defer server.Close()
conn, _, err := gorillawebsocket.DefaultDialer.Dial(wsUrl+"/displays/field_monitor/websocket?displayId=1&fta=true",
nil)
assert.Nil(t, err)
defer conn.Close()
ws := websocket.NewTestWebsocket(conn)
// Should get a few status updates right after connection.
readWebsocketType(t, ws, "displayConfiguration")
readWebsocketType(t, ws, "arenaStatus")
readWebsocketType(t, ws, "eventStatus")
// Should not be able to update team notes.
ws.Write("updateTeamNotes", map[string]interface{}{"station": "B1", "notes": "Bypassed in M1"})
readWebsocketType(t, ws, "arenaStatus")
assert.Equal(t, "Bypassed in M1", web.arena.AllianceStations["B1"].Team.FtaNotes)
// Check error scenarios.
ws.Write("updateTeamNotes", map[string]interface{}{"station": "N", "notes": "Bypassed in M2"})
assert.Contains(t, readWebsocketError(t, ws), "Invalid alliance station")
ws.Write("updateTeamNotes", map[string]interface{}{"station": "R3", "notes": "Bypassed in M3"})
assert.Contains(t, readWebsocketError(t, ws), "No team present")
}