From 73bff738ba888586dd53568944ed5ced8c116c29 Mon Sep 17 00:00:00 2001 From: Erik Vroon Date: Thu, 7 Nov 2024 16:49:08 +0100 Subject: [PATCH] Handle edge case with empty stage item inputs --- .../scheduling/handle_stage_activation.py | 18 ++++++++++++++++-- backend/bracket/routes/stages.py | 3 ++- .../modals/activate_next_stage_modal.tsx | 9 +++++++-- frontend/src/components/utils/error_alert.tsx | 8 +++++++- frontend/src/components/utils/skeletons.tsx | 10 +++++++--- .../src/pages/tournaments/[id]/settings.tsx | 4 ++-- 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/backend/bracket/logic/scheduling/handle_stage_activation.py b/backend/bracket/logic/scheduling/handle_stage_activation.py index d11909ec0..da2c9e81e 100644 --- a/backend/bracket/logic/scheduling/handle_stage_activation.py +++ b/backend/bracket/logic/scheduling/handle_stage_activation.py @@ -1,12 +1,18 @@ from collections import defaultdict +from fastapi import HTTPException from pydantic import BaseModel +from starlette import status from bracket.logic.ranking.elo import ( determine_team_ranking_for_stage_item, ) from bracket.logic.ranking.statistics import TeamStatistics -from bracket.models.db.stage_item_inputs import StageItemInputFinal, StageItemInputTentative +from bracket.models.db.stage_item_inputs import ( + StageItemInputEmpty, + StageItemInputFinal, + StageItemInputTentative, +) from bracket.models.db.team import Team from bracket.models.db.util import StageWithStageItems from bracket.sql.matches import clear_scores_for_matches_in_stage_item @@ -65,7 +71,15 @@ async def get_team_update_for_input( target_stage_item_input = await get_stage_item_input_by_id( tournament_id, target_stage_item_input_id ) - assert isinstance(target_stage_item_input, StageItemInputFinal) + if isinstance(target_stage_item_input, StageItemInputEmpty): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Please first assign teams to all stage items in the current stage.", + ) + + assert isinstance( + target_stage_item_input, StageItemInputFinal + ), f"Unexpected stage item type: {type(target_stage_item_input)}" return StageItemInputUpdate( stage_item_input=stage_item_input, team=target_stage_item_input.team ) diff --git a/backend/bracket/routes/stages.py b/backend/bracket/routes/stages.py index a51549cbd..ccdddd869 100644 --- a/backend/bracket/routes/stages.py +++ b/backend/bracket/routes/stages.py @@ -126,12 +126,13 @@ async def activate_next_stage( stages = await get_full_tournament_details(tournament_id) deactivated_stage = next((stage for stage in stages if stage.is_active), None) - await sql_activate_next_stage(new_active_stage_id, tournament_id) if stage_body.direction == "next": await update_matches_in_activated_stage(tournament_id, new_active_stage_id) else: if deactivated_stage: await update_matches_in_deactivated_stage(tournament_id, deactivated_stage) + + await sql_activate_next_stage(new_active_stage_id, tournament_id) return SuccessResponse() diff --git a/frontend/src/components/modals/activate_next_stage_modal.tsx b/frontend/src/components/modals/activate_next_stage_modal.tsx index f0247066d..457814eb9 100644 --- a/frontend/src/components/modals/activate_next_stage_modal.tsx +++ b/frontend/src/components/modals/activate_next_stage_modal.tsx @@ -1,4 +1,4 @@ -import { Alert, Button, Container, Grid, Loader, Modal, Title } from '@mantine/core'; +import { Alert, Button, Container, Grid, Modal, Title } from '@mantine/core'; import { useForm } from '@mantine/form'; import { FaArrowRight } from '@react-icons/all-files/fa/FaArrowRight'; import { IconAlertCircle, IconSquareArrowRight } from '@tabler/icons-react'; @@ -11,6 +11,8 @@ import { StageItemInput, formatStageItemInput } from '../../interfaces/stage_ite import { TeamInterface } from '../../interfaces/team'; import { getStageItemLookup } from '../../services/lookups'; import { activateNextStage } from '../../services/stage'; +import RequestErrorAlert from '../utils/error_alert'; +import { GenericSkeleton } from '../utils/skeletons'; type Update = { stage_item_input: StageItemInput; team: TeamInterface }; type StageItemUpdate = { updates: Update[]; stageItem: StageItemWithRounds }; @@ -47,7 +49,10 @@ function UpdatesToStageItemInputsTables({ swrRankingsPerStageItemResponse: SWRResponse; }) { if (swrRankingsPerStageItemResponse.isLoading) { - return ; + return ; + } + if (swrRankingsPerStageItemResponse.error) { + return ; } const items = swrRankingsPerStageItemResponse.data.data; diff --git a/frontend/src/components/utils/error_alert.tsx b/frontend/src/components/utils/error_alert.tsx index 604532d72..528dbaebf 100644 --- a/frontend/src/components/utils/error_alert.tsx +++ b/frontend/src/components/utils/error_alert.tsx @@ -4,7 +4,13 @@ import React from 'react'; export function ErrorAlert({ title, message }: { title: string; message: string }) { return ( - } title={title} color="red" radius="lg"> + } + title={title} + color="red" + radius="lg" + variant="outline" + > {message} ); diff --git a/frontend/src/components/utils/skeletons.tsx b/frontend/src/components/utils/skeletons.tsx index 387513223..8938e2b6a 100644 --- a/frontend/src/components/utils/skeletons.tsx +++ b/frontend/src/components/utils/skeletons.tsx @@ -2,11 +2,15 @@ import { Center, Grid, Skeleton } from '@mantine/core'; import React from 'react'; export function GenericSkeleton() { + return ; +} + +export function GenericSkeletonThreeRows() { return ( <> - - - + + + ); } diff --git a/frontend/src/pages/tournaments/[id]/settings.tsx b/frontend/src/pages/tournaments/[id]/settings.tsx index bb4fb07bc..0cc0b9e8b 100644 --- a/frontend/src/pages/tournaments/[id]/settings.tsx +++ b/frontend/src/pages/tournaments/[id]/settings.tsx @@ -25,7 +25,7 @@ import { SWRResponse } from 'swr'; import NotFoundTitle from '../../404'; import { DropzoneButton } from '../../../components/utils/file_upload'; -import { GenericSkeleton } from '../../../components/utils/skeletons'; +import { GenericSkeletonThreeRows } from '../../../components/utils/skeletons'; import { capitalize, getBaseURL, getTournamentIdFromRouter } from '../../../components/utils/util'; import { Club } from '../../../interfaces/club'; import { Tournament } from '../../../interfaces/tournament'; @@ -266,7 +266,7 @@ export default function SettingsPage() { let content = ; if (swrTournamentResponse.isLoading || swrClubsResponse.isLoading) { - content = ; + content = ; } if (tournamentDataFull != null) {