Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rating History API #780

Merged
merged 4 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1823,6 +1827,8 @@ components:
type: integer
min_score:
type: integer
maximum: 32767
minimum: 0
required:
- episode
- maps
Expand Down Expand Up @@ -2193,15 +2199,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 @@ -2298,6 +2301,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 @@ -2968,6 +2983,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 @@ -3090,6 +3117,8 @@ components:
type: string
external_id:
type: integer
maximum: 32767
minimum: -32768
nullable: true
name:
type: string
Expand All @@ -3099,9 +3128,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
Loading