From c3271e2f207de3fe4a23e128816014dd0780c300 Mon Sep 17 00:00:00 2001 From: Lowell Torola <44183219+lowtorola@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:32:51 -0500 Subject: [PATCH] client release version (#868) --- backend/siarnaq/api/episodes/admin.py | 1 + .../0008_episode_release_version_client.py | 18 +++++++++++++++++ backend/siarnaq/api/episodes/models.py | 3 +++ backend/siarnaq/api/episodes/serializers.py | 1 + frontend/schema.yml | 3 +++ frontend/src/api/_autogen/models/Episode.ts | 8 ++++++++ frontend/src/api/helpers.ts | 12 ++++++++++- frontend/src/api/team/useTeam.ts | 6 +++++- .../src/components/tables/RankingsTable.tsx | 14 ++++++------- frontend/src/views/Rankings.tsx | 16 --------------- frontend/src/views/TeamProfile.tsx | 11 +++++----- frontend/src/views/Tournament.tsx | 20 +++++++++++-------- 12 files changed, 74 insertions(+), 39 deletions(-) create mode 100644 backend/siarnaq/api/episodes/migrations/0008_episode_release_version_client.py diff --git a/backend/siarnaq/api/episodes/admin.py b/backend/siarnaq/api/episodes/admin.py index 5067929b5..7bb280e3a 100644 --- a/backend/siarnaq/api/episodes/admin.py +++ b/backend/siarnaq/api/episodes/admin.py @@ -50,6 +50,7 @@ class EpisodeAdmin(admin.ModelAdmin): "fields": ( "scaffold", "artifact_name", + "release_version_client", "release_version_public", "release_version_saturn", ), diff --git a/backend/siarnaq/api/episodes/migrations/0008_episode_release_version_client.py b/backend/siarnaq/api/episodes/migrations/0008_episode_release_version_client.py new file mode 100644 index 000000000..7c2481b6f --- /dev/null +++ b/backend/siarnaq/api/episodes/migrations/0008_episode_release_version_client.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.2 on 2024-12-23 20:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("episodes", "0007_remove_episode_pass_requirement_out_of_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="episode", + name="release_version_client", + field=models.CharField(blank=True, max_length=32), + ), + ] diff --git a/backend/siarnaq/api/episodes/models.py b/backend/siarnaq/api/episodes/models.py index 027ad1aa1..2e802a8a1 100644 --- a/backend/siarnaq/api/episodes/models.py +++ b/backend/siarnaq/api/episodes/models.py @@ -93,6 +93,9 @@ class Episode(models.Model): artifact_name = models.CharField(max_length=32, blank=True) """The name of the artifact generated by deploy systems.""" + release_version_client = models.CharField(max_length=32, blank=True) + """The code release version for the client.""" + release_version_public = models.CharField(max_length=32, blank=True) """The code release available for public use.""" diff --git a/backend/siarnaq/api/episodes/serializers.py b/backend/siarnaq/api/episodes/serializers.py index 8369c3daf..4f448ecb6 100644 --- a/backend/siarnaq/api/episodes/serializers.py +++ b/backend/siarnaq/api/episodes/serializers.py @@ -37,6 +37,7 @@ class Meta: "language", "scaffold", "artifact_name", + "release_version_client", "release_version_public", "release_version_saturn", "eligibility_criteria", diff --git a/frontend/schema.yml b/frontend/schema.yml index 2b3baaa2f..be219e591 100644 --- a/frontend/schema.yml +++ b/frontend/schema.yml @@ -2244,6 +2244,9 @@ components: artifact_name: type: string maxLength: 32 + release_version_client: + type: string + maxLength: 32 release_version_public: type: string maxLength: 32 diff --git a/frontend/src/api/_autogen/models/Episode.ts b/frontend/src/api/_autogen/models/Episode.ts index fdef961bc..12bb5db7b 100644 --- a/frontend/src/api/_autogen/models/Episode.ts +++ b/frontend/src/api/_autogen/models/Episode.ts @@ -74,6 +74,12 @@ export interface Episode { * @memberof Episode */ artifact_name?: string; + /** + * + * @type {string} + * @memberof Episode + */ + release_version_client?: string; /** * * @type {string} @@ -132,6 +138,7 @@ export function EpisodeFromJSONTyped(json: any, ignoreDiscriminator: boolean): E 'language': LanguageEnumFromJSON(json['language']), 'scaffold': !exists(json, 'scaffold') ? undefined : json['scaffold'], 'artifact_name': !exists(json, 'artifact_name') ? undefined : json['artifact_name'], + 'release_version_client': !exists(json, 'release_version_client') ? undefined : json['release_version_client'], 'release_version_public': !exists(json, 'release_version_public') ? undefined : json['release_version_public'], 'release_version_saturn': !exists(json, 'release_version_saturn') ? undefined : json['release_version_saturn'], 'eligibility_criteria': ((json['eligibility_criteria'] as Array).map(EligibilityCriterionFromJSON)), @@ -155,6 +162,7 @@ export function EpisodeToJSON(value?: Episode | null): any { 'language': LanguageEnumToJSON(value.language), 'scaffold': value.scaffold, 'artifact_name': value.artifact_name, + 'release_version_client': value.release_version_client, 'release_version_public': value.release_version_public, 'release_version_saturn': value.release_version_saturn, 'eligibility_criteria': ((value.eligibility_criteria as Array).map(EligibilityCriterionToJSON)), diff --git a/frontend/src/api/helpers.ts b/frontend/src/api/helpers.ts index 3bb80e4d2..bc331c407 100644 --- a/frontend/src/api/helpers.ts +++ b/frontend/src/api/helpers.ts @@ -1,5 +1,9 @@ import Cookies from "js-cookie"; -import { Configuration, type ResponseError } from "./_autogen"; +import { + Configuration, + type EligibilityCriterion, + type ResponseError, +} from "./_autogen"; import type { PaginatedQueryFactory, PaginatedRequestMinimal, @@ -117,3 +121,9 @@ export const safeEnsureQueryData = ( export const isNilOrEmptyStr = (str: string | undefined | null): boolean => isNil(str) || str === ""; + +export const getEligibilities = ( + criteria: EligibilityCriterion[], + eligibilityIds: number[], +): EligibilityCriterion[] => + eligibilityIds.flatMap((id) => criteria.find((ec) => ec.id === id) ?? []); diff --git a/frontend/src/api/team/useTeam.ts b/frontend/src/api/team/useTeam.ts index a4d829612..28368f219 100644 --- a/frontend/src/api/team/useTeam.ts +++ b/frontend/src/api/team/useTeam.ts @@ -17,7 +17,7 @@ import type { TeamTMeRetrieveRequest, TeamTRetrieveRequest, } from "../_autogen"; -import { teamMutationKeys } from "./teamKeys"; +import { teamMutationKeys, teamQueryKeys } from "./teamKeys"; import { createTeam, joinTeam, @@ -208,6 +208,10 @@ export const useUpdateTeam = ( buildKey(myTeamFactory.queryKey, { episodeId }), data, ); + + void queryClient.refetchQueries({ + queryKey: teamQueryKeys.teamBase.key({ episodeId }), + }); }, }); diff --git a/frontend/src/components/tables/RankingsTable.tsx b/frontend/src/components/tables/RankingsTable.tsx index ac656eaf2..f11089e36 100644 --- a/frontend/src/components/tables/RankingsTable.tsx +++ b/frontend/src/components/tables/RankingsTable.tsx @@ -8,14 +8,14 @@ import Table from "../Table"; import TableBottom from "../TableBottom"; import { NavLink } from "react-router-dom"; import EligibilityIcon from "../EligibilityIcon"; -import { isPresent } from "../../utils/utilTypes"; import { useEpisodeId } from "../../contexts/EpisodeContext"; +import { useEpisodeInfo } from "api/episode/useEpisode"; +import { getEligibilities } from "api/helpers"; interface RankingsTableProps { data: Maybe; loading: boolean; page: number; - eligibilityMap: Map; handlePage: (page: number) => void; } @@ -30,10 +30,10 @@ const RankingsTable: React.FC = ({ data, loading, page, - eligibilityMap, handlePage, }) => { const { episodeId } = useEpisodeId(); + const episode = useEpisodeInfo({ id: episodeId }); const MAX_NAME_LENGTH = 13; @@ -96,10 +96,10 @@ const RankingsTable: React.FC = ({ header: "Eligibility", key: "eligibility", value: (team) => { - const icons: EligibilityCriterion[] = - team.profile?.eligible_for - ?.map((el) => eligibilityMap.get(el)) - .filter(isPresent) ?? []; + const icons: EligibilityCriterion[] = getEligibilities( + episode.data?.eligibility_criteria ?? [], + team.profile?.eligible_for ?? [], + ); return (
{icons.map((el) => ( diff --git a/frontend/src/views/Rankings.tsx b/frontend/src/views/Rankings.tsx index be7ca405d..f8f8a1c60 100644 --- a/frontend/src/views/Rankings.tsx +++ b/frontend/src/views/Rankings.tsx @@ -1,14 +1,12 @@ import type React from "react"; import { useMemo, useState } from "react"; import { useEpisodeId } from "../contexts/EpisodeContext"; -import type { EligibilityCriterion } from "../api/_autogen"; import { useSearchParams } from "react-router-dom"; import Input from "../components/elements/Input"; import Button from "../components/elements/Button"; import { PageTitle } from "../components/elements/BattlecodeStyle"; import RankingsTable from "../components/tables/RankingsTable"; import { getParamEntries, parsePageParam } from "../utils/searchParamHelpers"; -import { useEpisodeInfo } from "../api/episode/useEpisode"; import { useSearchTeams } from "../api/team/useTeam"; import { useQueryClient } from "@tanstack/react-query"; @@ -32,7 +30,6 @@ const Rankings: React.FC = () => { [searchParams], ); - const episodeData = useEpisodeInfo({ id: episodeId }); const rankingsData = useSearchTeams( { episodeId, @@ -42,18 +39,6 @@ const Rankings: React.FC = () => { queryClient, ); - /** - * This enables us to look up eligibility criteria by index in the table component. - */ - const eligibilityMap: Map = useMemo(() => { - if (!episodeData.isSuccess) { - return new Map(); - } - return new Map( - episodeData.data.eligibility_criteria.map((crit, idx) => [idx, crit]), - ); - }, [episodeData]); - function handlePage(page: number): void { if (!rankingsData.isLoading) { setSearchParams({ ...queryParams, page: page.toString() }); @@ -104,7 +89,6 @@ const Rankings: React.FC = () => { data={rankingsData.data} loading={rankingsData.isLoading} page={queryParams.page} - eligibilityMap={eligibilityMap} handlePage={handlePage} />
diff --git a/frontend/src/views/TeamProfile.tsx b/frontend/src/views/TeamProfile.tsx index 93956d9e5..a89177feb 100644 --- a/frontend/src/views/TeamProfile.tsx +++ b/frontend/src/views/TeamProfile.tsx @@ -13,6 +13,7 @@ import PageNotFound from "./PageNotFound"; // import TeamChart from "components/compete/chart/TeamChart"; // import { useTeamRatingHistory } from "api/compete/useCompete"; import ScrimmagingRecord from "components/compete/ScrimmagingRecord"; +import { getEligibilities } from "api/helpers"; const isNilOrEmptyStr = (str: string | undefined | null): boolean => isNil(str) || str === ""; @@ -40,12 +41,10 @@ const TeamProfile: React.FC = () => { const eligibles = useMemo( () => - episode.data?.eligibility_criteria.filter( - (criterion) => - team.data?.profile?.eligible_for?.find( - (id) => id === criterion.id, - ) !== undefined, - ) ?? [], + getEligibilities( + episode.data?.eligibility_criteria ?? [], + team.data?.profile?.eligible_for ?? [], + ), [episode, team], ); diff --git a/frontend/src/views/Tournament.tsx b/frontend/src/views/Tournament.tsx index 3b2e151aa..8649746f3 100644 --- a/frontend/src/views/Tournament.tsx +++ b/frontend/src/views/Tournament.tsx @@ -15,6 +15,7 @@ import { useEpisodeInfo, useTournamentInfo } from "../api/episode/useEpisode"; import { useTournamentMatchList } from "../api/compete/useCompete"; import { useQueryClient } from "@tanstack/react-query"; import SearchTeamsMenu from "../components/team/SearchTeamsMenu"; +import { getEligibilities } from "api/helpers"; interface QueryParams { page: number; @@ -68,14 +69,17 @@ const TournamentPage: React.FC = () => { if (episode === undefined || tourneyData.data === undefined) { return { includes: [], excludes: [], isEligible: false }; } - const includes = - tourneyData.data.eligibility_includes?.flatMap( - (inc) => episode.eligibility_criteria.find((ec) => ec.id === inc) ?? [], - ) ?? []; - const excludes = - tourneyData.data.eligibility_excludes?.flatMap( - (exc) => episode.eligibility_criteria.find((ec) => ec.id === exc) ?? [], - ) ?? []; + + const includes = getEligibilities( + episode.eligibility_criteria, + tourneyData.data.eligibility_includes ?? [], + ); + + const excludes = getEligibilities( + episode.eligibility_criteria, + tourneyData.data.eligibility_excludes ?? [], + ); + const isEligible = tourneyData.data.is_eligible; return { includes, excludes, isEligible };