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

Add multi metric leaderboard #3487

Merged
merged 6 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.20 on 2021-08-07 14:19

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("challenges", "0085_challenge_submission_time_limit"),
]

operations = [
migrations.AddField(
model_name="challengephasesplit",
name="is_multi_metric_leaderboard",
field=models.BooleanField(default=True),
),
]
2 changes: 2 additions & 0 deletions apps/challenges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ class ChallengePhaseSplit(TimeStampedModel):
is_leaderboard_order_descending = models.BooleanField(default=True)
show_leaderboard_by_latest_submission = models.BooleanField(default=False)
show_execution_time = models.BooleanField(default=False)
# Allow ordering leaderboard by all metrics
is_multi_metric_leaderboard = models.BooleanField(default=True)

def __str__(self):
return "{0} : {1}".format(
Expand Down
6 changes: 6 additions & 0 deletions apps/challenges/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class ChallengePhaseSplitSerializer(serializers.ModelSerializer):

dataset_split_name = serializers.SerializerMethodField()
challenge_phase_name = serializers.SerializerMethodField()
leaderboard_schema = serializers.SerializerMethodField()

class Meta:
model = ChallengePhaseSplit
Expand All @@ -156,8 +157,13 @@ class Meta:
"visibility",
"show_leaderboard_by_latest_submission",
"show_execution_time",
"leaderboard_schema",
"is_multi_metric_leaderboard",
)

def get_leaderboard_schema(self, obj):
return obj.leaderboard.schema

def get_dataset_split_name(self, obj):
return obj.dataset_split.name

Expand Down
1 change: 0 additions & 1 deletion apps/challenges/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1246,7 +1246,6 @@ def create_challenge_using_zip_file(request, challenge_host_team_pk):
challenge_phase_data[
field
] = challenge_phase_data_from_hosts.get(field)

try:
with transaction.atomic():
serializer = ZipChallengeSerializer(
Expand Down
32 changes: 28 additions & 4 deletions apps/jobs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def handle_submission_rerun(submission, updated_status):


def calculate_distinct_sorted_leaderboard_data(
user, challenge_obj, challenge_phase_split, only_public_entries
user, challenge_obj, challenge_phase_split, only_public_entries, order_by
):
"""
Function to calculate and return the sorted leaderboard data
Expand All @@ -253,13 +253,39 @@ def calculate_distinct_sorted_leaderboard_data(
leaderboard = challenge_phase_split.leaderboard

# Get the default order by key to rank the entries on the leaderboard
default_order_by = None
is_leaderboard_order_descending = (
challenge_phase_split.is_leaderboard_order_descending
)
try:
default_order_by = leaderboard.schema["default_order_by"]
except KeyError:
response_data = {
"error": "Sorry, default_order_by key is missing in leaderboard schema!"
}
return response_data, status.HTTP_400_BAD_REQUEST
# Use order by field from request only if it is valid
try:
if order_by in leaderboard.schema["labels"]:
default_order_by = order_by
except KeyError:
response_data = {
"error": "Sorry, labels key is missing in leaderboard schema!"
}
return response_data, status.HTTP_400_BAD_REQUEST

leaderboard_schema = leaderboard.schema
if (
leaderboard_schema.get("metadata") is not None
and leaderboard_schema.get("metadata").get(default_order_by)
is not None
):
is_leaderboard_order_descending = (
leaderboard_schema["metadata"][default_order_by].get(
"sort_ascending"
)
is False
)

# Exclude the submissions done by members of the host team
# while populating leaderboard
Expand Down Expand Up @@ -398,9 +424,7 @@ def calculate_distinct_sorted_leaderboard_data(
float(k["filtering_score"]),
float(-k["filtering_error"]),
),
reverse=True
if challenge_phase_split.is_leaderboard_order_descending
else False,
reverse=True if is_leaderboard_order_descending else False,
)
distinct_sorted_leaderboard_data = []
team_list = []
Expand Down
6 changes: 4 additions & 2 deletions apps/jobs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ def change_submission_data_and_visibility(


@swagger_auto_schema(
methods=["get"],
methods=["get", "post"],
manual_parameters=[
openapi.Parameter(
name="challenge_phase_split_id",
Expand Down Expand Up @@ -562,7 +562,7 @@ def change_submission_data_and_visibility(
)
},
)
@api_view(["GET"])
@api_view(["GET", "POST"])
@throttle_classes([AnonRateThrottle])
def leaderboard(request, challenge_phase_split_id):
"""
Expand All @@ -580,6 +580,7 @@ def leaderboard(request, challenge_phase_split_id):
challenge_phase_split_id
)
challenge_obj = challenge_phase_split.challenge_phase.challenge
order_by = request.data.get("order_by")
(
response_data,
http_status_code,
Expand All @@ -588,6 +589,7 @@ def leaderboard(request, challenge_phase_split_id):
challenge_obj,
challenge_phase_split,
only_public_entries=True,
order_by=order_by,
)
# The response 400 will be returned if the leaderboard isn't public or `default_order_by` key is missing in leaderboard.
if http_status_code == status.HTTP_400_BAD_REQUEST:
Expand Down
27 changes: 21 additions & 6 deletions frontend/src/js/controllers/challengeCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@
vm.projectUrl = "";
vm.publicationUrl = "";
vm.isPublicSubmission = null;
vm.isMultiMetricLeaderboardEnabled = {};
vm.wrnMsg = {};
vm.page = {};
vm.isParticipated = false;
vm.isActive = false;
vm.phases = {};
vm.phaseSplits = {};
vm.orderLeaderboardBy = decodeURIComponent($stateParams.metric);
vm.phaseSplitLeaderboardSchema = {};
vm.submissionMetaAttributes = []; // Stores the attributes format and phase ID for all the phases of a challenge.
vm.metaAttributesforCurrentSubmission = null; // Stores the attributes while making a submission for a selected phase.
vm.selectedPhaseSplit = {};
Expand Down Expand Up @@ -829,6 +832,8 @@
vm.phaseSplits[i].showPrivate = true;
vm.showPrivateIds.push(vm.phaseSplits[i].id);
}
vm.isMultiMetricLeaderboardEnabled[vm.phaseSplits[i].id] = vm.phaseSplits[i].is_multi_metric_leaderboard;
vm.phaseSplitLeaderboardSchema[vm.phaseSplits[i].id] = vm.phaseSplits[i].leaderboard_schema;
}
utilities.hideLoader();
},
Expand Down Expand Up @@ -909,8 +914,10 @@
vm.stopLeaderboard();
vm.poller = $interval(function() {
parameters.url = "jobs/" + "challenge_phase_split/" + vm.phaseSplitId + "/leaderboard/?page_size=1000";
parameters.method = 'GET';
parameters.data = {};
parameters.method = 'POST';
parameters.data = {
"order_by": vm.orderLeaderboardBy
};
parameters.callback = {
onSuccess: function(response) {
var details = response.data;
Expand Down Expand Up @@ -967,8 +974,10 @@
// Show leaderboard
vm.leaderboard = {};
parameters.url = "jobs/" + "challenge_phase_split/" + vm.phaseSplitId + "/leaderboard/?page_size=1000";
parameters.method = 'GET';
parameters.data = {};
parameters.method = 'POST';
parameters.data = {
"order_by": vm.orderLeaderboardBy
};
parameters.callback = {
onSuccess: function(response) {
var details = response.data;
Expand Down Expand Up @@ -1389,8 +1398,10 @@
vm.startLoader("Loading Leaderboard Items");
vm.leaderboard = {};
parameters.url = "jobs/" + "challenge_phase_split/" + vm.phaseSplitId + "/leaderboard/?page_size=1000";
parameters.method = 'GET';
parameters.data = {};
parameters.method = 'POST';
parameters.data = {
"order_by": vm.orderLeaderboardBy
};
parameters.callback = {
onSuccess: function(response) {
var details = response.data;
Expand Down Expand Up @@ -2694,6 +2705,10 @@
}
};

vm.encodeMetricURI = function(metric) {
return encodeURIComponent(metric);
};

}

})();
10 changes: 10 additions & 0 deletions frontend/src/js/route-config/route-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@
title: 'Leaderboard'
};

var challenge_phase_metric_leaderboard = {
name: "web.challenge-main.challenge-page.phase-metric-leaderboard",
url: "/leaderboard/:phaseSplitId/:metric",
controller: 'ChallengeCtrl',
controllerAs: 'challenge',
templateUrl: baseUrl + "/web/challenge/leaderboard.html",
title: 'Leaderboard'
};

var profile = {
name: "web.profile",
parent: "web",
Expand Down Expand Up @@ -503,6 +512,7 @@
$stateProvider.state(my_challenge_all_submission);
$stateProvider.state(leaderboard);
$stateProvider.state(challenge_phase_leaderboard);
$stateProvider.state(challenge_phase_metric_leaderboard);

// featured challenge details
$stateProvider.state(featured_challenge_page);
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/views/web/challenge/leaderboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ <h5 class="w-300">Leaderboard</h5>
</span>
</div>
</div>
<div class="row" ng-if="challenge.isMultiMetricLeaderboardEnabled[challenge.phaseSplitId]">
<div class="col xs12 s6">
<span>
<md-select ng-model="challenge.orderLeaderboardBy" placeholder="Order by metric" class="rm-margin">
<md-option value="{{key}}" ui-sref="web.challenge-main.challenge-page.phase-metric-leaderboard({phaseSplitId:challenge.phaseSplitId, metric:challenge.encodeMetricURI(key)})"
ng-repeat="key in challenge.phaseSplitLeaderboardSchema[challenge.phaseSplitId].labels">
<span class="fs-16 w-300">{{key}}</span>
</md-select>
</span>
</div>
</div>
<div class="row">
<div class="horizontal-scroll">
<div class="col s12">
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/challenges/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2962,6 +2962,8 @@ def test_get_challenge_phase_split(self):
"visibility": self.challenge_phase_split.visibility,
"show_leaderboard_by_latest_submission": self.challenge_phase_split.show_leaderboard_by_latest_submission,
"show_execution_time": False,
"leaderboard_schema": self.challenge_phase_split.leaderboard.schema,
"is_multi_metric_leaderboard": self.challenge_phase_split.is_multi_metric_leaderboard,
}
]
self.client.force_authenticate(user=self.participant_user)
Expand Down Expand Up @@ -2999,6 +3001,8 @@ def test_get_challenge_phase_split_when_user_is_challenge_host(self):
"visibility": self.challenge_phase_split.visibility,
"show_leaderboard_by_latest_submission": self.challenge_phase_split.show_leaderboard_by_latest_submission,
"show_execution_time": False,
"leaderboard_schema": self.challenge_phase_split.leaderboard.schema,
"is_multi_metric_leaderboard": self.challenge_phase_split.is_multi_metric_leaderboard,
},
{
"id": self.challenge_phase_split_host.id,
Expand All @@ -3009,6 +3013,8 @@ def test_get_challenge_phase_split_when_user_is_challenge_host(self):
"visibility": self.challenge_phase_split_host.visibility,
"show_leaderboard_by_latest_submission": self.challenge_phase_split_host.show_leaderboard_by_latest_submission,
"show_execution_time": False,
"leaderboard_schema": self.challenge_phase_split_host.leaderboard.schema,
"is_multi_metric_leaderboard": self.challenge_phase_split_host.is_multi_metric_leaderboard,
},
]
self.client.force_authenticate(user=self.user)
Expand Down