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..1a19e088b 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, 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 @@ -9,6 +10,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +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 @@ -173,3 +175,30 @@ 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""" + team = request.data["id"] + + 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, 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() + + return Response({"wins": win_count, "losses": loss_count}) 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 a4a164275..fb81b2ebb 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; @@ -29,16 +23,15 @@ class RankCard extends Component {
- -

{rankStr}

+ +

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



-

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

+

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

{this.props.wins}

- +

{this.props.losses}

@@ -82,6 +74,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 +91,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() { @@ -116,35 +108,35 @@ 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 */} - {/*
-
-
-
- + { +
+
+
+
+ +
-
-
-
-
- +
+
+
+ +
-
-
-
-
- + + {/*
+
+
+ +
-
+
*/}
-
*/} + }
);