1
0
mirror of https://github.com/jcwimer/wrestlingApp synced 2026-03-24 17:04:43 +00:00

Use websockets on stats page to determine which match to stat.

This commit is contained in:
2026-02-23 17:56:40 -05:00
parent 654cb84827
commit ca4d5ce0db
11 changed files with 211 additions and 25 deletions

View File

@@ -1,22 +1,21 @@
class MatsController < ApplicationController class MatsController < ApplicationController
before_action :set_mat, only: [:show, :edit, :update, :destroy, :assign_next_match] before_action :set_mat, only: [:show, :edit, :update, :destroy, :assign_next_match]
before_action :check_access, only: [:new,:create,:update,:destroy,:edit,:show, :assign_next_match] before_action :check_access, only: [:new,:create,:update,:destroy,:edit,:show, :assign_next_match]
before_action :check_for_matches, only: [:show]
# GET /mats/1 # GET /mats/1
# GET /mats/1.json # GET /mats/1.json
def show def show
bout_number_param = params[:bout_number] # Read the bout_number from the URL params bout_number_param = params[:bout_number]
@queue_matches = @mat.queue_matches
if bout_number_param @match = if bout_number_param
@show_next_bout_button = false @queue_matches.compact.find { |m| m.bout_number == bout_number_param.to_i }
@match = @mat.queue_matches.compact.find { |m| m.bout_number == bout_number_param.to_i }
else else
@show_next_bout_button = true @queue_matches[0]
@match = @mat.queue1_match
end end
# If a requested bout is no longer queued, fall back to queue1.
@next_match = @mat.queue2_match # Second match on the mat @match ||= @queue_matches[0]
@next_match = @queue_matches[1]
@show_next_bout_button = false
@wrestlers = [] @wrestlers = []
if @match if @match
@@ -142,11 +141,4 @@ class MatsController < ApplicationController
end end
def check_for_matches
if @mat
if @mat.tournament.matches.empty?
redirect_to "/tournaments/#{@tournament.id}/no_matches"
end
end
end
end end

View File

@@ -184,6 +184,7 @@ class Mat < ApplicationRecord
def clear_queue! def clear_queue!
update!(queue1: nil, queue2: nil, queue3: nil, queue4: nil) update!(queue1: nil, queue2: nil, queue3: nil, queue4: nil)
broadcast_current_match
end end
def unfinished_matches def unfinished_matches

View File

@@ -97,6 +97,7 @@ class Tournament < ApplicationRecord
def destroy_all_matches def destroy_all_matches
matches.destroy_all matches.destroy_all
mats.each(&:clear_queue!)
end end
def matches_by_round(round) def matches_by_round(round)

View File

@@ -14,10 +14,10 @@ class WipeTournamentMatches
end end
def wipeMatches def wipeMatches
@tournament.matches.destroy_all @tournament.destroy_all_matches
end end
def resetSchoolScores def resetSchoolScores
@tournament.schools.update_all("score = 0.0") @tournament.schools.update_all("score = 0.0")
end end
end end

View File

@@ -18,8 +18,19 @@
<div id="cable-status-indicator" data-match-data-target="statusIndicator" class="alert alert-secondary" style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div> <div id="cable-status-indicator" data-match-data-target="statusIndicator" class="alert alert-secondary" style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div>
<h4>Bout <strong><%= @match.bout_number %></strong></h4> <h4>Bout <strong><%= @match.bout_number %></strong></h4>
<% if @show_next_bout_button && @next_match %> <% if @mat %>
<%= link_to "Skip to Next Match for Mat #{@mat.name}", mat_path(@mat, bout_number: @next_match.bout_number), class: "btn btn-primary" %> <% queue_matches = @queue_matches || @mat.queue_matches %>
<div style="margin-bottom: 10px;">
<% queue_matches.each_with_index do |queue_match, index| %>
<% queue_label = "Queue #{index + 1}" %>
<% if queue_match %>
<% button_class = queue_match.id == @match.id ? "btn btn-success btn-sm" : "btn btn-primary btn-sm" %>
<%= link_to "#{queue_label}: Bout #{queue_match.bout_number}", mat_path(@mat, bout_number: queue_match.bout_number), class: button_class %>
<% else %>
<button type="button" class="btn btn-default btn-sm" disabled><%= "#{queue_label}: Not assigned" %></button>
<% end %>
<% end %>
</div>
<% end %> <% end %>
<h4>Bracket Position: <strong><%= @match.bracket_position %></strong></h4> <h4>Bracket Position: <strong><%= @match.bracket_position %></strong></h4>

View File

@@ -1,6 +1,8 @@
<% @mat = mat %> <% @mat = mat %>
<% @match = local_assigns[:match] || mat.queue1_match %> <% @queue_matches = local_assigns[:queue_matches] || mat.queue_matches %>
<% @next_match = local_assigns[:next_match] || mat.queue2_match %> <% @match = local_assigns[:match] || @queue_matches[0] %>
<% @match ||= @queue_matches[0] %>
<% @next_match = local_assigns[:next_match] || @queue_matches[1] %>
<% @show_next_bout_button = local_assigns.key?(:show_next_bout_button) ? local_assigns[:show_next_bout_button] : true %> <% @show_next_bout_button = local_assigns.key?(:show_next_bout_button) ? local_assigns[:show_next_bout_button] : true %>
<% @wrestlers = [] %> <% @wrestlers = [] %>

View File

@@ -259,6 +259,62 @@ class MatsControllerTest < ActionController::TestCase
assert_match /#{bout_number}/, response.body, "The bout_number should be rendered on the page" assert_match /#{bout_number}/, response.body, "The bout_number should be rendered on the page"
end end
test "mat show renders queue buttons for all four queue slots" do
sign_in_owner
available_matches = @tournament.matches.where(mat_id: nil).limit(3).to_a
@mat.assign_match_to_queue!(available_matches[0], 2) if available_matches[0]
@mat.assign_match_to_queue!(available_matches[1], 3) if available_matches[1]
@mat.assign_match_to_queue!(available_matches[2], 4) if available_matches[2]
get :show, params: { id: @mat.id }
assert_response :success
assert_includes response.body, "Queue 1: Bout"
assert_includes response.body, "Queue 2:"
assert_includes response.body, "Queue 3:"
assert_includes response.body, "Queue 4:"
end
test "mat show highlights selected queue button and keeps bout_number links working" do
sign_in_owner
queue2_match = @mat.queue2_match
unless queue2_match
assignable = @tournament.matches.where(mat_id: nil).first
@mat.assign_match_to_queue!(assignable, 2) if assignable
queue2_match = @mat.reload.queue2_match
end
assert queue2_match, "Expected queue2 match to be present"
get :show, params: { id: @mat.id, bout_number: queue2_match.bout_number, foo: "bar" }
assert_response :success
assert_includes response.body, "Queue 2: Bout #{queue2_match.bout_number}"
assert_match(/btn btn-success btn-sm/, response.body)
assert_includes response.body, "bout_number=#{queue2_match.bout_number}"
end
test "mat show falls back to queue1 when requested bout number is not currently queued" do
sign_in_owner
queue1 = @mat.reload.queue1_match
assert queue1, "Expected queue1 to be present"
get :show, params: { id: @mat.id, bout_number: 999999 }
assert_response :success
assert_includes response.body, "Bout <strong>#{queue1.bout_number}</strong>"
end
test "mat show renders no matches assigned when queue is empty" do
sign_in_owner
@mat.clear_queue!
get :show, params: { id: @mat.id }
assert_response :success
assert_includes response.body, "No matches assigned to this mat."
end
test "logged in tournament owner should redirect back to the first unfinished bout on a mat after submitting a match with a bout number param" do test "logged in tournament owner should redirect back to the first unfinished bout on a mat after submitting a match with a bout number param" do
sign_in_owner sign_in_owner
@@ -287,11 +343,12 @@ class MatsControllerTest < ActionController::TestCase
end end
#TESTS THAT NEED MATCHES PUT ABOVE THIS #TESTS THAT NEED MATCHES PUT ABOVE THIS
test "redirect show if no matches" do test "show renders when no matches" do
sign_in_owner sign_in_owner
wipe wipe
show show
no_matches success
assert_includes response.body, "No matches assigned to this mat."
end end
# Assign Next Match Permissions # Assign Next Match Permissions

View File

@@ -50,6 +50,29 @@ class UpMatchesCacheTest < ActionController::TestCase
assert_operator cache_writes(third_events), :>, 0, "Expected queue change to invalidate and rewrite at least one row fragment" assert_operator cache_writes(third_events), :>, 0, "Expected queue change to invalidate and rewrite at least one row fragment"
end end
test "up_matches row fragments hit cache after queue clear rewrite" do
first_events = cache_events_for_up_matches do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(first_events), :>, 0, "Expected initial render to write row fragments"
mat = @tournament.mats.first
clear_events = cache_events_for_up_matches do
mat.clear_queue!
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(clear_events), :>, 0, "Expected queue clear to invalidate and rewrite at least one row fragment"
repeat_events = cache_events_for_up_matches do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_equal 0, cache_writes(repeat_events), "Expected subsequent render after queue clear rewrite to reuse cached row fragments"
assert_operator cache_hits(repeat_events), :>, 0, "Expected cache hits after queue clear rewrite"
end
private private
def cache_events_for_up_matches def cache_events_for_up_matches

View File

@@ -0,0 +1,37 @@
require "test_helper"
class MatStatsLiveUpdatesTest < ActionDispatch::IntegrationTest
include ActionView::RecordIdentifier
test "destroying all matches broadcasts no-match state for mat stats stream" do
create_double_elim_tournament_single_weight_1_6(4)
mat = @tournament.mats.create!(name: "Mat 1")
@tournament.reset_and_fill_bout_board
stream = stream_name_for(mat)
clear_streams(stream)
@tournament.destroy_all_matches
assert_operator broadcasts_for(stream).size, :>, 0
payload = broadcasts_for(stream).last
assert_includes payload, dom_id(mat, :current_match)
assert_includes payload, "No matches assigned to this mat."
end
private
def broadcasts_for(stream)
ActionCable.server.pubsub.broadcasts(stream)
end
def clear_streams(*streams)
ActionCable.server.pubsub.clear
streams.each do |stream|
broadcasts_for(stream).clear
end
end
def stream_name_for(streamable)
Turbo::StreamsChannel.send(:stream_name_from, [streamable])
end
end

View File

@@ -49,6 +49,38 @@ class MatchBroadcastTest < ActiveSupport::TestCase
assert_includes broadcasts_for(stream).last, dom_id(mat, :current_match) assert_includes broadcasts_for(stream).last, dom_id(mat, :current_match)
end end
test "destroy_all_matches clears mats and broadcasts no matches assigned" do
create_double_elim_tournament_single_weight_1_6(4)
mat = @tournament.mats.create!(name: "Mat 1")
@tournament.reset_and_fill_bout_board
stream = stream_name_for(mat)
clear_streams(stream)
@tournament.destroy_all_matches
assert_operator broadcasts_for(stream).size, :>, 0
payload = broadcasts_for(stream).last
assert_includes payload, dom_id(mat, :current_match)
assert_includes payload, "No matches assigned to this mat."
assert_equal [nil, nil, nil, nil], mat.reload.queue_match_ids
end
test "wipe tournament matches service clears mats and broadcasts no matches assigned" do
create_double_elim_tournament_single_weight_1_6(4)
mat = @tournament.mats.create!(name: "Mat 1")
@tournament.reset_and_fill_bout_board
stream = stream_name_for(mat)
clear_streams(stream)
WipeTournamentMatches.new(@tournament).setUpMatchGeneration
assert_operator broadcasts_for(stream).size, :>, 0
payload = broadcasts_for(stream).last
assert_includes payload, dom_id(mat, :current_match)
assert_includes payload, "No matches assigned to this mat."
assert_equal [nil, nil, nil, nil], mat.reload.queue_match_ids
end
private private
def broadcasts_for(stream) def broadcasts_for(stream)

View File

@@ -26,6 +26,21 @@ class UpMatchesBroadcastTest < ActiveSupport::TestCase
assert_up_matches_replace_payload(broadcasts_for(stream).last) assert_up_matches_replace_payload(broadcasts_for(stream).last)
end end
test "mat clear_queue broadcasts up matches board update" do
tournament = tournaments(:one)
mat = mats(:one)
match = matches(:tournament_1_bout_2000)
stream = stream_name_for(tournament)
mat.update!(queue1: match.id)
clear_streams(stream)
mat.clear_queue!
assert_operator broadcasts_for(stream).size, :>, 0
assert_up_matches_replace_payload(broadcasts_for(stream).last)
end
test "match mat assignment change broadcasts up matches board update" do test "match mat assignment change broadcasts up matches board update" do
tournament = tournaments(:one) tournament = tournaments(:one)
mat = mats(:one) mat = mats(:one)
@@ -39,6 +54,21 @@ class UpMatchesBroadcastTest < ActiveSupport::TestCase
assert_up_matches_replace_payload(broadcasts_for(stream).last) assert_up_matches_replace_payload(broadcasts_for(stream).last)
end end
test "match mat unassignment broadcasts up matches board update" do
tournament = tournaments(:one)
mat = mats(:one)
match = matches(:tournament_1_bout_2001)
stream = stream_name_for(tournament)
match.update!(mat_id: mat.id)
clear_streams(stream)
match.update!(mat_id: nil)
assert_operator broadcasts_for(stream).size, :>, 0
assert_up_matches_replace_payload(broadcasts_for(stream).last)
end
test "mat update without queue slot changes does not broadcast up matches board update" do test "mat update without queue slot changes does not broadcast up matches board update" do
tournament = tournaments(:one) tournament = tournaments(:one)
mat = mats(:one) mat = mats(:one)