feat: track team action list completion
This commit is contained in:
parent
164b7df625
commit
7d65e4df82
|
|
@ -65,9 +65,12 @@ class StagesController < ApplicationController
|
||||||
raise ActiveRecord::Rollback
|
raise ActiveRecord::Rollback
|
||||||
end
|
end
|
||||||
|
|
||||||
return if AddPlayoffsToTournamentAndSave.call(tournament: @stage.tournament,
|
return unless AddPlayoffsToTournamentAndSave.call(tournament: @stage.tournament,
|
||||||
teams: GroupStageService.get_advancing_teams(@stage)).success?
|
teams: GroupStageService.get_advancing_teams(@stage)).success?
|
||||||
|
|
||||||
|
TeamActionListService.ensure_group_stage_survivor_playoff_token_list!(@stage.tournament)
|
||||||
|
return
|
||||||
|
|
||||||
render json: { error: 'Generating group stage failed' }, status: :unprocessable_entity
|
render json: { error: 'Generating group stage failed' }, status: :unprocessable_entity
|
||||||
raise ActiveRecord::Rollback
|
raise ActiveRecord::Rollback
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TeamActionItemsController < ApplicationController
|
||||||
|
before_action :set_team_action_item, only: %i[update]
|
||||||
|
before_action :authenticate_user!, only: %i[update]
|
||||||
|
before_action -> { require_owner! @team_action_item.owner }, only: %i[update]
|
||||||
|
before_action -> { require_writable_tournament!(@team_action_item.tournament) }, only: %i[update]
|
||||||
|
|
||||||
|
# PATCH /team_action_items/1
|
||||||
|
def update
|
||||||
|
if @team_action_item.update(team_action_item_params)
|
||||||
|
render json: @team_action_item
|
||||||
|
else
|
||||||
|
render json: @team_action_item.errors, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_team_action_item
|
||||||
|
@team_action_item = TeamActionItem.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def team_action_item_params
|
||||||
|
params.slice(:completed).permit(:completed)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -5,6 +5,7 @@ class Team < ApplicationRecord
|
||||||
has_many :group_scores, dependent: :destroy
|
has_many :group_scores, dependent: :destroy
|
||||||
has_many :match_scores, dependent: :destroy
|
has_many :match_scores, dependent: :destroy
|
||||||
has_many :bets, dependent: :destroy
|
has_many :bets, dependent: :destroy
|
||||||
|
has_many :team_action_items, dependent: :destroy
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TeamActionItem < ApplicationRecord
|
||||||
|
belongs_to :team_action_list
|
||||||
|
belongs_to :team
|
||||||
|
|
||||||
|
validates :team_id, uniqueness: { scope: :team_action_list_id }
|
||||||
|
|
||||||
|
delegate :tournament, :owner, to: :team_action_list
|
||||||
|
|
||||||
|
before_validation :normalize_completed_at
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def normalize_completed_at
|
||||||
|
self.completed_at = completed? ? (completed_at || Time.current) : nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TeamActionList < ApplicationRecord
|
||||||
|
belongs_to :tournament
|
||||||
|
has_many :team_action_items, dependent: :destroy
|
||||||
|
|
||||||
|
validates :name, :key, :action_name, :source, presence: true
|
||||||
|
validates :key, uniqueness: { scope: :tournament_id }
|
||||||
|
|
||||||
|
delegate :owner, to: :tournament
|
||||||
|
|
||||||
|
def sync_teams!(teams)
|
||||||
|
desired_team_ids = teams.map(&:id).uniq
|
||||||
|
|
||||||
|
transaction do
|
||||||
|
team_action_items.where.not(team_id: desired_team_ids).destroy_all
|
||||||
|
|
||||||
|
desired_team_ids.each do |team_id|
|
||||||
|
team_action_items.find_or_create_by!(team_id: team_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -7,6 +7,7 @@ class Tournament < ApplicationRecord
|
||||||
has_many :teams, dependent: :destroy
|
has_many :teams, dependent: :destroy
|
||||||
has_many :stages, dependent: :destroy
|
has_many :stages, dependent: :destroy
|
||||||
has_many :beamers, dependent: :destroy
|
has_many :beamers, dependent: :destroy
|
||||||
|
has_many :team_action_lists, dependent: :destroy
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :code, presence: true, uniqueness: true
|
validates :code, presence: true, uniqueness: true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TeamActionItemSerializer < ApplicationSerializer
|
||||||
|
attributes :completed, :completed_at, :team
|
||||||
|
|
||||||
|
attribute :completed_at do
|
||||||
|
object.completed_at&.iso8601
|
||||||
|
end
|
||||||
|
|
||||||
|
attribute :team do
|
||||||
|
{
|
||||||
|
id: object.team.id,
|
||||||
|
name: object.team.name
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TeamActionListSerializer < ApplicationSerializer
|
||||||
|
attributes :name, :key, :action_name, :source
|
||||||
|
|
||||||
|
has_many :team_action_items
|
||||||
|
end
|
||||||
|
|
@ -9,6 +9,7 @@ class TournamentSerializer < SimpleTournamentSerializer
|
||||||
# NEVER expose sync_auth_token anywhere - it should only ever be written to or checked against
|
# NEVER expose sync_auth_token anywhere - it should only ever be written to or checked against
|
||||||
|
|
||||||
has_many :stages
|
has_many :stages
|
||||||
|
has_many :team_action_lists
|
||||||
|
|
||||||
attribute :timer_end do
|
attribute :timer_end do
|
||||||
object.timer_end&.iso8601
|
object.timer_end&.iso8601
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TeamActionListService
|
||||||
|
GROUP_STAGE_SURVIVOR_PLAYOFF_TOKEN_KEY = 'group_stage_survivor_playoff_tokens'
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def ensure_group_stage_survivor_playoff_token_list!(tournament)
|
||||||
|
group_stage = tournament.group_stage
|
||||||
|
return nil unless group_stage&.finished?
|
||||||
|
|
||||||
|
teams = tournament.teams_advancing_from_group_stage
|
||||||
|
return nil if teams.empty?
|
||||||
|
|
||||||
|
ensure_list!(
|
||||||
|
tournament: tournament,
|
||||||
|
key: GROUP_STAGE_SURVIVOR_PLAYOFF_TOKEN_KEY,
|
||||||
|
name: 'Playoff token collection',
|
||||||
|
action_name: 'Collect playoff token',
|
||||||
|
source: 'group_stage_survivors',
|
||||||
|
teams: teams
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_list!(tournament:, key:, name:, action_name:, source:, teams:)
|
||||||
|
list = tournament.team_action_lists.find_or_initialize_by(key: key)
|
||||||
|
list.name = name
|
||||||
|
list.action_name = action_name
|
||||||
|
list.source = source
|
||||||
|
list.save!
|
||||||
|
list.sync_teams!(teams)
|
||||||
|
list
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -9,6 +9,8 @@ class TournamentSnapshotBuilder
|
||||||
MATCH_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(Match).map(&:to_sym).freeze
|
MATCH_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(Match).map(&:to_sym).freeze
|
||||||
MATCH_SCORE_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(MatchScore).map(&:to_sym).freeze
|
MATCH_SCORE_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(MatchScore).map(&:to_sym).freeze
|
||||||
GROUP_SCORE_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(GroupScore).map(&:to_sym).freeze
|
GROUP_SCORE_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(GroupScore).map(&:to_sym).freeze
|
||||||
|
TEAM_ACTION_LIST_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(TeamActionList).map(&:to_sym).freeze
|
||||||
|
TEAM_ACTION_ITEM_ATTRIBUTES = TournamentSyncSchema.synced_columns_for(TeamActionItem).map(&:to_sym).freeze
|
||||||
|
|
||||||
def self.build(tournament)
|
def self.build(tournament)
|
||||||
new(tournament).build
|
new(tournament).build
|
||||||
|
|
@ -29,6 +31,8 @@ class TournamentSnapshotBuilder
|
||||||
payload[:matches] = matches_payload
|
payload[:matches] = matches_payload
|
||||||
payload[:match_scores] = match_scores_payload
|
payload[:match_scores] = match_scores_payload
|
||||||
payload[:group_scores] = group_scores_payload
|
payload[:group_scores] = group_scores_payload
|
||||||
|
payload[:team_action_lists] = team_action_lists_payload
|
||||||
|
payload[:team_action_items] = team_action_items_payload
|
||||||
payload
|
payload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -78,6 +82,20 @@ class TournamentSnapshotBuilder
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def team_action_lists_payload
|
||||||
|
tournament.team_action_lists.order(:id).map do |team_action_list|
|
||||||
|
team_action_list.attributes.symbolize_keys.slice(*TEAM_ACTION_LIST_ATTRIBUTES)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def team_action_items_payload
|
||||||
|
tournament.team_action_lists.flat_map(&:team_action_items).sort_by(&:id).map do |team_action_item|
|
||||||
|
attrs = team_action_item.attributes.symbolize_keys.slice(*TEAM_ACTION_ITEM_ATTRIBUTES)
|
||||||
|
attrs[:completed_at] = team_action_item.completed_at&.iso8601
|
||||||
|
attrs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def teams_for_snapshot
|
def teams_for_snapshot
|
||||||
(
|
(
|
||||||
tournament.teams.to_a +
|
tournament.teams.to_a +
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ class TournamentSnapshotImporter
|
||||||
existing_matches = tournament.matches
|
existing_matches = tournament.matches
|
||||||
existing_match_scores = existing_matches.flat_map(&:match_scores)
|
existing_match_scores = existing_matches.flat_map(&:match_scores)
|
||||||
existing_group_scores = existing_groups.flat_map(&:group_scores)
|
existing_group_scores = existing_groups.flat_map(&:group_scores)
|
||||||
|
existing_team_action_lists = tournament.team_action_lists.to_a
|
||||||
|
existing_team_action_items = existing_team_action_lists.flat_map(&:team_action_items)
|
||||||
|
|
||||||
update_tournament!
|
update_tournament!
|
||||||
teams_by_source_id = sync_teams!
|
teams_by_source_id = sync_teams!
|
||||||
|
|
@ -41,7 +43,11 @@ class TournamentSnapshotImporter
|
||||||
matches_by_source_id = sync_matches!(stages_by_source_id, groups_by_source_id)
|
matches_by_source_id = sync_matches!(stages_by_source_id, groups_by_source_id)
|
||||||
sync_match_scores!(matches_by_source_id, teams_by_source_id)
|
sync_match_scores!(matches_by_source_id, teams_by_source_id)
|
||||||
sync_group_scores!(groups_by_source_id, teams_by_source_id)
|
sync_group_scores!(groups_by_source_id, teams_by_source_id)
|
||||||
|
team_action_lists_by_source_id = sync_team_action_lists!
|
||||||
|
sync_team_action_items!(team_action_lists_by_source_id, teams_by_source_id)
|
||||||
|
|
||||||
|
destroy_stale_records(existing_team_action_items, snapshot.fetch(:team_action_items, []))
|
||||||
|
destroy_stale_records(existing_team_action_lists, snapshot.fetch(:team_action_lists, []))
|
||||||
destroy_stale_records(existing_beamers, snapshot.fetch(:beamers, []))
|
destroy_stale_records(existing_beamers, snapshot.fetch(:beamers, []))
|
||||||
destroy_stale_records(existing_group_scores, snapshot.fetch(:group_scores, []))
|
destroy_stale_records(existing_group_scores, snapshot.fetch(:group_scores, []))
|
||||||
destroy_stale_records(existing_match_scores, snapshot.fetch(:match_scores, []))
|
destroy_stale_records(existing_match_scores, snapshot.fetch(:match_scores, []))
|
||||||
|
|
@ -162,6 +168,33 @@ class TournamentSnapshotImporter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sync_team_action_lists!
|
||||||
|
sync_records(
|
||||||
|
model_class: TeamActionList,
|
||||||
|
existing_records: tournament.team_action_lists.to_a,
|
||||||
|
payload: snapshot.fetch(:team_action_lists, [])
|
||||||
|
) do |team_action_list, attrs|
|
||||||
|
team_action_list.tournament = tournament
|
||||||
|
team_action_list.name = attrs.fetch(:name)
|
||||||
|
team_action_list.key = attrs.fetch(:key)
|
||||||
|
team_action_list.action_name = attrs.fetch(:action_name)
|
||||||
|
team_action_list.source = attrs.fetch(:source)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_team_action_items!(team_action_lists_by_source_id, teams_by_source_id)
|
||||||
|
sync_records(
|
||||||
|
model_class: TeamActionItem,
|
||||||
|
existing_records: tournament.team_action_lists.flat_map(&:team_action_items),
|
||||||
|
payload: snapshot.fetch(:team_action_items, [])
|
||||||
|
) do |team_action_item, attrs|
|
||||||
|
team_action_item.team_action_list = team_action_lists_by_source_id.fetch(attrs.fetch(:team_action_list_id))
|
||||||
|
team_action_item.team = teams_by_source_id.fetch(attrs.fetch(:team_id))
|
||||||
|
team_action_item.completed = attrs.fetch(:completed)
|
||||||
|
team_action_item.completed_at = parse_time(attrs[:completed_at])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def sync_records(model_class:, existing_records:, payload:)
|
def sync_records(model_class:, existing_records:, payload:)
|
||||||
existing_by_source_id = existing_records.index_by(&:sync_source_id)
|
existing_by_source_id = existing_records.index_by(&:sync_source_id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,14 @@ class TournamentSyncSchema
|
||||||
GroupScore => {
|
GroupScore => {
|
||||||
synced: %w[id group_points scored_points received_points team_id group_id position].freeze,
|
synced: %w[id group_points scored_points received_points team_id group_id position].freeze,
|
||||||
ignored: %w[created_at updated_at sync_source_id].freeze
|
ignored: %w[created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze,
|
||||||
|
TeamActionList => {
|
||||||
|
synced: %w[id name key action_name source].freeze,
|
||||||
|
ignored: %w[tournament_id created_at updated_at sync_source_id].freeze
|
||||||
|
}.freeze,
|
||||||
|
TeamActionItem => {
|
||||||
|
synced: %w[id team_action_list_id team_id completed completed_at].freeze,
|
||||||
|
ignored: %w[created_at updated_at sync_source_id].freeze
|
||||||
}.freeze
|
}.freeze
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
resources :stages, only: %i[show update]
|
resources :stages, only: %i[show update]
|
||||||
resources :teams, only: %i[show update]
|
resources :teams, only: %i[show update]
|
||||||
|
resources :team_action_items, only: %i[update]
|
||||||
resources :tournaments do
|
resources :tournaments do
|
||||||
resources :statistics, only: %i[index]
|
resources :statistics, only: %i[index]
|
||||||
resources :matches, only: %i[index]
|
resources :matches, only: %i[index]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateTeamActionLists < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :team_action_lists do |t|
|
||||||
|
t.references :tournament, null: false, foreign_key: { on_delete: :cascade }, index: true
|
||||||
|
t.string :name, null: false
|
||||||
|
t.string :key, null: false
|
||||||
|
t.string :action_name, null: false
|
||||||
|
t.string :source, null: false
|
||||||
|
t.bigint :sync_source_id, index: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :team_action_lists, [:tournament_id, :key], unique: true
|
||||||
|
|
||||||
|
create_table :team_action_items do |t|
|
||||||
|
t.references :team_action_list, null: false, foreign_key: { on_delete: :cascade }, index: true
|
||||||
|
t.references :team, null: false, foreign_key: { on_delete: :cascade }, index: true
|
||||||
|
t.boolean :completed, null: false, default: false
|
||||||
|
t.datetime :completed_at
|
||||||
|
t.bigint :sync_source_id, index: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :team_action_items, [:team_action_list_id, :team_id], unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -91,6 +91,16 @@ RSpec.describe StagesController, type: :controller do
|
||||||
expect(running_group_stage.tournament.reload.stages.max_by(&:level).teams)
|
expect(running_group_stage.tournament.reload.stages.max_by(&:level).teams)
|
||||||
.to match_array(GroupStageService.get_advancing_teams(running_group_stage))
|
.to match_array(GroupStageService.get_advancing_teams(running_group_stage))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates playoff token collection list for advancing teams' do
|
||||||
|
tournament = running_group_stage.tournament.reload
|
||||||
|
list = tournament.team_action_lists.find_by!(key: TeamActionListService::GROUP_STAGE_SURVIVOR_PLAYOFF_TOKEN_KEY)
|
||||||
|
|
||||||
|
expect(list.name).to eq('Playoff token collection')
|
||||||
|
expect(list.team_action_items.includes(:team).map(&:team))
|
||||||
|
.to match_array(GroupStageService.get_advancing_teams(running_group_stage))
|
||||||
|
expect(list.team_action_items.pluck(:completed).uniq).to eq([false])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'as another user' do
|
context 'as another user' do
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe TeamActionItemsController, type: :controller do
|
||||||
|
let(:team_action_item) { create(:team_action_item) }
|
||||||
|
|
||||||
|
describe 'PATCH #update' do
|
||||||
|
context 'as owner' do
|
||||||
|
before do
|
||||||
|
apply_authentication_headers_for team_action_item.owner
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ticks item off' do
|
||||||
|
patch :update, params: { id: team_action_item.to_param, completed: true }
|
||||||
|
|
||||||
|
expect(response).to be_successful
|
||||||
|
expect(team_action_item.reload.completed).to eq(true)
|
||||||
|
expect(team_action_item.completed_at).not_to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can untick item' do
|
||||||
|
team_action_item.update!(completed: true, completed_at: Time.zone.parse('2026-04-22T09:00:00Z'))
|
||||||
|
|
||||||
|
patch :update, params: { id: team_action_item.to_param, completed: false }
|
||||||
|
|
||||||
|
expect(response).to be_successful
|
||||||
|
expect(team_action_item.reload.completed).to eq(false)
|
||||||
|
expect(team_action_item.completed_at).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as another user' do
|
||||||
|
before do
|
||||||
|
apply_authentication_headers_for create(:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns forbidden' do
|
||||||
|
patch :update, params: { id: team_action_item.to_param, completed: true }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -105,7 +105,7 @@ RSpec.describe TournamentsController, type: :controller do
|
||||||
expected_keys = %i[
|
expected_keys = %i[
|
||||||
id name code public description playoff_teams_amount
|
id name code public description playoff_teams_amount
|
||||||
instant_finalists_amount intermediate_round_participants_amount
|
instant_finalists_amount intermediate_round_participants_amount
|
||||||
timer_end owner_username stages teams
|
timer_end owner_username stages teams team_action_lists
|
||||||
]
|
]
|
||||||
expect(json.keys).to match_array(expected_keys)
|
expect(json.keys).to match_array(expected_keys)
|
||||||
expect(json[:name]).to eq(@tournament.name)
|
expect(json[:name]).to eq(@tournament.name)
|
||||||
|
|
@ -204,6 +204,19 @@ RSpec.describe TournamentsController, type: :controller do
|
||||||
advancing_teams = advancing_teams.map { |team| Team.find(team[:id]) }
|
advancing_teams = advancing_teams.map { |team| Team.find(team[:id]) }
|
||||||
expect(advancing_teams).to match_array(expected_advancing_teams)
|
expect(advancing_teams).to match_array(expected_advancing_teams)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes team action lists and item status for all viewers' do
|
||||||
|
list = create(:team_action_list, tournament: @group_stage_tournament)
|
||||||
|
create(:team_action_item, team_action_list: list, team: @group_stage_tournament.teams.first, completed: true)
|
||||||
|
allow(GroupStageService).to receive(:get_advancing_teams).and_return([])
|
||||||
|
|
||||||
|
get :show, params: { id: @group_stage_tournament.to_param }
|
||||||
|
body = deserialize_response response
|
||||||
|
|
||||||
|
expect(body[:team_action_lists].size).to eq(1)
|
||||||
|
expect(body[:team_action_lists].first[:key]).to eq('group_stage_survivor_playoff_tokens')
|
||||||
|
expect(body[:team_action_lists].first[:team_action_items].first[:completed]).to eq(true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :team_action_item do
|
||||||
|
team_action_list
|
||||||
|
team { create(:team, tournament: team_action_list.tournament) }
|
||||||
|
completed { false }
|
||||||
|
completed_at { nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :team_action_list do
|
||||||
|
tournament
|
||||||
|
name { 'Playoff token collection' }
|
||||||
|
key { 'group_stage_survivor_playoff_tokens' }
|
||||||
|
action_name { 'Collect playoff token' }
|
||||||
|
source { 'group_stage_survivors' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe TeamActionItemsController, type: :routing do
|
||||||
|
describe 'routing' do
|
||||||
|
it 'routes to #update via PATCH' do
|
||||||
|
expect(patch: '/team_action_items/1').to route_to('team_action_items#update', id: '1')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -7,6 +7,8 @@ RSpec.describe TournamentSnapshotImporter do
|
||||||
it 'replaces follower tournament graph using source ids as stable mapping keys' do
|
it 'replaces follower tournament graph using source ids as stable mapping keys' do
|
||||||
source = create(:group_stage_tournament, group_count: 2, match_factory: :filled_group_match)
|
source = create(:group_stage_tournament, group_count: 2, match_factory: :filled_group_match)
|
||||||
source.beamers.create!(name: 'stream-a', display_state: 'during_game', qr_code_url: 'https://example.com/qr')
|
source.beamers.create!(name: 'stream-a', display_state: 'during_game', qr_code_url: 'https://example.com/qr')
|
||||||
|
list = create(:team_action_list, tournament: source)
|
||||||
|
create(:team_action_item, team_action_list: list, team: source.teams.first, completed: true)
|
||||||
follower = create(:tournament, read_only_mode: true, sync_auth_token: 'shared-secret')
|
follower = create(:tournament, read_only_mode: true, sync_auth_token: 'shared-secret')
|
||||||
|
|
||||||
snapshot = TournamentSnapshotBuilder.build(source)
|
snapshot = TournamentSnapshotBuilder.build(source)
|
||||||
|
|
@ -18,6 +20,8 @@ RSpec.describe TournamentSnapshotImporter do
|
||||||
expect(follower.name).to eq(source.name)
|
expect(follower.name).to eq(source.name)
|
||||||
expect(follower.teams.count).to eq(snapshot.fetch(:teams).count)
|
expect(follower.teams.count).to eq(snapshot.fetch(:teams).count)
|
||||||
expect(follower.beamers.count).to eq(snapshot.fetch(:beamers).count)
|
expect(follower.beamers.count).to eq(snapshot.fetch(:beamers).count)
|
||||||
|
expect(follower.team_action_lists.count).to eq(snapshot.fetch(:team_action_lists).count)
|
||||||
|
expect(follower.team_action_lists.first.team_action_items.count).to eq(snapshot.fetch(:team_action_items).count)
|
||||||
expect(follower.stages.count).to eq(source.stages.count)
|
expect(follower.stages.count).to eq(source.stages.count)
|
||||||
expect(follower.matches.count).to eq(source.matches.count)
|
expect(follower.matches.count).to eq(source.matches.count)
|
||||||
expect(follower.teams.pluck(:sync_source_id)).to match_array(snapshot.fetch(:teams).map { |team| team.fetch(:id) })
|
expect(follower.teams.pluck(:sync_source_id)).to match_array(snapshot.fetch(:teams).map { |team| team.fetch(:id) })
|
||||||
|
|
@ -180,6 +184,21 @@ RSpec.describe TournamentSnapshotImporter do
|
||||||
intermediate_round_participants_amount: tournament.intermediate_round_participants_amount,
|
intermediate_round_participants_amount: tournament.intermediate_round_participants_amount,
|
||||||
timer_end: tournament.timer_end&.iso8601,
|
timer_end: tournament.timer_end&.iso8601,
|
||||||
teams: tournament.teams.order(:name).map { |team| team.name },
|
teams: tournament.teams.order(:name).map { |team| team.name },
|
||||||
|
team_action_lists: tournament.team_action_lists.order(:key).map do |list|
|
||||||
|
{
|
||||||
|
key: list.key,
|
||||||
|
name: list.name,
|
||||||
|
action_name: list.action_name,
|
||||||
|
source: list.source,
|
||||||
|
items: list.team_action_items.includes(:team).sort_by { |item| item.team.name }.map do |item|
|
||||||
|
{
|
||||||
|
team_name: item.team.name,
|
||||||
|
completed: item.completed,
|
||||||
|
completed_at: item.completed_at&.iso8601
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end,
|
||||||
beamers: tournament.beamers.order(:name).map do |beamer|
|
beamers: tournament.beamers.order(:name).map do |beamer|
|
||||||
beamer.slice(:name, :display_state, :is_default, :qr_code_url, :logo_url).symbolize_keys
|
beamer.slice(:name, :display_state, :is_default, :qr_code_url, :logo_url).symbolize_keys
|
||||||
end,
|
end,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue