Skip to content

Commit 3d48dff

Browse files
Display task scores in CWS
There's a little issue here when there are many submissions waiting for score updates: since the request for each submission is independent, and now we also return the task score with each, it might be that the task score is more up to date than the list of submission. I don't think it's a big deal.
1 parent 81da4fd commit 3d48dff

4 files changed

Lines changed: 201 additions & 27 deletions

File tree

cms/server/contest/handlers/tasksubmission.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from cms import config, FEEDBACK_LEVEL_FULL
4848
from cms.db import Submission, SubmissionResult
4949
from cms.grading.languagemanager import get_language
50+
from cms.grading.scoring import task_score
5051
from cms.server import multi_contest
5152
from cms.server.contest.submission import get_submission_count, \
5253
UnacceptableSubmission, accept_submission
@@ -133,6 +134,13 @@ def get(self, task_name):
133134
.options(joinedload(Submission.results))\
134135
.all()
135136

137+
public_score, is_public_score_partial = task_score(
138+
participation, task, public=True)
139+
tokened_score, is_tokened_score_partial = task_score(
140+
participation, task, only_tokened=True)
141+
# These two should be the same, anyway.
142+
is_score_partial = is_public_score_partial or is_tokened_score_partial
143+
136144
submissions_left_contest = None
137145
if self.contest.max_submission_number is not None:
138146
submissions_c = \
@@ -162,6 +170,9 @@ def get(self, task_name):
162170
download_allowed = self.contest.submissions_download_allowed
163171
self.render("task_submissions.html",
164172
task=task, submissions=submissions,
173+
public_score=public_score,
174+
tokened_score=tokened_score,
175+
is_score_partial=is_score_partial,
165176
tokens_task=task.token_mode,
166177
tokens_info=tokens_info,
167178
submissions_left=submissions_left,
@@ -173,6 +184,42 @@ class SubmissionStatusHandler(ContestHandler):
173184

174185
refresh_cookie = False
175186

187+
def add_task_score(self, participation, task, data):
188+
"""Add the task score information to the dict to be returned.
189+
190+
participation (Participation): user for which we want the score.
191+
task (Task): task for which we want the score.
192+
data (dict): where to put the data; all fields will start with "task",
193+
followed by "public" if referring to the public scores, or
194+
"tokened" if referring to the total score (always limited to
195+
tokened submissions); for both public and tokened, the fields are:
196+
"score" and "score_message"; in addition we have
197+
"task_is_score_partial" as partial info is the same for both.
198+
199+
"""
200+
# Just to preload all information required to compute the task score.
201+
self.sql_session.query(Submission)\
202+
.filter(Submission.participation == participation)\
203+
.filter(Submission.task == task)\
204+
.options(joinedload(Submission.token))\
205+
.options(joinedload(Submission.results))\
206+
.all()
207+
data["task_public_score"], public_score_is_partial = \
208+
task_score(participation, task, public=True)
209+
data["task_tokened_score"], tokened_score_is_partial = \
210+
task_score(participation, task, only_tokened=True)
211+
# These two should be the same, anyway.
212+
data["task_score_is_partial"] = \
213+
public_score_is_partial or tokened_score_is_partial
214+
215+
score_type = task.active_dataset.score_type_object
216+
data["task_public_score_message"] = score_type.format_score(
217+
data["task_public_score"], score_type.max_public_score, None,
218+
task.score_precision, translation=self.translation)
219+
data["task_tokened_score_message"] = score_type.format_score(
220+
data["task_tokened_score"], score_type.max_score, None,
221+
task.score_precision, translation=self.translation)
222+
176223
@tornado.web.authenticated
177224
@actual_phase_required(0, 3)
178225
@multi_contest
@@ -186,7 +233,8 @@ def get(self, task_name, submission_num):
186233
raise tornado.web.HTTPError(404)
187234

188235
sr = submission.get_result(task.active_dataset)
189-
data = dict()
236+
237+
data = {}
190238

191239
if sr is None:
192240
# implicit compiling state while result is not created
@@ -204,6 +252,7 @@ def get(self, task_name, submission_num):
204252
data["status_text"] = self._("Scoring...")
205253
elif data["status"] == SubmissionResult.SCORED:
206254
data["status_text"] = self._("Evaluated")
255+
self.add_task_score(submission.participation, task, data)
207256

208257
score_type = task.active_dataset.score_type_object
209258
if score_type.max_public_score > 0:

cms/server/contest/static/cws_style.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,37 @@ td.token_rules p:last-child {
411411
clear: both;
412412
}
413413

414+
/*** Task score */
415+
416+
.task_score_container {
417+
display: flex;
418+
justify-content: space-evenly;
419+
font-weight: bold;
420+
}
421+
422+
.task_score {
423+
display: flex;
424+
flex-direction: column;
425+
align-items: center;
426+
margin-bottom: 0;
427+
}
428+
429+
.task_score .score img {
430+
margin-left: 5px;
431+
}
432+
433+
.task_score.score_0 {
434+
background-color: hsla(0, 100%, 50%, 0.4);
435+
}
436+
437+
.task_score.score_0_100 {
438+
background-color: hsla(60, 100%, 50%, 0.4);
439+
}
440+
441+
.task_score.score_100 {
442+
background-color: hsla(120, 100%, 50%, 0.4);
443+
}
444+
414445
/*** Submit a solution */
415446

416447
#submit_solution {

cms/server/contest/templates/submission_row.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
{% endif %}
9797
</td>
9898
{% endif %}
99-
{% if tokens_contest != TOKEN_MODE_DISABLED and tokens_task != TOKEN_MODE_DISABLED and actual_phase == 0 %}
99+
{% if can_use_tokens and actual_phase == 0 %}
100100
<td class="token">
101101
{% if s.token is not none %}
102102
<a class="btn disabled">{% trans %}Played{% endtrans %}</a>

cms/server/contest/templates/task_submissions.html

Lines changed: 119 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
{% set page = "task_submissions" %}
44

5+
{% set score_type = get_score_type(dataset=task.active_dataset) %}
6+
{% set can_use_tokens = tokens_contest != TOKEN_MODE_DISABLED
7+
and tokens_task != TOKEN_MODE_DISABLED %}
8+
59
{% block additional_js %}
610

711
$(document).on("click", "#submission_list tbody tr td.status .details", function (event) {
@@ -39,7 +43,45 @@
3943
}
4044
};
4145

42-
update_submission_row = function (submission_id, data) {
46+
/**
47+
* Update the score (public or tokened) in the UI (both in the task score and in
48+
* the submission row) given the results of a newly scored submisson
49+
*
50+
* score_elem: table cell with the submission score to update.
51+
* task_score_elem: container element of the task score to update.
52+
* score (Number): the score of the submission.
53+
* score_message (String): the score of the submission as a string.
54+
* task_score (Number): the current score of the task.
55+
* task_score_message (String): the current score of the task as a string.
56+
* task_score_is_partial (Boolean): if some submission has yet to be scored.
57+
* max_score (Number): maximum score of the task.
58+
*/
59+
update_score = function(score_elem, task_score_elem,
60+
score, score_message,
61+
task_score, task_score_message, task_score_is_partial,
62+
max_score) {
63+
// Submission row.
64+
if (score !== undefined) {
65+
score_elem.addClass(get_score_class(score, max_score));
66+
score_elem.removeClass("undefined");
67+
score_elem.text(score_message);
68+
}
69+
70+
// Task score.
71+
var task_score_span = task_score_elem.find(".score");
72+
task_score_span.text(task_score_message);
73+
if (task_score_is_partial) {
74+
task_score_span.append(
75+
$("<img class=\"details\" src=\"{{ url("static", "loading.gif") }}\"/>"));
76+
}
77+
task_score_elem.removeClass("undefined");
78+
task_score_elem.removeClass("score_0");
79+
task_score_elem.removeClass("score_0_100");
80+
task_score_elem.removeClass("score_100");
81+
task_score_elem.addClass(get_score_class(task_score, max_score));
82+
};
83+
84+
update_scores = function (submission_id, data) {
4385
var row = $("#submission_list tbody tr[data-submission=\"" + submission_id + "\"]");
4486
row.attr("data-status", data["status"]);
4587
row.children("td.status").text(data["status_text"]);
@@ -52,47 +94,48 @@
5294
$("<a class=\"details\">{% trans %}details{% endtrans %}</a>"));
5395
}
5496
if (data["status"] == {{ SubmissionResult.SCORED }}) {
55-
if (data["public_score"] != undefined) {
56-
row.children("td.public_score").addClass(get_score_class(data["public_score"], data["max_public_score"]));
57-
row.children("td.public_score").removeClass("undefined");
58-
row.children("td.public_score").html(data["public_score_message"]);
59-
}
60-
if (data["score"] != undefined) {
61-
// This element may not exist, if max_public_score == max_score.
62-
row.children("td.total_score").addClass(get_score_class(data["score"], data["max_score"]));
63-
row.children("td.total_score").removeClass("undefined");
64-
row.children("td.total_score").html(data["score_message"]);
65-
}
97+
update_score(
98+
row.children("td.public_score"), $("#task_score_public"),
99+
data["public_score"], data["public_score_message"],
100+
data["task_public_score"], data["task_public_score_message"],
101+
data["task_score_is_partial"], data["max_public_score"]);
102+
{% if can_use_tokens %}
103+
update_score(
104+
row.children("td.total_score"), $("#task_score_tokened"),
105+
data["score"], data["score_message"],
106+
data["task_tokened_score"], data["task_tokened_score_message"],
107+
data["task_score_is_partial"], data["max_score"]);
108+
{% endif %}
66109
} else if (data["status"] != {{ SubmissionResult.COMPILATION_FAILED }}) {
67-
schedule_update_submission_row(submission_id);
110+
schedule_update_scores(submission_id);
68111
}
69112
};
70113

71-
schedule_update_submission_row = function (submission_id) {
72-
if (typeof(schedule_update_submission_row.delays) === "undefined") {
73-
schedule_update_submission_row.delays = {};
114+
schedule_update_scores = function (submission_id) {
115+
if (typeof(schedule_update_scores.delays) === "undefined") {
116+
schedule_update_scores.delays = {};
74117
}
75-
if (!schedule_update_submission_row.delays[submission_id]) {
76-
schedule_update_submission_row.delays[submission_id] = 1000.0;
118+
if (!schedule_update_scores.delays[submission_id]) {
119+
schedule_update_scores.delays[submission_id] = 1000.0;
77120
} else {
78121
// We want exponential backoff, but slightly staggered across submissions to
79122
// avoid asking about all of them at the same time, so we use 1.4 plus a
80123
// value depending on the submission id.
81124
var hash = (37 * parseInt(submission_id)) % 100 / 100.0;
82-
schedule_update_submission_row.delays[submission_id] =
83-
schedule_update_submission_row.delays[submission_id]
125+
schedule_update_scores.delays[submission_id] =
126+
schedule_update_scores.delays[submission_id]
84127
* (1.4 + hash * 0.2);
85128
}
86129
setTimeout(function () {
87130
$.get(utils.contest_url("tasks", "{{ task.name }}", "submissions", submission_id), function (data) {
88-
update_submission_row(submission_id, data);
131+
update_scores(submission_id, data);
89132
});
90-
}, schedule_update_submission_row.delays[submission_id]);
133+
}, schedule_update_scores.delays[submission_id]);
91134
};
92135

93136
$(document).ready(function () {
94137
$('#submission_list tbody tr[data-status][data-status!="{{ SubmissionResult.COMPILATION_FAILED }}"][data-status!="{{ SubmissionResult.SCORED }}"]').each(function (idx, elem) {
95-
schedule_update_submission_row($(this).attr("data-submission"));
138+
schedule_update_scores($(this).attr("data-submission"));
96139
});
97140
});
98141

@@ -106,6 +149,59 @@
106149
<h1>{% trans name=task.title, short_name=task.name %}{{ name }} ({{ short_name }}) <small>submissions</small>{% endtrans %}</h1>
107150
</div>
108151

152+
{% if score_type is defined %}
153+
{% set two_task_scores = score_type.max_public_score > 0
154+
and score_type.max_public_score < score_type.max_score %}
155+
<div class="task_score_container row-fluid">
156+
157+
{% if score_type.max_public_score > 0 %}
158+
{# Show the public score (alone, if everything is public or tokens are disabled, or together with the tokened score). #}
159+
<div id="task_score_public"
160+
class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(public_score, score_type.max_public_score) }}">
161+
<span>
162+
{% if score_type.max_public_score == score_type.max_score %}
163+
{% trans %}Score:{% endtrans %}
164+
{% else %}
165+
{% trans %}Public score:{% endtrans %}
166+
{% endif %}
167+
</span>
168+
<br/>
169+
<span class="score">
170+
{{ score_type.format_score(public_score, score_type.max_public_score, none, task.score_precision, translation=translation) }}
171+
{% if is_score_partial %}
172+
<img src="{{ url("static", "loading.gif") }}" />
173+
{% endif %}
174+
</span>
175+
</div>
176+
{% endif %}
177+
178+
{% if score_type.max_public_score < score_type.max_score %}
179+
{# Show the tokened score (alone if everything is non-public, or together with the public score). #}
180+
<div id="task_score_tokened"
181+
class="{{ "span6" if two_task_scores else "span12" }} well well-small task_score {{ get_score_class(tokened_score, score_type.max_score) if can_use_tokens else "undefined" }}">
182+
<span>
183+
{% if can_use_tokens %}
184+
{% trans %}Score of tokened submissions:{% endtrans %}
185+
{% else %}
186+
{% trans %}Total score:{% endtrans %}
187+
{% endif %}
188+
</span>
189+
<br/>
190+
<span class="score">
191+
{% if can_use_tokens %}
192+
{{ score_type.format_score(tokened_score, score_type.max_score, none, task.score_precision, translation=translation) }}
193+
{% if is_score_partial %}
194+
<img src="{{ url("static", "loading.gif") }}" />
195+
{% endif %}
196+
{% else %}
197+
{% trans %}N/A{% endtrans %}
198+
{% endif %}
199+
</span>
200+
</div>
201+
{% endif %}
202+
203+
</div>
204+
{% endif %}
109205

110206
<h2 style="margin-bottom: 10px">{% trans %}Submit a solution{% endtrans %}</h2>
111207

@@ -229,8 +325,6 @@ <h2 style="margin: 40px 0 10px">{% trans %}Previous submissions{% endtrans %}</h
229325
{% endif %}
230326

231327

232-
{% set score_type = get_score_type(dataset=task.active_dataset) %}
233-
234328
{% set show_date = not submissions|map(attribute="timestamp")|all("today") %}
235329

236330

0 commit comments

Comments
 (0)