Skip to content

Commit

Permalink
Rating History API (#780)
Browse files Browse the repository at this point in the history
Co-authored-by: Desperationis <[email protected]>
  • Loading branch information
lowtorola and Desperationis authored May 3, 2024
1 parent 87e1dad commit d1067a6
Show file tree
Hide file tree
Showing 17 changed files with 503 additions and 84 deletions.
Binary file removed .DS_Store
Binary file not shown.
14 changes: 12 additions & 2 deletions backend/siarnaq/api/compete/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from siarnaq.api.episodes.models import Map, ReleaseStatus
from siarnaq.api.episodes.serializers import TournamentRoundSerializer
from siarnaq.api.teams.models import Team, TeamStatus
from siarnaq.api.teams.serializers import RatingField
from siarnaq.api.teams.serializers import RatingField, TeamPublicSerializer

logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -466,10 +466,20 @@ def save(self, *args, **kwargs):
super().save(*args, **kwargs)


class HistoricalRatingSerializer(serializers.Serializer):
class MatchRatingSerializer(serializers.Serializer):
rating = RatingField()
timestamp = serializers.DateTimeField()


class TeamRatingSerializer(serializers.Serializer):
team = TeamPublicSerializer()
rating_history = MatchRatingSerializer(many=True)


class HistoricalRatingSerializer(serializers.Serializer):
team_id = serializers.IntegerField()
team_rating = TeamRatingSerializer(default=None)


class EmptySerializer(serializers.Serializer):
pass
53 changes: 33 additions & 20 deletions backend/siarnaq/api/compete/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,30 +388,34 @@ def scrimmage(self, request, pk=None, *, episode_id):
@extend_schema(
parameters=[
OpenApiParameter(
name="team_id",
name="team_ids",
type=int,
description="A team to filter for. Defaults to your own team.",
description="A list of teams to filter for. Defaults to your own team.",
many=True,
),
],
responses={
status.HTTP_204_NO_CONTENT: OpenApiResponse(
description="No ranked matches found."
),
status.HTTP_200_OK: HistoricalRatingSerializer(),
status.HTTP_200_OK: HistoricalRatingSerializer(many=True),
},
)
@action(
detail=False,
methods=["get"],
permission_classes=(IsEpisodeMutable,),
# needed so that the generated schema is not paginated
pagination_class=None,
)
def historical_rating(self, request, pk=None, *, episode_id):
"""List the historical rating of a team."""
"""List the historical ratings of a list of teams."""
queryset = Match.objects.all().filter(tournament_round__isnull=True)

team_id = parse_int(self.request.query_params.get("team_id"))
if team_id is not None:
queryset = queryset.filter(participants__team=team_id)
team_ids = self.request.query_params.getlist("team_ids")
if team_ids is not None and len(team_ids) > 0:
team_ids = {parse_int(team_id) for team_id in team_ids}
queryset = queryset.filter(participants__team__in=team_ids)
elif request.user.pk is not None:
queryset = queryset.filter(participants__team__members=request.user.pk)
else:
Expand All @@ -420,21 +424,30 @@ def historical_rating(self, request, pk=None, *, episode_id):
participants__team__status=TeamStatus.INVISIBLE
)
queryset = queryset.exclude(pk__in=Subquery(has_invisible.values("pk")))
queryset = queryset.filter(is_ranked=True)

matches = queryset.all().order_by("created")
queryset = queryset.filter(is_ranked=True).filter(
participants__rating__isnull=False
)

ordered = [
{
"timestamp": match.created,
"rating": match.participants.get(team=team_id).rating
if team_id is not None
else match.participants.get(team__members__pk=request.user.pk).rating,
}
for match in matches
]
matches = queryset.all().order_by("-created")
grouped = {
team_id: {"team_id": team_id, "team_rating": None} for team_id in team_ids
}

results = HistoricalRatingSerializer(ordered, many=True).data
for match in matches:
matching_participants = match.participants.filter(team__in=team_ids).all()
for participant in matching_participants:
match_info = {"timestamp": match.created, "rating": participant.rating}
if grouped[participant.team.id]["team_rating"] is None:
grouped[participant.team.id]["team_rating"] = {
"team": participant.team,
"rating_history": [match_info],
}
else:
grouped[participant.team.id]["team_rating"][
"rating_history"
].append(match_info)

results = HistoricalRatingSerializer(grouped.values(), many=True).data
return Response(results, status=status.HTTP_200_OK)

@extend_schema(
Expand Down
64 changes: 49 additions & 15 deletions frontend2/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ paths:
description: This match was already finalized
/api/compete/{episode_id}/match/historical_rating/:
get:
operationId: compete_match_historical_rating_retrieve
description: List the historical rating of a team.
operationId: compete_match_historical_rating_list
description: List the historical ratings of a list of teams.
parameters:
- in: path
name: episode_id
Expand All @@ -153,10 +153,12 @@ paths:
pattern: ^[^\/.]+$
required: true
- in: query
name: team_id
name: team_ids
schema:
type: integer
description: A team to filter for. Defaults to your own team.
type: array
items:
type: integer
description: A list of teams to filter for. Defaults to just your own team.
tags:
- compete
security:
Expand All @@ -168,7 +170,9 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/HistoricalRating'
type: array
items:
$ref: '#/components/schemas/HistoricalRating'
description: ''
/api/compete/{episode_id}/match/scrimmage/:
get:
Expand Down Expand Up @@ -1817,6 +1821,8 @@ components:
type: integer
min_score:
type: integer
maximum: 32767
minimum: 0
required:
- episode
- maps
Expand Down Expand Up @@ -2187,15 +2193,12 @@ components:
HistoricalRating:
type: object
properties:
rating:
type: number
format: double
timestamp:
type: string
format: date-time
team_id:
type: integer
team_rating:
$ref: '#/components/schemas/TeamRating'
required:
- rating
- timestamp
- team_id
LanguageEnum:
enum:
- java8
Expand Down Expand Up @@ -2292,6 +2295,18 @@ components:
- submission
- team
- teamname
MatchRating:
type: object
properties:
rating:
type: number
format: double
timestamp:
type: string
format: date-time
required:
- rating
- timestamp
MatchReportRequest:
type: object
properties:
Expand Down Expand Up @@ -2962,6 +2977,18 @@ components:
- members
- name
- status
TeamRating:
type: object
properties:
team:
$ref: '#/components/schemas/TeamPublic'
rating_history:
type: array
items:
$ref: '#/components/schemas/MatchRating'
required:
- rating_history
- team
TeamReportRequest:
type: object
properties:
Expand Down Expand Up @@ -3084,6 +3111,8 @@ components:
type: string
external_id:
type: integer
maximum: 32767
minimum: -32768
nullable: true
name:
type: string
Expand All @@ -3093,9 +3122,14 @@ components:
items:
type: integer
release_status:
$ref: '#/components/schemas/ReleaseStatusEnum'
allOf:
- $ref: '#/components/schemas/ReleaseStatusEnum'
minimum: -2147483648
maximum: 2147483647
display_order:
type: integer
maximum: 32767
minimum: 0
required:
- display_order
- id
Expand Down
12 changes: 12 additions & 0 deletions frontend2/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { tournamentsLoader } from "./api/loaders/tournamentsLoader";
import { tournamentLoader } from "./api/loaders/tournamentLoader";
import { homeLoader } from "./api/loaders/homeLoader";
import ErrorBoundary from "./views/ErrorBoundary";
import { searchTeamsFactory } from "api/team/teamFactories";

const queryClient = new QueryClient({
queryCache: new QueryCache({
Expand Down Expand Up @@ -93,6 +94,17 @@ const episodeLoader: LoaderFunction = ({ params }) => {
staleTime: Infinity,
});

// Prefetch the top 10 ranked teams' rating histories.
void queryClient.ensureQueryData({
queryKey: buildKey(searchTeamsFactory.queryKey, { episodeId: id, page: 1 }),
queryFn: async () =>
await searchTeamsFactory.queryFn(
{ episodeId: id, page: 1 },
queryClient,
false, // We don't want to prefetch teams 11-20
),
});

return null;
};

Expand Down
2 changes: 2 additions & 0 deletions frontend2/src/api/_autogen/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ models/HistoricalRating.ts
models/LanguageEnum.ts
models/Match.ts
models/MatchParticipant.ts
models/MatchRating.ts
models/MatchReportRequest.ts
models/PaginatedClassRequirementList.ts
models/PaginatedEpisodeList.ts
Expand Down Expand Up @@ -57,6 +58,7 @@ models/TeamProfilePrivate.ts
models/TeamProfilePrivateRequest.ts
models/TeamProfilePublic.ts
models/TeamPublic.ts
models/TeamRating.ts
models/TeamReportRequest.ts
models/TokenObtainPair.ts
models/TokenObtainPairRequest.ts
Expand Down
22 changes: 11 additions & 11 deletions frontend2/src/api/_autogen/apis/CompeteApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ import {
TournamentSubmissionToJSON,
} from '../models';

export interface CompeteMatchHistoricalRatingRetrieveRequest {
export interface CompeteMatchHistoricalRatingListRequest {
episodeId: string;
teamId?: number;
teamIds?: Array<number>;
}

export interface CompeteMatchListRequest {
Expand Down Expand Up @@ -169,17 +169,17 @@ export interface CompeteSubmissionTournamentListRequest {
export class CompeteApi extends runtime.BaseAPI {

/**
* List the historical rating of a team.
* List the historical ratings of a list of teams.
*/
async competeMatchHistoricalRatingRetrieveRaw(requestParameters: CompeteMatchHistoricalRatingRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<HistoricalRating>> {
async competeMatchHistoricalRatingListRaw(requestParameters: CompeteMatchHistoricalRatingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<HistoricalRating>>> {
if (requestParameters.episodeId === null || requestParameters.episodeId === undefined) {
throw new runtime.RequiredError('episodeId','Required parameter requestParameters.episodeId was null or undefined when calling competeMatchHistoricalRatingRetrieve.');
throw new runtime.RequiredError('episodeId','Required parameter requestParameters.episodeId was null or undefined when calling competeMatchHistoricalRatingList.');
}

const queryParameters: any = {};

if (requestParameters.teamId !== undefined) {
queryParameters['team_id'] = requestParameters.teamId;
if (requestParameters.teamIds) {
queryParameters['team_ids'] = requestParameters.teamIds;
}

const headerParameters: runtime.HTTPHeaders = {};
Expand All @@ -199,14 +199,14 @@ export class CompeteApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);

return new runtime.JSONApiResponse(response, (jsonValue) => HistoricalRatingFromJSON(jsonValue));
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(HistoricalRatingFromJSON));
}

/**
* List the historical rating of a team.
* List the historical ratings of a list of teams.
*/
async competeMatchHistoricalRatingRetrieve(requestParameters: CompeteMatchHistoricalRatingRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<HistoricalRating> {
const response = await this.competeMatchHistoricalRatingRetrieveRaw(requestParameters, initOverrides);
async competeMatchHistoricalRatingList(requestParameters: CompeteMatchHistoricalRatingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<HistoricalRating>> {
const response = await this.competeMatchHistoricalRatingListRaw(requestParameters, initOverrides);
return await response.value();
}

Expand Down
Loading

0 comments on commit d1067a6

Please sign in to comment.