diff --git a/src/apps/api/serializers/competitions.py b/src/apps/api/serializers/competitions.py index d29ca7ba7..81e233523 100644 --- a/src/apps/api/serializers/competitions.py +++ b/src/apps/api/serializers/competitions.py @@ -17,6 +17,7 @@ from api.serializers.queues import QueueSerializer from datetime import datetime +from django.utils.timezone import now class PhaseSerializer(WritableNestedModelSerializer): @@ -92,9 +93,10 @@ def validate_leaderboard(self, value): class PhaseDetailSerializer(serializers.ModelSerializer): tasks = PhaseTaskInstanceSerializer(source='task_instances', many=True) status = serializers.SerializerMethodField() - public_data = DataDetailSerializer(read_only=True) starting_kit = DataDetailSerializer(read_only=True) + used_submissions_per_day = serializers.SerializerMethodField() + used_submissions_per_person = serializers.SerializerMethodField() class Meta: model = Phase @@ -117,6 +119,9 @@ class Meta: 'public_data', 'starting_kit', 'is_final_phase', + 'used_submissions_per_day', + 'used_submissions_per_person' + ) def get_status(self, obj): @@ -155,6 +160,34 @@ def get_status(self, obj): elif not phase_started: return Phase.NEXT + def get_used_submissions_per_day(self, obj): + + # Check if 'request' key exists in the context + if 'request' in self.context: + # Get user from the request + user = self.context['request'].user + if user.is_authenticated: + # Get all submissions which are not failed and belongs to this user for this phase + qs = obj.submissions.filter(owner=user, parent__isnull=True).exclude(status='Failed') + # Count submissions made today + daily_submission_count = qs.filter(created_when__day=now().day).count() + return daily_submission_count + return 0 + + def get_used_submissions_per_person(self, obj): + + # Check if 'request' key exists in the context + if 'request' in self.context: + # Get user from the request + user = self.context['request'].user + if user.is_authenticated: + # Get all submissions which are not failed and belongs to this user for this phase + qs = obj.submissions.filter(owner=user, parent__isnull=True).exclude(status='Failed') + # Count all submissions + total_submission_count = qs.count() + return total_submission_count + return 0 + class PhaseUpdateSerializer(PhaseSerializer): tasks = PhaseTaskInstanceSerializer(source='task_instances', many=True) diff --git a/src/apps/pages/views.py b/src/apps/pages/views.py index 7395d416a..bf32519b7 100644 --- a/src/apps/pages/views.py +++ b/src/apps/pages/views.py @@ -62,15 +62,45 @@ def get_context_data(self, *args, **kwargs): if not self.request.user.is_staff: raise HttpResponse(status=404) + show_child_submissions = self.request.GET.get('show_child_submissions', False) + qs = Submission.objects.all() qs = qs.filter(created_when__gte=now() - timedelta(days=2)) + if not show_child_submissions: + qs = qs.filter(parent__isnull=True) qs = qs.order_by('-created_when') qs = qs.select_related('phase__competition', 'owner') context = super().get_context_data(*args, **kwargs) context['submissions'] = qs[:250] + context['show_child_submissions'] = show_child_submissions + + for submission in context['submissions']: + # Get filesize from each submissions's data + submission.file_size = self.format_file_size(submission.data.file_size) + # Get queue from each submission's competition + queue_name = "*" if submission.phase.competition.queue is None else submission.phase.competition.queue.name + submission.competition_queue = queue_name + return context + def format_file_size(self, file_size): + """ + A custom function to convert file size to KB, MB, GB and return with the unit + """ + try: + n = float(file_size) + except ValueError: + return "" + + units = ['KB', 'MB', 'GB'] + i = 0 + while n >= 1000 and i < len(units) - 1: + n /= 1000 + i += 1 + + return f"{n:.1f} {units[i]}" + def page_not_found_view(request, exception): print(request) diff --git a/src/static/js/ours/latex_markdown_html.js b/src/static/js/ours/latex_markdown_html.js new file mode 100644 index 000000000..4a0e645c2 --- /dev/null +++ b/src/static/js/ours/latex_markdown_html.js @@ -0,0 +1,28 @@ +// Function to render Markdown, HTML and Latex and return updated content +function renderMarkdownWithLatex(content) { + if(content === null){ + return [] + } + const parsedHtml = new DOMParser().parseFromString(marked(content), "text/html") + + const traverseAndRenderLatex = (node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + const latexPattern = /\$\$([\s\S]*?)\$\$|\$([^\$\n]*?)\$/g + const hasLatex = latexPattern.test(node.textContent) + if (hasLatex) { + const tempDiv = document.createElement('div') + tempDiv.innerHTML = node.innerHTML.replace(latexPattern, (_, formula1, formula2) => { + const formula = formula1 || formula2 + return katex.renderToString(formula, { throwOnError: false }) + }); + node.innerHTML = tempDiv.innerHTML + } + } + + node.childNodes.forEach(traverseAndRenderLatex) + }; + + traverseAndRenderLatex(parsedHtml.body) + + return parsedHtml.body.childNodes +} \ No newline at end of file diff --git a/src/static/riot/competitions/detail/_registration.tag b/src/static/riot/competitions/detail/_registration.tag index 224341018..6bfe16511 100644 --- a/src/static/riot/competitions/detail/_registration.tag +++ b/src/static/riot/competitions/detail/_registration.tag @@ -72,7 +72,11 @@ CODALAB.events.on('competition_loaded', (competition) => { self.competition_id = competition.id if (self.refs.terms_content) { - self.refs.terms_content.innerHTML = render_markdown(competition.terms) + const rendered_content = renderMarkdownWithLatex(competition.terms) + self.refs.terms_content.innerHTML = "" + rendered_content.forEach(node => { + self.refs.terms_content.appendChild(node.cloneNode(true)); // Append each node + }); } self.registration_auto_approve = competition.registration_auto_approve self.status = competition.participant_status diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag index 09647588c..1a482839f 100644 --- a/src/static/riot/competitions/detail/_tabs.tag +++ b/src/static/riot/competitions/detail/_tabs.tag @@ -128,6 +128,7 @@
+
@@ -318,16 +319,21 @@ self.update() _.forEach(self.competition.pages, (page, index) => { - if (self.isHTML(page.content)){ - $(`#page_${index}`)[0].innerHTML = sanitize_HTML(page.content) - }else{ - $(`#page_${index}`)[0].innerHTML = render_markdown(page.content) - } - + // Render html pages + const rendered_content = renderMarkdownWithLatex(page.content) + $(`#page_${index}`)[0].innerHTML = "" + rendered_content.forEach(node => { + $(`#page_${index}`)[0].appendChild(node.cloneNode(true)); // Append each node + }); }) _.forEach(self.competition.phases, (phase, index) => { - $(`#phase_${index}`)[0].innerHTML = render_markdown(phase.description) + // Render phase description + const rendered_content = renderMarkdownWithLatex(phase.description) + $(`#phase_${index}`)[0].innerHTML = "" + rendered_content.forEach(node => { + $(`#phase_${index}`)[0].appendChild(node.cloneNode(true)); // Append each node + }); }) _.delay(() => { self.loading = false @@ -355,12 +361,6 @@ CODALAB.events.trigger('phase_selected', data) } } - // To check if page content has HTML - // Return true if content is html - // Return false if content is not html i.e. MarkDown - self.isHTML = function (page_content) { - return /<(?=.*? .*?\/ ?>|br|hr|input|!--|wbr)[a-z]+.*?>|<([a-z]+).*?<\/\1>/i.test(page_content); - } self.update() diff --git a/src/static/riot/competitions/detail/submission_limit.tag b/src/static/riot/competitions/detail/submission_limit.tag new file mode 100644 index 000000000..b8cc7dbe0 --- /dev/null +++ b/src/static/riot/competitions/detail/submission_limit.tag @@ -0,0 +1,94 @@ + +
+
+
+ Number of submissions used for the day +
+ + {selected_phase.used_submissions_per_day} out of {selected_phase.max_submissions_per_day} + +
+
+
+ Number of total submissions used +
+ + {selected_phase.used_submissions_per_person} out of {selected_phase.max_submissions_per_person} + +
+
+ + + +
diff --git a/src/static/riot/competitions/editor/_pages.tag b/src/static/riot/competitions/editor/_pages.tag index 45b56d6b2..369f7fcc0 100644 --- a/src/static/riot/competitions/editor/_pages.tag +++ b/src/static/riot/competitions/editor/_pages.tag @@ -159,7 +159,11 @@ self.view_page = function (page_index) { self.selected_page_index = page_index $(self.refs.view_modal).modal('show') - self.refs.page_content.innerHTML = render_markdown(self.pages[page_index].content) + const rendered_content = renderMarkdownWithLatex(self.pages[page_index].content) + self.refs.page_content.innerHTML = "" + rendered_content.forEach(node => { + self.refs.page_content.appendChild(node.cloneNode(true)); // Append each node + }); } self.form_updated = function () { diff --git a/src/templates/base.html b/src/templates/base.html index c65de9078..e6530429f 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -23,6 +23,9 @@ + + + {% block extra_head %} {% endblock %} @@ -239,6 +242,10 @@

CodaBench

+ + + + @@ -357,6 +364,7 @@

CodaBench

+ {% block extra_js %} {% endblock %} diff --git a/src/templates/pages/server_status.html b/src/templates/pages/server_status.html index 8e4bc172b..a6dbc741f 100644 --- a/src/templates/pages/server_status.html +++ b/src/templates/pages/server_status.html @@ -7,11 +7,24 @@ {% block content %}

Recent submissions (up to 250 or 2 days old)

+ + + + + {% if show_child_submissions %} + + {% endif %} + + @@ -26,7 +39,12 @@

Recent submissions (up to 250 or 2 days old)

- + {% if show_child_submissions %} + + {% endif %} + + + @@ -73,4 +91,22 @@

Monitor queues

+ + {% endblock %}
Competition Submission PKParent PKSize SubmitterQueue Hostname Submitted at Status
{{ submission.phase.competition.title }} {{ submission.pk }}{{ submission.owner.username }}{{ submission.parent.pk }}{{ submission.file_size }}{{ submission.owner.username }}{{ submission.competition_queue }} {{ submission.worker_hostname }} {{ submission.created_when|timesince }} ago {{ submission.status }}