Added publishing to The Blue Alliance.

This commit is contained in:
Patrick Fairbank
2014-07-30 22:55:14 -07:00
parent 19f8aa7910
commit d77f2288e9
13 changed files with 363 additions and 81 deletions

View File

@@ -16,6 +16,7 @@ func TestAssignTeam(t *testing.T) {
db, err = OpenDatabase(testDbPath)
assert.Nil(t, err)
defer db.Close()
eventSettings, _ = db.GetEventSettings()
team := Team{Id: 254}
err = db.CreateTeam(&team)
assert.Nil(t, err)

View File

@@ -12,9 +12,11 @@ import (
"time"
)
const elimMatchSpacingSec = 600
// Incrementally creates any elimination matches that can be created, based on the results of alliance
// selection or prior elimination rounds. Returns the winning alliance once it has been determined.
func (database *Database) UpdateEliminationSchedule(startTime time.Time, matchSpacingSec int) ([]AllianceTeam, error) {
func (database *Database) UpdateEliminationSchedule(startTime time.Time) ([]AllianceTeam, error) {
alliances, err := database.GetAllAlliances()
if err != nil {
return []AllianceTeam{}, err
@@ -34,7 +36,7 @@ func (database *Database) UpdateEliminationSchedule(startTime time.Time, matchSp
if match.Status == "complete" {
continue
}
match.Time = startTime.Add(time.Duration(matchIndex*matchSpacingSec) * time.Second)
match.Time = startTime.Add(time.Duration(matchIndex*elimMatchSpacingSec) * time.Second)
database.SaveMatch(&match)
matchIndex++
}

View File

@@ -17,7 +17,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
defer db.Close()
createTestAlliances(db, 2)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err := db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -30,7 +30,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 3)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -46,7 +46,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 4)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -62,7 +62,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 5)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -81,7 +81,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 6)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -103,7 +103,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 7)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -125,7 +125,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 8)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -147,7 +147,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 9)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -172,7 +172,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 10)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -200,7 +200,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 11)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -231,7 +231,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 12)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -265,7 +265,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 13)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -299,7 +299,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 14)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -333,7 +333,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 15)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -367,7 +367,7 @@ func TestEliminationScheduleInitial(t *testing.T) {
db.TruncateMatches()
createTestAlliances(db, 16)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -409,14 +409,14 @@ func TestEliminationScheduleErrors(t *testing.T) {
defer db.Close()
createTestAlliances(db, 1)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.NotNil(t, err) {
assert.Equal(t, "Must have at least 2 alliances", err.Error())
}
db.TruncateAllianceTeams()
createTestAlliances(db, 17)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.NotNil(t, err) {
assert.Equal(t, "Round of depth 32 is not supported", err.Error())
}
@@ -426,7 +426,7 @@ func TestEliminationScheduleErrors(t *testing.T) {
db.CreateAllianceTeam(&AllianceTeam{0, 1, 1, 2})
db.CreateAllianceTeam(&AllianceTeam{0, 2, 0, 3})
db.CreateAllianceTeam(&AllianceTeam{0, 2, 1, 4})
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.NotNil(t, err) {
assert.Equal(t, "Alliances must consist of at least 3 teams", err.Error())
}
@@ -442,10 +442,10 @@ func TestEliminationSchedulePopulatePartialMatch(t *testing.T) {
// Final should be updated after semifinal is concluded.
createTestAlliances(db, 3)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "SF2-1", "B")
scoreMatch(db, "SF2-2", "B")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err := db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -460,10 +460,10 @@ func TestEliminationSchedulePopulatePartialMatch(t *testing.T) {
// Final should be generated and populated as both semifinals conclude.
createTestAlliances(db, 4)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "SF2-1", "R")
scoreMatch(db, "SF2-2", "R")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -474,7 +474,7 @@ func TestEliminationSchedulePopulatePartialMatch(t *testing.T) {
}
scoreMatch(db, "SF1-1", "R")
scoreMatch(db, "SF1-2", "R")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
@@ -496,24 +496,24 @@ func TestEliminationScheduleCreateNextRound(t *testing.T) {
defer db.Close()
createTestAlliances(db, 4)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "SF1-1", "B")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, _ := db.GetMatchesByType("elimination")
assert.Equal(t, 6, len(matches))
scoreMatch(db, "SF2-1", "B")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 6, len(matches))
scoreMatch(db, "SF1-2", "B")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 9, len(matches))
scoreMatch(db, "SF2-2", "B")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
matches, _ = db.GetMatchesByType("elimination")
if assert.Equal(t, 9, len(matches)) {
@@ -532,21 +532,21 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
// Round with one tie and a sweep.
createTestAlliances(db, 2)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "F-1", "T")
winner, err := db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err := db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ := db.GetMatchesByType("elimination")
assert.Equal(t, 3, len(matches))
scoreMatch(db, "F-2", "B")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 3, len(matches))
scoreMatch(db, "F-3", "B")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.Nil(t, err) {
if assert.Equal(t, 3, len(winner)) {
assert.Equal(t, 2, winner[0].TeamId)
@@ -560,32 +560,32 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
// Round with one tie and a split.
createTestAlliances(db, 2)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "F-1", "R")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 3, len(matches))
scoreMatch(db, "F-2", "T")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 3, len(matches))
scoreMatch(db, "F-3", "B")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 4, len(matches))
assert.Equal(t, "F-4", matches[3].DisplayName)
scoreMatch(db, "F-4", "T")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
scoreMatch(db, "F-5", "R")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.Nil(t, err) {
if assert.Equal(t, 3, len(winner)) {
assert.Equal(t, 1, winner[0].TeamId)
@@ -597,21 +597,21 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
// Round with two ties.
createTestAlliances(db, 2)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "F-1", "T")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 3, len(matches))
scoreMatch(db, "F-2", "B")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ = db.GetMatchesByType("elimination")
assert.Equal(t, 3, len(matches))
scoreMatch(db, "F-3", "T")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
assert.Nil(t, err)
assert.Empty(t, winner)
matches, _ = db.GetMatchesByType("elimination")
@@ -619,7 +619,7 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
assert.Equal(t, "F-4", matches[3].DisplayName)
assert.Equal(t, "F-5", matches[4].DisplayName)
scoreMatch(db, "F-4", "B")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.Nil(t, err) {
if assert.Equal(t, 3, len(winner)) {
assert.Equal(t, 2, winner[0].TeamId)
@@ -631,19 +631,19 @@ func TestEliminationScheduleDetermineWinner(t *testing.T) {
// Round with repeated ties.
createTestAlliances(db, 2)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "F-1", "T")
scoreMatch(db, "F-2", "T")
scoreMatch(db, "F-3", "T")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "F-4", "T")
scoreMatch(db, "F-5", "T")
scoreMatch(db, "F-6", "T")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "F-7", "R")
scoreMatch(db, "F-8", "B")
scoreMatch(db, "F-9", "R")
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
winner, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.Nil(t, err) {
if assert.Equal(t, 3, len(winner)) {
assert.Equal(t, 1, winner[0].TeamId)
@@ -662,9 +662,9 @@ func TestEliminationScheduleUnscoredMatch(t *testing.T) {
defer db.Close()
createTestAlliances(db, 2)
db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
db.UpdateEliminationSchedule(time.Unix(0, 0))
scoreMatch(db, "F-1", "blorpy")
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0), 0)
_, err = db.UpdateEliminationSchedule(time.Unix(0, 0))
if assert.NotNil(t, err) {
assert.Equal(t, "Completed match 1 has invalid winner 'blorpy'", err.Error())
}
@@ -678,7 +678,7 @@ func TestEliminationScheduleTiming(t *testing.T) {
defer db.Close()
createTestAlliances(db, 4)
db.UpdateEliminationSchedule(time.Unix(1000, 0), 600)
db.UpdateEliminationSchedule(time.Unix(1000, 0))
matches, err := db.GetMatchesByType("elimination")
assert.Nil(t, err)
if assert.Equal(t, 6, len(matches)) {
@@ -691,16 +691,16 @@ func TestEliminationScheduleTiming(t *testing.T) {
}
scoreMatch(db, "SF1-1", "R")
scoreMatch(db, "SF1-3", "B")
db.UpdateEliminationSchedule(time.Unix(5000, 0), 1000)
db.UpdateEliminationSchedule(time.Unix(5000, 0))
matches, err = db.GetMatchesByType("elimination")
assert.Nil(t, err)
if assert.Equal(t, 6, len(matches)) {
assert.True(t, time.Unix(1000, 0).Equal(matches[0].Time))
assert.True(t, time.Unix(5000, 0).Equal(matches[1].Time))
assert.True(t, time.Unix(6000, 0).Equal(matches[2].Time))
assert.True(t, time.Unix(7000, 0).Equal(matches[3].Time))
assert.True(t, time.Unix(5600, 0).Equal(matches[2].Time))
assert.True(t, time.Unix(6200, 0).Equal(matches[3].Time))
assert.True(t, time.Unix(3400, 0).Equal(matches[4].Time))
assert.True(t, time.Unix(8000, 0).Equal(matches[5].Time))
assert.True(t, time.Unix(6800, 0).Equal(matches[5].Time))
}
}

View File

@@ -15,6 +15,7 @@ import (
"sort"
"strconv"
"text/template"
"time"
)
type MatchPlayListItem struct {
@@ -344,10 +345,36 @@ func CommitMatchScore(match *Match, matchResult *MatchResult) error {
return err
}
// Recalculate all the rankings.
err = db.CalculateRankings()
if err != nil {
return err
if match.Type == "qualification" {
// Recalculate all the rankings.
err = db.CalculateRankings()
if err != nil {
return err
}
}
if match.Type == "elimination" {
// Generate any subsequent elimination matches.
_, err = db.UpdateEliminationSchedule(time.Now().Add(time.Second * elimMatchSpacingSec))
if err != nil {
return err
}
}
if eventSettings.TbaPublishingEnabled && match.Type != "practice" {
// Publish asynchronously to The Blue Alliance.
go func() {
err = PublishMatches()
if err != nil {
log.Println(err)
}
if match.Type == "qualification" {
err = PublishRankings()
if err != nil {
log.Println(err)
}
}
}()
}
return nil

View File

@@ -53,6 +53,7 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
db.CreateMatch(&match)
matchResult := buildTestMatchResult(match.Id, 1)
db.CreateMatchResult(&matchResult)
createTestAlliances(db, 2)
recorder := getHttpResponse("/match_review")
assert.Equal(t, 200, recorder.Code)
@@ -96,6 +97,7 @@ func TestMatchReviewCreateNewResult(t *testing.T) {
match := Match{Type: "elimination", DisplayName: "QF4-3", Status: "complete", Winner: "R", Red1: 101,
Red2: 102, Red3: 103, Blue1: 104, Blue2: 105, Blue3: 106}
db.CreateMatch(&match)
createTestAlliances(db, 2)
recorder := getHttpResponse("/match_review")
assert.Equal(t, 200, recorder.Code)

View File

@@ -137,11 +137,6 @@ func AllianceSelectionFinalizeHandler(w http.ResponseWriter, r *http.Request) {
renderAllianceSelection(w, r, "Must specify a valid start time for the elimination rounds.")
return
}
matchSpacingSec, err := strconv.Atoi(r.PostFormValue("matchSpacingSec"))
if err != nil || matchSpacingSec <= 0 {
renderAllianceSelection(w, r, "Must specify a valid match spacing for the elimination rounds.")
return
}
// Check that all spots are filled.
for _, alliance := range cachedAlliances {
@@ -165,12 +160,26 @@ func AllianceSelectionFinalizeHandler(w http.ResponseWriter, r *http.Request) {
}
// Generate the first round of elimination matches.
_, err = db.UpdateEliminationSchedule(startTime, matchSpacingSec)
_, err = db.UpdateEliminationSchedule(startTime)
if err != nil {
handleWebErr(w, err)
return
}
if eventSettings.TbaPublishingEnabled {
// Publish alliances and schedule to The Blue Alliance.
err = PublishAlliances()
if err != nil {
handleWebErr(w, err)
return
}
err = PublishMatches()
if err != nil {
handleWebErr(w, err)
return
}
}
http.Redirect(w, r, "/setup/alliance_selection", 302)
}

View File

@@ -79,8 +79,7 @@ func TestSetupAllianceSelection(t *testing.T) {
assert.Contains(t, recorder.Body.String(), ">110<")
// Finalize alliance selection.
recorder = postHttpResponse("/setup/alliance_selection/finalize",
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=2014-01-01 01:00:00 PM")
assert.Equal(t, 302, recorder.Code)
alliances, err := db.GetAllAlliances()
assert.Nil(t, err)
@@ -135,19 +134,15 @@ func TestSetupAllianceSelectionErrors(t *testing.T) {
recorder = postHttpResponse("/setup/alliance_selection", "selection0_0=101&selection0_1=102&"+
"selection0_2=103&selection1_0=104&selection1_1=105&selection1_2=106")
assert.Equal(t, 302, recorder.Code)
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=asdf&matchSpacingSec=100")
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=asdf")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "valid start time")
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=2014-01-01 01:00:00 PM")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "valid match spacing")
recorder = postHttpResponse("/setup/alliance_selection/finalize",
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
"startTime=2014-01-01 01:00:00 PM")
assert.Equal(t, 302, recorder.Code)
// Do other things after finalization.
recorder = postHttpResponse("/setup/alliance_selection/finalize",
"startTime=2014-01-01 01:00:00 PM&matchSpacingSec=360")
recorder = postHttpResponse("/setup/alliance_selection/finalize", "startTime=2014-01-01 01:00:00 PM")
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Body.String(), "already been finalized")
recorder = postHttpResponse("/setup/alliance_selection/reset", "")

View File

@@ -106,6 +106,16 @@ func ScheduleSavePostHandler(w http.ResponseWriter, r *http.Request) {
return
}
}
if eventSettings.TbaPublishingEnabled && cachedMatchType != "practice" {
// Publish schedule to The Blue Alliance.
err = PublishMatches()
if err != nil {
handleWebErr(w, err)
return
}
}
http.Redirect(w, r, "/setup/schedule", 302)
}

View File

@@ -157,6 +157,16 @@ func TeamDeletePostHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/setup/teams", 302)
}
// Publishes the team list to the web.
func TeamsPublishHandler(w http.ResponseWriter, r *http.Request) {
err := PublishTeams()
if err != nil {
handleWebErr(w, err)
return
}
http.Redirect(w, r, "/setup/teams", 302)
}
func renderTeams(w http.ResponseWriter, r *http.Request, showErrorMessage bool) {
teams, err := db.GetAllTeams()
if err != nil {

204
tba.go Normal file
View File

@@ -0,0 +1,204 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Methods for publishing data to and retrieving data from The Blue Alliance.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
)
var tbaBaseUrl = "http://tbatv-dev-efang.appspot.com"
type TbaMatch struct {
CompLevel string `json:"comp_level"`
SetNumber int `json:"set_number"`
MatchNumber int `json:"match_number"`
Alliances map[string]interface{} `json:"alliances"`
TimeString string `json:"time_string"`
}
type TbaRanking struct {
TeamKey string `json:"team_key"`
Rank int `json:"rank"`
Wins int `json:"wins"`
Losses int `json:"losses"`
Ties int `json:"ties"`
Played int `json:"played"`
Dqs int `json:"dqs"`
QS int
Assist int
Auto int
TrussCatch int `json:"T&C"`
GoalFoul int `json:"G&F"`
}
// Uploads the event team list to The Blue Alliance.
func PublishTeams() error {
teams, err := db.GetAllTeams()
if err != nil {
return err
}
// Build a JSON array of TBA-format team keys (e.g. "frc254").
teamKeys := make([]string, len(teams))
for i, team := range teams {
teamKeys[i] = getTbaTeam(team.Id)
}
jsonBody, err := json.Marshal(teamKeys)
if err != nil {
return err
}
resp, err := http.PostForm(getTbaPostUrl("team_list"), getTbaPostBody("team_list", string(jsonBody)))
if err != nil {
return err
}
if resp.StatusCode != 200 {
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("Got status code %d from TBA: %s", resp.StatusCode, body)
}
return nil
}
// Uploads the qualification and elimination match schedule and results to The Blue Alliance.
func PublishMatches() error {
qualMatches, err := db.GetMatchesByType("qualification")
if err != nil {
return err
}
elimMatches, err := db.GetMatchesByType("elimination")
if err != nil {
return err
}
matches := append(qualMatches, elimMatches...)
tbaMatches := make([]TbaMatch, len(matches))
// Build a JSON array of TBA-format matches.
for i, match := range matches {
matchNumber, _ := strconv.Atoi(match.DisplayName)
redAlliance := map[string]interface{}{"teams": []string{getTbaTeam(match.Red1), getTbaTeam(match.Red2),
getTbaTeam(match.Red3)}, "score": nil}
blueAlliance := map[string]interface{}{"teams": []string{getTbaTeam(match.Blue1), getTbaTeam(match.Blue2),
getTbaTeam(match.Blue3)}, "score": nil}
// Fill in scores if the match has been played.
if match.Status == "complete" {
matchResult, err := db.GetMatchResultForMatch(match.Id)
if err != nil {
return err
}
if matchResult != nil {
redAlliance["score"] = matchResult.RedScoreSummary().Score
blueAlliance["score"] = matchResult.BlueScoreSummary().Score
}
}
tbaMatches[i] = TbaMatch{"qm", 0, matchNumber, map[string]interface{}{"red": redAlliance,
"blue": blueAlliance}, match.Time.Local().Format("3:04 PM")}
if match.Type == "elimination" {
tbaMatches[i].CompLevel = map[int]string{1: "f", 2: "sf", 4: "qf", 8: "ef"}[match.ElimRound]
tbaMatches[i].SetNumber = match.ElimGroup
tbaMatches[i].MatchNumber = match.ElimInstance
}
}
jsonBody, err := json.Marshal(tbaMatches)
if err != nil {
return err
}
resp, err := http.PostForm(getTbaPostUrl("matches"), getTbaPostBody("matches", string(jsonBody)))
if err != nil {
return err
}
if resp.StatusCode != 200 {
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("Got status code %d from TBA: %s", resp.StatusCode, body)
}
return nil
}
// Uploads the team standings to The Blue Alliance.
func PublishRankings() error {
rankings, err := db.GetAllRankings()
if err != nil {
return err
}
// Build a JSON object of TBA-format rankings.
breakdowns := []string{"QS", "Assist", "Auto", "T&C", "G&F"}
tbaRankings := make([]TbaRanking, len(rankings))
for i, ranking := range rankings {
tbaRankings[i] = TbaRanking{getTbaTeam(ranking.TeamId), ranking.Rank, ranking.Wins, ranking.Losses,
ranking.Ties, ranking.Played, ranking.Disqualifications, ranking.QualificationScore,
ranking.AssistPoints, ranking.AutoPoints, ranking.TrussCatchPoints, ranking.GoalFoulPoints}
}
jsonBody, err := json.Marshal(map[string]interface{}{"breakdowns": breakdowns, "rankings": tbaRankings})
if err != nil {
return err
}
resp, err := http.PostForm(getTbaPostUrl("rankings"), getTbaPostBody("rankings", string(jsonBody)))
if err != nil {
return err
}
if resp.StatusCode != 200 {
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("Got status code %d from TBA: %s", resp.StatusCode, body)
}
return nil
}
// Uploads the alliances selection results to The Blue Alliance.
func PublishAlliances() error {
alliances, err := db.GetAllAlliances()
if err != nil {
return err
}
// Build a JSON object of TBA-format alliances.
tbaAlliances := make([][]string, len(alliances))
for i, alliance := range alliances {
for _, team := range alliance {
tbaAlliances[i] = append(tbaAlliances[i], getTbaTeam(team.TeamId))
}
}
jsonBody, err := json.Marshal(tbaAlliances)
if err != nil {
return err
}
resp, err := http.PostForm(getTbaPostUrl("alliance_selections"), getTbaPostBody("alliances", string(jsonBody)))
if err != nil {
return err
}
if resp.StatusCode != 200 {
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("Got status code %d from TBA: %s", resp.StatusCode, body)
}
return nil
}
func getTbaPostUrl(resource string) string {
return fmt.Sprintf("%s/api/trusted/v1/event/%s/%s/update", tbaBaseUrl, eventSettings.TbaEventCode,
resource)
}
func getTbaPostBody(key string, value string) url.Values {
return url.Values{"secret-id": {eventSettings.TbaSecretId}, "secret": {eventSettings.TbaSecret}, key: {value}}
}
// Converts an integer team number into the "frcXXXX" format TBA expects.
func getTbaTeam(team int) string {
return fmt.Sprintf("frc%d", team)
}

View File

@@ -134,12 +134,6 @@
</div>
</div>
</div>
<div class="form-group">
<label class="col-lg-6 control-label">Elimination Round Cycle Time (sec)</label>
<div class="col-lg-6">
<input type="text" class="form-control input-sm" name="matchSpacingSec" value="480" />
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>

View File

@@ -24,6 +24,13 @@
Clear Team List
</button>
</div>
{{if .EventSettings.TbaPublishingEnabled}}
<div class="form-group">
<button type="button" class="btn btn-info" onclick="$('#confirmPublishTeams').modal('show');">
Publish Team List to TBA
</button>
</div>
{{end}}
</fieldset>
</form>
</div>
@@ -87,5 +94,25 @@
</div>
</div>
</div>
<div id="confirmPublishTeams" class="modal" style="top: 20%;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Confirm</h4>
</div>
<div class="modal-body">
<p>Are you sure you want to publish the team list to The Blue Alliance? This will overwrite any
existing team list data.</p>
</div>
<div class="modal-footer">
<form class="form-horizontal" action="/setup/teams/publish" method="POST">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Publish Team List</button>
</form>
</div>
</div>
</div>
</div>
{{end}}
{{define "script"}}{{end}}

1
web.go
View File

@@ -111,6 +111,7 @@ func newHandler() http.Handler {
router.HandleFunc("/setup/teams/{id}/edit", TeamEditGetHandler).Methods("GET")
router.HandleFunc("/setup/teams/{id}/edit", TeamEditPostHandler).Methods("POST")
router.HandleFunc("/setup/teams/{id}/delete", TeamDeletePostHandler).Methods("POST")
router.HandleFunc("/setup/teams/publish", TeamsPublishHandler).Methods("POST")
router.HandleFunc("/setup/schedule", ScheduleGetHandler).Methods("GET")
router.HandleFunc("/setup/schedule/generate", ScheduleGeneratePostHandler).Methods("POST")
router.HandleFunc("/setup/schedule/save", ScheduleSavePostHandler).Methods("POST")