From cfb2dd98ee6d217599141118711934de487ee476 Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Sun, 15 Jan 2023 13:40:37 -0500 Subject: [PATCH 1/9] add compete data to teaminfo --- frontend/src/views/teamInfo.js | 53 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/frontend/src/views/teamInfo.js b/frontend/src/views/teamInfo.js index a4a164275..8d93c00c4 100644 --- a/frontend/src/views/teamInfo.js +++ b/frontend/src/views/teamInfo.js @@ -59,11 +59,11 @@ class WinsCard extends Component {
-

{this.props.wins}

+

{wins}

-

{this.props.losses}

+

{losses}

@@ -98,9 +98,9 @@ class TeamInfo extends Component { // Commented out since we don't have scrimmages, records, etc. // Work on this once we are ready to. // Track in #368. - // Api.getOtherTeamWinStats(teamId, (data) => { - // this.setState({ wins: data[0], losses: data[1] }); - // }); + Api.getOtherTeamWinStats(teamId, (data) => { + this.setState({ wins: data[0], losses: data[1] }); + }); } setTeam = (team) => { @@ -116,35 +116,34 @@ class TeamInfo extends Component {
- {/* Commented out since we don't have scrimmages, records, etc. - Work on this once we are ready to. - Do in #368 */} - {/*
-
-
-
- + { +
+
+
+
+ +
-
-
-
-
- +
+
+
+ +
-
-
-
-
- +
+
+
+ +
-
*/} + }
); From f260215dd2d0c751d09a6b9f009068cfe30d402b Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Mon, 16 Jan 2023 02:14:49 -0500 Subject: [PATCH 2/9] build endpoint for win record --- backend/siarnaq/api/compete/urls.py | 3 +++ backend/siarnaq/api/teams/views.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/backend/siarnaq/api/compete/urls.py b/backend/siarnaq/api/compete/urls.py index 647194f78..06e383e8d 100644 --- a/backend/siarnaq/api/compete/urls.py +++ b/backend/siarnaq/api/compete/urls.py @@ -15,6 +15,9 @@ views.ScrimmageRequestViewSet, basename="request", ) +router.register( + r"(?P[^\/.]+)/matchParticipant", views.MatchViewSet, basename="match" +) urlpatterns = [ path("", include(router.urls)), diff --git a/backend/siarnaq/api/teams/views.py b/backend/siarnaq/api/teams/views.py index 76f78df1e..180741561 100644 --- a/backend/siarnaq/api/teams/views.py +++ b/backend/siarnaq/api/teams/views.py @@ -9,6 +9,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +from siarnaq.api.compete.models import MatchParticipant from siarnaq.api.episodes.permissions import IsEpisodeAvailable from siarnaq.api.teams.exceptions import TeamMaxSizeExceeded from siarnaq.api.teams.filters import TeamActiveSubmissionFilter, TeamOrderingFilter @@ -173,3 +174,35 @@ def avatar(self, request, pk=None, *, episode_id): titan.upload_image(avatar, profile.get_avatar_path()) return Response(None, status=status.HTTP_204_NO_CONTENT) + + @action( + detail=False, + methods=["post"], + serializer_class=None, + permission_classes=(IsAuthenticated, IsEpisodeAvailable), + ) + def record(self, request, pk=None, *, episode_id): + """Retrieve the win/loss record of a team""" + match_participations = MatchParticipant.objects.filter( + team_id=request.data["id"] + ) + win_count = 0 + loss_count = 0 + for mp in match_participations: + match = mp.match + team_score = [ + p.score + for p in match.participants.all() + if p.team.id == request.data["id"] + ] + opponent_score = [ + p.score + for p in match.participants.all() + if p.team.id != request.data["id"] + ] + if team_score and opponent_score: + if team_score[0] > opponent_score[0]: + win_count += 1 + else: + loss_count += 1 + return Response({"wins": win_count, "losses": loss_count}) From 746146d1248f48e2d7cf262c4c0219975898208d Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Mon, 16 Jan 2023 02:17:02 -0500 Subject: [PATCH 3/9] create getTeamWinStats in api.js --- frontend/src/api.js | 39 +++++++++++-------------------- frontend/src/views/teamInfo.js | 42 +++++++++++++++------------------- 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/frontend/src/api.js b/frontend/src/api.js index db22f5646..c30e39834 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -99,34 +99,21 @@ class Api { // clean these calls, fix in #368 // data from scrimmaging - static getOwnTeamMuHistory(callback) { - return Api.getTeamMuHistory(Cookies.get("team_id"), callback); - } - - static getTeamMuHistory(team, callback) { - $.get(`${URL}/api/${LEAGUE}/team/${team}/history/`).done((data, status) => { - callback(data); - }); - } - static getTeamWinStats(callback) { - return Api.getOtherTeamWinStats(Cookies.get("team_id"), callback); - } - - static getOtherTeamWinStats(team, callback) { - this.getTeamMuHistory(team, (data) => { - let wins = 0; - let losses = 0; - data.forEach((entry) => { - if (entry.won === true) { - wins++; - } else if (entry.won === false) { - losses++; - } // entry.won can be null when errors occur, doesn't contribute to win/loss + static getTeamWinStats(team, episode, callback) { + $.ajax({ + url: `${URL}/api/team/${episode}/t/record/`, + data: JSON.stringify(team), + type: "POST", + contentType: "application/json", + dataType: "json", + }) + .done((data) => { + callback(data); + }) + .fail((xhr, status, error) => { + callback(xhr, false); }); - - callback([wins, losses]); - }); } //get data for team with team_id diff --git a/frontend/src/views/teamInfo.js b/frontend/src/views/teamInfo.js index 8d93c00c4..ac6974691 100644 --- a/frontend/src/views/teamInfo.js +++ b/frontend/src/views/teamInfo.js @@ -6,20 +6,14 @@ import TeamCard from "../components/teamCard"; import PerfCard from "../components/perfCard"; class RankCard extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { - ranking: null, + rating: this.props.team.profile.rating, }; } - componentDidMount() { - Api.getTeamRanking(this.props.teamId, this.setRanking); - } - - setRanking = (ranking_data) => { - this.setState({ ranking: ranking_data.ranking }); - }; + componentDidMount() {} render() { const { ranking } = this.state; @@ -34,10 +28,10 @@ class RankCard extends Component {


- Score:{" "} - {this.props.team.score === -1000000 + Rating:{" "} + {this.state.rating === -1000000 ? "N/A" - : Math.round(this.props.team.score)} + : Math.round(this.state.rating)}

@@ -52,18 +46,17 @@ class WinsCard extends Component { } render() { - const { wins, draws, losses } = this.state; return (
- -

{wins}

+ +

{this.props.wins}

- -

{losses}

+ +

{this.props.losses}

@@ -82,6 +75,7 @@ class TeamInfo extends Component { // and the :team_id part makes props.match.params.team_id // whatever that id is) id: this.props.match.params.team_id, + episode: this.props.match.params.episode, team: null, wins: 0, losses: 0, @@ -98,13 +92,12 @@ class TeamInfo extends Component { // Commented out since we don't have scrimmages, records, etc. // Work on this once we are ready to. // Track in #368. - Api.getOtherTeamWinStats(teamId, (data) => { - this.setState({ wins: data[0], losses: data[1] }); - }); } setTeam = (team) => { - this.setState({ team }); + Api.getTeamWinStats(team, this.state.episode, (data) => { + this.setState({ team: team, wins: data["wins"], losses: data["losses"] }); + }); }; render() { @@ -135,13 +128,14 @@ class TeamInfo extends Component {
-
+ + {/*
-
+
*/} } From 549d17dbd895b7a45602931560561410320ef8d8 Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Mon, 16 Jan 2023 18:16:10 -0500 Subject: [PATCH 4/9] calculate win/loss record without loops --- backend/siarnaq/api/teams/views.py | 35 +++++++++++------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/backend/siarnaq/api/teams/views.py b/backend/siarnaq/api/teams/views.py index 180741561..a6df0b9a8 100644 --- a/backend/siarnaq/api/teams/views.py +++ b/backend/siarnaq/api/teams/views.py @@ -2,6 +2,7 @@ import structlog from django.db import transaction +from django.db.models import F, Max, Q from django.shortcuts import get_object_or_404 from drf_spectacular.utils import extend_schema from rest_framework import filters, mixins, status, viewsets @@ -9,7 +10,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from siarnaq.api.compete.models import MatchParticipant +from siarnaq.api.compete.models import Match from siarnaq.api.episodes.permissions import IsEpisodeAvailable from siarnaq.api.teams.exceptions import TeamMaxSizeExceeded from siarnaq.api.teams.filters import TeamActiveSubmissionFilter, TeamOrderingFilter @@ -183,26 +184,16 @@ def avatar(self, request, pk=None, *, episode_id): ) def record(self, request, pk=None, *, episode_id): """Retrieve the win/loss record of a team""" - match_participations = MatchParticipant.objects.filter( - team_id=request.data["id"] + team = request.data["id"] + matches = Match.objects.filter(participants__team=team) + + matches.annotate( + score_1=Max("participants__score", filter=Q(participants__team=team)), + score_2=Max("participants__score", filter=~Q(participants__team=team)), ) - win_count = 0 - loss_count = 0 - for mp in match_participations: - match = mp.match - team_score = [ - p.score - for p in match.participants.all() - if p.team.id == request.data["id"] - ] - opponent_score = [ - p.score - for p in match.participants.all() - if p.team.id != request.data["id"] - ] - if team_score and opponent_score: - if team_score[0] > opponent_score[0]: - win_count += 1 - else: - loss_count += 1 + + win_count = matches.filter(score_1__gt=F("score_2")).count() + + loss_count = matches.filter(score_1__lt=F("score_2")).count() + return Response({"wins": win_count, "losses": loss_count}) From 89098b548496d58cfbdd1ad19846844f56022418 Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Wed, 18 Jan 2023 20:34:05 -0500 Subject: [PATCH 5/9] annotate matches --- backend/siarnaq/api/teams/views.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/siarnaq/api/teams/views.py b/backend/siarnaq/api/teams/views.py index a6df0b9a8..8f413fb96 100644 --- a/backend/siarnaq/api/teams/views.py +++ b/backend/siarnaq/api/teams/views.py @@ -185,15 +185,13 @@ def avatar(self, request, pk=None, *, episode_id): def record(self, request, pk=None, *, episode_id): """Retrieve the win/loss record of a team""" team = request.data["id"] - matches = Match.objects.filter(participants__team=team) + matches = Match.objects.filter(participants__team=team, status="OK!") - matches.annotate( + matches = matches.annotate( score_1=Max("participants__score", filter=Q(participants__team=team)), - score_2=Max("participants__score", filter=~Q(participants__team=team)), + score_2=Max("participants__score", exclude=Q(participants__team=team)), ) - win_count = matches.filter(score_1__gt=F("score_2")).count() - loss_count = matches.filter(score_1__lt=F("score_2")).count() return Response({"wins": win_count, "losses": loss_count}) From 48d62e217f03ca4c6a0ce906d5e97540443f4bd4 Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Mon, 23 Jan 2023 17:56:18 -0500 Subject: [PATCH 6/9] Correctly calculate win/loss record --- backend/siarnaq/api/teams/views.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/backend/siarnaq/api/teams/views.py b/backend/siarnaq/api/teams/views.py index 8f413fb96..ebf6d2634 100644 --- a/backend/siarnaq/api/teams/views.py +++ b/backend/siarnaq/api/teams/views.py @@ -2,7 +2,7 @@ import structlog from django.db import transaction -from django.db.models import F, Max, Q +from django.db.models import F, OuterRef, Subquery from django.shortcuts import get_object_or_404 from drf_spectacular.utils import extend_schema from rest_framework import filters, mixins, status, viewsets @@ -10,7 +10,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from siarnaq.api.compete.models import Match +from siarnaq.api.compete.models import Match, MatchParticipant from siarnaq.api.episodes.permissions import IsEpisodeAvailable from siarnaq.api.teams.exceptions import TeamMaxSizeExceeded from siarnaq.api.teams.filters import TeamActiveSubmissionFilter, TeamOrderingFilter @@ -187,11 +187,21 @@ def record(self, request, pk=None, *, episode_id): team = request.data["id"] matches = Match.objects.filter(participants__team=team, status="OK!") - matches = matches.annotate( - score_1=Max("participants__score", filter=Q(participants__team=team)), - score_2=Max("participants__score", exclude=Q(participants__team=team)), + matches = Match.objects.annotate( + my_score=Subquery( + MatchParticipant.objects.filter(match=OuterRef("pk")) + .filter(team=team) + .values("score") + ), + opponent_score=Subquery( + MatchParticipant.objects.filter(match=OuterRef("pk")) + .exclude(team=team) + .values("score")[:1] + ), + ).filter( + my_score__isnull=False, ) - win_count = matches.filter(score_1__gt=F("score_2")).count() - loss_count = matches.filter(score_1__lt=F("score_2")).count() + win_count = matches.filter(my_score__gt=F("opponent_score")).count() + loss_count = matches.filter(my_score__lt=F("opponent_score")).count() return Response({"wins": win_count, "losses": loss_count}) From e9f61722f8dd74129220e5ae1634e97830cc467a Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Wed, 25 Jan 2023 16:00:36 -0500 Subject: [PATCH 7/9] Prevent tournament matches from appearing in W/L record --- backend/siarnaq/api/teams/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/siarnaq/api/teams/views.py b/backend/siarnaq/api/teams/views.py index ebf6d2634..797bf43c6 100644 --- a/backend/siarnaq/api/teams/views.py +++ b/backend/siarnaq/api/teams/views.py @@ -185,7 +185,9 @@ def avatar(self, request, pk=None, *, episode_id): def record(self, request, pk=None, *, episode_id): """Retrieve the win/loss record of a team""" team = request.data["id"] - matches = Match.objects.filter(participants__team=team, status="OK!") + matches = Match.objects.filter(tournament_round__isnull=True).filter( + participants__team=team, status="OK!" + ) matches = Match.objects.annotate( my_score=Subquery( From d3c809a756befe876eca183cf6dc4a737ba6ec54 Mon Sep 17 00:00:00 2001 From: Apollo1291 Date: Wed, 25 Jan 2023 16:01:28 -0500 Subject: [PATCH 8/9] Show Rating only, no Ranking --- frontend/src/views/teamInfo.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/views/teamInfo.js b/frontend/src/views/teamInfo.js index ac6974691..fb81b2ebb 100644 --- a/frontend/src/views/teamInfo.js +++ b/frontend/src/views/teamInfo.js @@ -23,16 +23,15 @@ class RankCard extends Component {
- -

{rankStr}

+ +

+ {this.state.rating === -1000000 + ? "N/A" + : Math.round(this.state.rating)} +



-

- Rating:{" "} - {this.state.rating === -1000000 - ? "N/A" - : Math.round(this.state.rating)} -

+

); @@ -51,11 +50,11 @@ class WinsCard extends Component {
- +

{this.props.wins}

- +

{this.props.losses}

From aac0a66fc028f96aba01f53f3f0af01cdcb2c22a Mon Sep 17 00:00:00 2001 From: Nathan Kim Date: Wed, 25 Jan 2023 15:31:46 -0500 Subject: [PATCH 9/9] Move filtering to right part of query --- backend/siarnaq/api/teams/views.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/backend/siarnaq/api/teams/views.py b/backend/siarnaq/api/teams/views.py index 797bf43c6..1a19e088b 100644 --- a/backend/siarnaq/api/teams/views.py +++ b/backend/siarnaq/api/teams/views.py @@ -185,9 +185,6 @@ def avatar(self, request, pk=None, *, episode_id): def record(self, request, pk=None, *, episode_id): """Retrieve the win/loss record of a team""" team = request.data["id"] - matches = Match.objects.filter(tournament_round__isnull=True).filter( - participants__team=team, status="OK!" - ) matches = Match.objects.annotate( my_score=Subquery( @@ -200,9 +197,7 @@ def record(self, request, pk=None, *, episode_id): .exclude(team=team) .values("score")[:1] ), - ).filter( - my_score__isnull=False, - ) + ).filter(my_score__isnull=False, tournament_round__isnull=True, status="OK!") win_count = matches.filter(my_score__gt=F("opponent_score")).count() loss_count = matches.filter(my_score__lt=F("opponent_score")).count()