Skip to content

Commit

Permalink
Fix handling of available inputs (#993)
Browse files Browse the repository at this point in the history
  • Loading branch information
evroon authored Nov 6, 2024
1 parent 7d68f35 commit cfb74c3
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 58 deletions.
63 changes: 34 additions & 29 deletions backend/bracket/logic/scheduling/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from bracket.models.db.util import StageWithStageItems
from bracket.sql.rounds import get_next_round_name, sql_create_round
from bracket.sql.stage_items import get_stage_item
from bracket.utils.id_types import StageId, TournamentId
from bracket.utils.id_types import StageId, StageItemId, TournamentId
from tests.integration_tests.mocks import MOCK_NOW


Expand Down Expand Up @@ -71,10 +71,9 @@ async def build_matches_for_stage_item(stage_item: StageItem, tournament_id: Tou


def determine_available_inputs(
stage_id: StageId,
teams: list[FullTeamWithPlayers],
stages: list[StageWithStageItems],
) -> list[StageItemInputOptionTentative | StageItemInputOptionFinal]:
) -> dict[StageId, list[StageItemInputOptionTentative | StageItemInputOptionFinal]]:
"""
Returns available inputs for the given stage.
Expand All @@ -83,35 +82,41 @@ def determine_available_inputs(
- Previous ROUND_ROBIN stage items
"""
results_team_ids = {team.id: False for team in teams}
results_tentative = []
results_tentative: dict[tuple[StageItemId, int], StageItemInputOptionTentative] = {}
results = {}

for stage in stages:
# First, set options that are used in this round to have `already_taken=True`
for stage_item in stage.stage_items:
item_team_id_inputs = [
input.team_id for input in stage_item.inputs if input.team_id is not None
]
for input_ in item_team_id_inputs:
if input_ in results_team_ids:
if stage_id != stage.id:
results_team_ids.pop(input_)
else:
results_team_ids[input_] = True

if stage_id == stage.id:
break

for input_ in stage_item.inputs:
if input_.team_id is not None and input_.team_id in results_team_ids:
results_team_ids[input_.team_id] = True

if (
input_.winner_from_stage_item_id is not None
and input_.winner_position is not None
and (key := (input_.winner_from_stage_item_id, input_.winner_position))
in results_tentative
):
results_tentative[key].already_taken = True

# Store results for this stage
results_final = [
StageItemInputOptionFinal(team_id=team_id, already_taken=taken)
for team_id, taken in results_team_ids.items()
]
results[stage.id] = results_final + list(results_tentative.values())

# Then, add inputs from non-elimination stage items that can be used in the next stage.
for stage_item in stage.stage_items:
for winner_position in range(1, 5):
results_tentative.append(
StageItemInputOptionTentative(
winner_from_stage_item_id=stage_item.id,
winner_position=winner_position,
already_taken=False,
if stage_item.type in {StageType.ROUND_ROBIN, StageType.SWISS}:
for winner_position in range(1, 5):
results_tentative[(stage_item.id, winner_position)] = (
StageItemInputOptionTentative(
winner_from_stage_item_id=stage_item.id,
winner_position=winner_position,
already_taken=False,
)
)
)

results_final = [
StageItemInputOptionFinal(team_id=team_id, already_taken=taken)
for team_id, taken in results_team_ids.items()
]
return results_final + results_tentative
return results
5 changes: 1 addition & 4 deletions backend/bracket/routes/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,7 @@ async def get_available_inputs(
) -> StageItemInputOptionsResponse:
stages = await get_full_tournament_details(tournament_id)
teams = await get_teams_with_members(tournament_id)
available_inputs = {
stage.id: determine_available_inputs(stage.id, teams, stages) for stage in stages
}
return StageItemInputOptionsResponse(data=available_inputs)
return StageItemInputOptionsResponse(data=determine_available_inputs(teams, stages))


@router.get("/tournaments/{tournament_id}/next_stage_rankings")
Expand Down
50 changes: 32 additions & 18 deletions frontend/src/components/brackets/brackets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,12 @@ export function RoundsGridCols({
return <NoRoundsAlert readOnly={readOnly} />;
}

const items = stageItem.rounds
let result: React.JSX.Element[] | React.JSX.Element = stageItem.rounds
.sort((r1: any, r2: any) => (r1.name > r2.name ? 1 : -1))
.filter(
(round: RoundInterface) =>
round.matches.length > 0 || displaySettings.matchVisibility === 'all'
)
.map((round: RoundInterface) => (
<Round
key={round.id}
Expand All @@ -98,24 +102,34 @@ export function RoundsGridCols({
/>
));

let result: React.JSX.Element[] | React.JSX.Element = items;

if (result.length < 1) {
result = (
<Container mt="1rem">
<Stack align="center">
<NoContent title={t('no_round_description')} />
<AddRoundButton
t={t}
tournamentData={tournamentData}
stageItem={stageItem}
swrStagesResponse={swrStagesResponse}
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
size="lg"
/>
</Stack>
</Container>
);
if (stageItem.rounds.length < 1) {
result = (
<Container mt="1rem">
<Stack align="center">
<NoContent title={t('no_round_description')} />
{stageItem.rounds.length < 1 && (
<AddRoundButton
t={t}
tournamentData={tournamentData}
stageItem={stageItem}
swrStagesResponse={swrStagesResponse}
swrUpcomingMatchesResponse={swrUpcomingMatchesResponse}
size="lg"
/>
)}
</Stack>
</Container>
);
} else {
result = (
<Container mt="1rem">
<Stack align="center">
<NoContent title={t('no_round_found_title')} />
</Stack>
</Container>
);
}
}

const hideAddRoundButton = tournamentData == null || readOnly;
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/components/brackets/round.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ export default function Round({
/>
);

if (matches.length < 1 && displaySettings.matchVisibility !== 'all') {
return null;
}

const item = (
<div
style={{
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/no_content/empty_table_info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Alert, Container, Text, Title } from '@mantine/core';
import { IconAlertCircle } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next';
import React from 'react';
import { MdOutlineConstruction } from 'react-icons/md';
import { HiMiniWrenchScrewdriver } from 'react-icons/hi2';

import classes from './empty_table_info.module.css';

Expand Down Expand Up @@ -39,7 +39,7 @@ export function NoContent({
}) {
return (
<Container mt="md">
<div className={classes.label}>{icon || <MdOutlineConstruction />}</div>
<div className={classes.label}>{icon || <HiMiniWrenchScrewdriver />}</div>
<Title className={classes.title}>{title}</Title>
<Text size="lg" ta="center" className={classes.description}>
{description}
Expand Down
1 change: 0 additions & 1 deletion frontend/src/interfaces/stage_item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export interface StageItemWithRounds {
name: string;
type_name: string;
team_count: number;
is_active: boolean;
rounds: RoundInterface[];
inputs: StageItemInput[];
stage_id: number;
Expand Down

0 comments on commit cfb74c3

Please sign in to comment.