From 72269ddd7137a3aa65530c0bd94e44a0e809da87 Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Wed, 16 Aug 2023 16:28:07 +0500 Subject: [PATCH 01/14] do not allow users to rerun all submissions if submissions are more than 200. Only super admin can rerun in this case. --- src/apps/api/views/competitions.py | 15 +++++++++++++++ .../competitions/detail/submission_manager.tag | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index 8fcb69a4b..b082a8997 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -583,13 +583,28 @@ def manually_migrate(self, request, pk): @action(detail=True, url_name='rerun_submissions') def rerun_submissions(self, request, pk): + + # Limit for rerunning submissions + RERUN_SUBMISSION_LIMIT = 200 + phase = self.get_object() comp = phase.competition + + # error when user is not super user or admin of the competition if request.user not in comp.all_organizers and not request.user.is_superuser: raise PermissionDenied('You do not have permission to re-run submissions') + + # Get submissions submissions = phase.submissions.all() + + # error when user is not super user and submissions crosses the limit + if not request.user.is_superuser and len(submissions) >= RERUN_SUBMISSION_LIMIT: + raise PermissionDenied('You have too many submissions, Contact us on `info@codalab.org` to request a rerun.') + + # rerun all submissions for submission in submissions: submission.re_run() + rerun_count = len(submissions) return Response({"count": rerun_count}) diff --git a/src/static/riot/competitions/detail/submission_manager.tag b/src/static/riot/competitions/detail/submission_manager.tag index 057cd089b..7b9aa43d7 100644 --- a/src/static/riot/competitions/detail/submission_manager.tag +++ b/src/static/riot/competitions/detail/submission_manager.tag @@ -284,6 +284,9 @@ toastr.success(`Rerunning ${response.count} submissions`) self.update_submissions() }) + .fail(function (response) { + toastr.error(response.responseJSON.detail) + }) } } self.filter = function () { From 5b638eacb42adf566877eebc8147b0580a58dde2 Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Wed, 16 Aug 2023 19:02:21 +0500 Subject: [PATCH 02/14] additional conditions added for queue owner and organizer --- src/apps/api/views/competitions.py | 63 ++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index b082a8997..b2449c07b 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -585,28 +585,69 @@ def manually_migrate(self, request, pk): def rerun_submissions(self, request, pk): # Limit for rerunning submissions - RERUN_SUBMISSION_LIMIT = 200 + RERUN_SUBMISSION_LIMIT = 30 phase = self.get_object() comp = phase.competition - # error when user is not super user or admin of the competition - if request.user not in comp.all_organizers and not request.user.is_superuser: - raise PermissionDenied('You do not have permission to re-run submissions') - # Get submissions submissions = phase.submissions.all() + can_re_run_submissions = False + error_message = "" + + # Super admin can rerun without any restrictions + if request.user.is_superuser: + can_re_run_submissions = True + + # competition admin can run only if + elif request.user in comp.all_organizers: + + # submissions are in limit + if len(submissions) <= RERUN_SUBMISSION_LIMIT: + can_re_run_submissions = True + + # submissions are not in limit + else: + # Codabemch public queue + if comp.queue is None: + can_re_run_submissions = False + error_message = f"You cannot rerun more than {RERUN_SUBMISSION_LIMIT} submissions on Codabench public queue! Contact us on `info@codalab.org` to request a rerun." + + # Other queue where user is not owner and not organizer + elif request.user != comp.queue.owner and request.user not in comp.queue.organizers.all(): + can_re_run_submissions = False + error_message = f"You cannot rerun more than {RERUN_SUBMISSION_LIMIT} submissions on a queue which is not yours! Contact us on `info@codalab.org` to request a rerun." + + # User can rerun submissions where he is owner or organizer + else: + can_re_run_submissions = True + + else: + can_re_run_submissions = False + error_message = 'You do not have permission to re-run submissions' + + # error when user is not super user or admin of the competition + if can_re_run_submissions: + # rerun all submissions + # for submission in submissions: + # submission.re_run() + rerun_count = len(submissions) + return Response({"count": rerun_count}) + else: + raise PermissionDenied(error_message) + + + + + + + # error when user is not super user and submissions crosses the limit if not request.user.is_superuser and len(submissions) >= RERUN_SUBMISSION_LIMIT: raise PermissionDenied('You have too many submissions, Contact us on `info@codalab.org` to request a rerun.') - # rerun all submissions - for submission in submissions: - submission.re_run() - - rerun_count = len(submissions) - return Response({"count": rerun_count}) + @swagger_auto_schema(responses={200: PhaseResultsSerializer}) @action(detail=True, methods=['GET']) From bd3beddc871925dfc1073e170534f94ccbe66990 Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Wed, 16 Aug 2023 19:07:28 +0500 Subject: [PATCH 03/14] empty lines removed --- src/apps/api/views/competitions.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index b2449c07b..6902a8296 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -637,18 +637,6 @@ def rerun_submissions(self, request, pk): else: raise PermissionDenied(error_message) - - - - - - - # error when user is not super user and submissions crosses the limit - if not request.user.is_superuser and len(submissions) >= RERUN_SUBMISSION_LIMIT: - raise PermissionDenied('You have too many submissions, Contact us on `info@codalab.org` to request a rerun.') - - - @swagger_auto_schema(responses={200: PhaseResultsSerializer}) @action(detail=True, methods=['GET']) def get_leaderboard(self, request, pk): From 66d66b4cae07cc601a2db20e2a8add1ec0ea5e5b Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Wed, 16 Aug 2023 19:22:39 +0500 Subject: [PATCH 04/14] rerun code uncommented --- src/apps/api/views/competitions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index 6902a8296..5602204a0 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -630,8 +630,8 @@ def rerun_submissions(self, request, pk): # error when user is not super user or admin of the competition if can_re_run_submissions: # rerun all submissions - # for submission in submissions: - # submission.re_run() + for submission in submissions: + submission.re_run() rerun_count = len(submissions) return Response({"count": rerun_count}) else: From f92e15f209619ea65da8beef926c173f9f59cf35 Mon Sep 17 00:00:00 2001 From: bbearce Date: Sun, 10 Sep 2023 20:54:39 -0400 Subject: [PATCH 05/14] blessings for shell_plus --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 5a14473b8..291f4d483 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,6 +26,7 @@ bleach==3.1.4 # Heroku staging debug tools django-debug-toolbar==3.2 django-querycount==0.7.0 +blessings==1.7 # User impersonation django-su==0.9.0 From 318db685e69ab5860ca7aec22f37b4a44cd0ac34 Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Mon, 11 Sep 2023 18:57:02 +0500 Subject: [PATCH 06/14] http instead of https --- src/utils/context_processors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/context_processors.py b/src/utils/context_processors.py index 74213b888..4b6d77fab 100644 --- a/src/utils/context_processors.py +++ b/src/utils/context_processors.py @@ -21,6 +21,6 @@ def common_settings(request): return { 'STORAGE_TYPE': settings.STORAGE_TYPE, 'USER_JSON_DATA': json.dumps(user_json_data), - 'RABBITMQ_MANAGEMENT_URL': f"https://{settings.DOMAIN_NAME}:{settings.RABBITMQ_MANAGEMENT_PORT}", - 'FLOWER_URL': f"https://{settings.DOMAIN_NAME}:{settings.FLOWER_PUBLIC_PORT}", + 'RABBITMQ_MANAGEMENT_URL': f"http://{settings.DOMAIN_NAME}:{settings.RABBITMQ_MANAGEMENT_PORT}", + 'FLOWER_URL': f"http://{settings.DOMAIN_NAME}:{settings.FLOWER_PUBLIC_PORT}", } From 438de4d1911e251d132f0612a3d2d4d43859c0d6 Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Thu, 14 Sep 2023 16:08:28 +0500 Subject: [PATCH 07/14] task update and delete option visible to owner only --- src/apps/api/serializers/tasks.py | 1 + src/static/riot/tasks/management.tag | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/apps/api/serializers/tasks.py b/src/apps/api/serializers/tasks.py index b98ce36ea..1526a1980 100644 --- a/src/apps/api/serializers/tasks.py +++ b/src/apps/api/serializers/tasks.py @@ -138,6 +138,7 @@ class Meta: fields = ( 'id', 'created_when', + 'created_by', 'key', 'name', 'solutions', diff --git a/src/static/riot/tasks/management.tag b/src/static/riot/tasks/management.tag index 5a613b66a..06d24bb2f 100644 --- a/src/static/riot/tasks/management.tag +++ b/src/static/riot/tasks/management.tag @@ -45,15 +45,17 @@ - - +
+ + +
-
+
From 2fce7d6eff5f73f242a5ae5c3fd56b86b300ce39 Mon Sep 17 00:00:00 2001 From: didayolo Date: Thu, 14 Sep 2023 13:50:25 +0200 Subject: [PATCH 08/14] Add help in Files table --- src/static/riot/competitions/detail/_tabs.tag | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag index dccee3d94..74ba34972 100644 --- a/src/static/riot/competitions/detail/_tabs.tag +++ b/src/static/riot/competitions/detail/_tabs.tag @@ -60,7 +60,12 @@ Phase Task Type - Available + Available + + + Size From b568f68cf5bce8b2e24a885ca657844347baf34d Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Mon, 18 Sep 2023 01:38:42 +0500 Subject: [PATCH 09/14] compute worker crash handled: docker image pull and execution time limit --- compute_worker/compute_worker.py | 104 +++++++++++++++++++++++++++--- src/apps/api/views/submissions.py | 44 ++++++++++++- 2 files changed, 137 insertions(+), 11 deletions(-) diff --git a/compute_worker/compute_worker.py b/compute_worker/compute_worker.py index 875051090..5df5995eb 100644 --- a/compute_worker/compute_worker.py +++ b/compute_worker/compute_worker.py @@ -26,9 +26,12 @@ from kombu import Queue, Exchange from urllib3 import Retry - logger = logging.getLogger() + +# ----------------------------------------------- +# Celery + Rabbit MQ +# ----------------------------------------------- # Init celery + rabbit queue definitions app = Celery() app.config_from_object('celery_config') # grabs celery_config.py @@ -38,6 +41,9 @@ ] +# ----------------------------------------------- +# Directories +# ----------------------------------------------- # Setup base directories used by all submissions # note: we need to pass this directory to docker-compose so it knows where to store things! HOST_DIRECTORY = os.environ.get("HOST_DIRECTORY", "/tmp/codabench/") @@ -45,6 +51,10 @@ CACHE_DIR = os.path.join(BASE_DIR, "cache") MAX_CACHE_DIR_SIZE_GB = float(os.environ.get('MAX_CACHE_DIR_SIZE_GB', 10)) + +# ----------------------------------------------- +# Submission status +# ----------------------------------------------- # Status options for submissions STATUS_NONE = "None" STATUS_SUBMITTING = "Submitting" @@ -65,6 +75,10 @@ STATUS_FAILED, ) + +# ----------------------------------------------- +# Container Engine +# ----------------------------------------------- # Setup the container engine that we are using if os.environ.get("CONTAINER_ENGINE_EXECUTABLE"): CONTAINER_ENGINE_EXECUTABLE = os.environ.get("CONTAINER_ENGINE_EXECUTABLE") @@ -75,9 +89,18 @@ CONTAINER_ENGINE_EXECUTABLE = "docker" +# ----------------------------------------------- +# Exceptions +# ----------------------------------------------- class SubmissionException(Exception): pass +class DockerImagePullException(Exception): + pass + +class ExecutionTimeLimitExceeded(Exception): + pass + # ----------------------------------------------------------------------------- # The main compute worker entrypoint, this is how a job is ran at the highest @@ -94,6 +117,8 @@ def run_wrapper(run_args): if run.is_scoring: run.push_scores() run.push_output() + except DockerImagePullException as e: + run._update_status(STATUS_FAILED, str(e)) except SubmissionException as e: run._update_status(STATUS_FAILED, str(e)) except SoftTimeLimitExceeded: @@ -160,14 +185,14 @@ def is_valid_zip(zip_path): return False -class ExecutionTimeLimitExceeded(Exception): - pass - - def alarm_handler(signum, frame): raise ExecutionTimeLimitExceeded +# ----------------------------------------------- +# Class Run +# Respnosible for running a submission inside a docker container +# ----------------------------------------------- class Run: """A "Run" in Codalab is composed of some program, some data to work with, and some signed URLs to upload results to. There is also a secret key to do special commands for just this submission. @@ -340,8 +365,55 @@ def _get_container_image(self, image_name): container_engine_pull = check_output(cmd) logger.info("Pull complete for image: {0} with output of {1}".format(image_name, container_engine_pull)) except CalledProcessError: - logger.info("Pull for image: {} returned a non-zero exit code!") - raise SubmissionException(f"Pull for {image_name} failed!") + error_message = f"Pull for image: {image_name} returned a non-zero exit code! Check if the docker image exists on docker hub." + logger.info(error_message) + # Prepare data to be sent to submissions api + docker_pull_fail_data = { + "type": "Docker_Image_Pull_Fail", + "error_message": error_message, + } + # Send data to be written to ingestion logs + self._update_submission(docker_pull_fail_data) + # Send error through web socket to the frontend + asyncio.run(self._send_data_through_socket(error_message)) + raise DockerImagePullException(f"Pull for {image_name} failed!") + + async def _send_data_through_socket(self, error_message): + """ + This function gets an error messages and sends it through a web socket. This function is used for sending + - Docker image pull failure logs + - Execution time limit exceeded logs + """ + logger.info(f"Connecting to {self.websocket_url} to send docker image pull error") + + # connect to web socket + websocket = await websockets.connect(self.websocket_url) + + # define websocket errors + websocket_errors = (socket.gaierror, websockets.WebSocketException, websockets.ConnectionClosedError, ConnectionRefusedError) + + try: + # send message + await websocket.send(json.dumps({ + "kind": "stderr", + "message": error_message + })) + + except websocket_errors: + # handle websocket errors + logger.info(f"Error sending failed through websocket") + try: + await websocket.close() + except Exception as e: + logger.error(e) + else: + # no error in websocket message sending + logger.info(f"Error sent successfully through websocket") + + logger.info(f"Disconnecting from websocket {self.websocket_url}") + + # close websocket + await websocket.close() def _get_bundle(self, url, destination, cache=True): """Downloads zip from url and unzips into destination. If cache=True then url is hashed and checked @@ -384,7 +456,7 @@ def _get_bundle(self, url, destination, cache=True): raise # Re-raise the last caught BadZipFile exception else: logger.info("Failed. Retrying in 60 seconds...") - time.sleep(60) # Wait 60 seconds before retrying + time.sleep(60) # Wait 60 seconds before retrying # Return the zip file path for other uses, e.g. for creating a MD5 hash to identify it return bundle_file @@ -627,7 +699,7 @@ def _put_file(self, url, file=None, raw_data=None, content_type='application/zip """ if file and raw_data: raise Exception("Cannot put both a file and raw_data") - + headers = { # For Azure only, other systems ignore these headers 'x-ms-blob-type': 'BlockBlob', @@ -731,7 +803,19 @@ def start(self): try: loop.run_until_complete(gathered_tasks) except ExecutionTimeLimitExceeded: - raise SubmissionException(f"Execution Time Limit exceeded. Limit was {self.execution_time_limit} seconds") + error_message = f"Execution Time Limit exceeded. Limit was {self.execution_time_limit} seconds" + logger.info(error_message) + # Prepare data to be sent to submissions api + execution_time_limit_exceeded_data = { + "type": "Execution_Time_Limit_Exceeded", + "error_message": error_message, + "is_scoring": self.is_scoring + } + # Send data to be written to ingestion/scoring std_err + self._update_submission(execution_time_limit_exceeded_data) + # Send error through web socket to the frontend + asyncio.run(self._send_data_through_socket(error_message)) + raise SubmissionException(error_message) finally: self.watch = False for kind, logs in self.logs.items(): diff --git a/src/apps/api/views/submissions.py b/src/apps/api/views/submissions.py index d4d82fba2..6ce9da84b 100644 --- a/src/apps/api/views/submissions.py +++ b/src/apps/api/views/submissions.py @@ -1,5 +1,6 @@ import json import uuid +import logging from django.db.models import Q from django_filters.rest_framework import DjangoFilterBackend @@ -14,14 +15,17 @@ from rest_framework.settings import api_settings from rest_framework.viewsets import ModelViewSet from rest_framework_csv import renderers +from django.core.files.base import ContentFile from profiles.models import Organization, Membership from tasks.models import Task from api.serializers.submissions import SubmissionCreationSerializer, SubmissionSerializer, SubmissionFilesSerializer -from competitions.models import Submission, Phase, CompetitionParticipant +from competitions.models import Submission, SubmissionDetails, Phase, CompetitionParticipant from leaderboards.strategies import put_on_leaderboard_by_submission_rule from leaderboards.models import SubmissionScore, Column, Leaderboard +logger = logging.getLogger() + class SubmissionViewSet(ModelViewSet): queryset = Submission.objects.all().order_by('-pk') @@ -50,6 +54,44 @@ def check_object_permissions(self, request, obj): hostname = request.data['status_details'].replace('scoring_hostname-', '') obj.scoring_worker_hostname = hostname obj.save() + + # check if type is in request data. type can have the following values + # - Docker_Image_Pull_Fail + # - Execution_Time_Limit_Exceeded + if "type" in self.request.data.keys(): + + if request.data["type"] in ["Docker_Image_Pull_Fail", "Execution_Time_Limit_Exceeded"]: + + # Get the error message + error_message = request.data['error_message'] + + # Set file name to ingestion std error as default + error_file_name = "prediction_ingestion_stderr" + + # Change error file name when error comes from execution time limit + # and error occured during scoring + if request.data["type"] == "Execution_Time_Limit_Exceeded" and request.data['is_scoring'] == "True": + error_file_name = "scoring_stderr" + + try: + # Get submission detail for this submission + submission_detail = SubmissionDetails.objects.get( + name=error_file_name, + submission=obj, + ) + + # Read the existing content from the file + existing_content = submission_detail.data_file.read().decode("utf-8") + + # Append the new error message to the existing content + modified_content = existing_content + "\n" + error_message + + # write error message to the file + submission_detail.data_file.save(submission_detail.data_file.name, ContentFile(modified_content.encode("utf-8"))) + + except SubmissionDetails.DoesNotExist: + logger.warning("SubmissionDetails object not found.") + not_bot_user = self.request.user.is_authenticated and not self.request.user.is_bot if self.action in ['update_fact_sheet', 're_run_submission']: From 0e1511e975e80d145ea77b779d8f1a7e9eb4279b Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Mon, 18 Sep 2023 21:25:50 +0500 Subject: [PATCH 10/14] env variable added --- .env_sample | 8 ++++++++ src/apps/api/views/competitions.py | 10 ++++------ src/settings/base.py | 7 +++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.env_sample b/.env_sample index ce6916ef1..84a0888dc 100644 --- a/.env_sample +++ b/.env_sample @@ -59,6 +59,14 @@ AWS_STORAGE_PRIVATE_BUCKET_NAME=private AWS_S3_ENDPOINT_URL=http://minio:9000/ AWS_QUERYSTRING_AUTH=False +# ----------------------------------------------------------------------------- +# Limit for re-running submission +# This is used to limit users to rerun submissions +# on default queue when number of submissions are < RERUN_SUBMISSION_LIMIT +# ----------------------------------------------------------------------------- +RERUN_SUBMISSION_LIMIT=30 + + # # S3 storage example # STORAGE_TYPE=s3 # AWS_ACCESS_KEY_ID=12312312312312312331223 diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index 5602204a0..a68052f5b 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -38,6 +38,7 @@ from api.permissions import IsOrganizerOrCollaborator from datetime import datetime from django.db import transaction +from django.conf import settings class CompetitionViewSet(ModelViewSet): @@ -584,9 +585,6 @@ def manually_migrate(self, request, pk): @action(detail=True, url_name='rerun_submissions') def rerun_submissions(self, request, pk): - # Limit for rerunning submissions - RERUN_SUBMISSION_LIMIT = 30 - phase = self.get_object() comp = phase.competition @@ -604,7 +602,7 @@ def rerun_submissions(self, request, pk): elif request.user in comp.all_organizers: # submissions are in limit - if len(submissions) <= RERUN_SUBMISSION_LIMIT: + if len(submissions) <= settings.RERUN_SUBMISSION_LIMIT: can_re_run_submissions = True # submissions are not in limit @@ -612,12 +610,12 @@ def rerun_submissions(self, request, pk): # Codabemch public queue if comp.queue is None: can_re_run_submissions = False - error_message = f"You cannot rerun more than {RERUN_SUBMISSION_LIMIT} submissions on Codabench public queue! Contact us on `info@codalab.org` to request a rerun." + error_message = f"You cannot rerun more than {settings.RERUN_SUBMISSION_LIMIT} submissions on Codabench public queue! Contact us on `info@codalab.org` to request a rerun." # Other queue where user is not owner and not organizer elif request.user != comp.queue.owner and request.user not in comp.queue.organizers.all(): can_re_run_submissions = False - error_message = f"You cannot rerun more than {RERUN_SUBMISSION_LIMIT} submissions on a queue which is not yours! Contact us on `info@codalab.org` to request a rerun." + error_message = f"You cannot rerun more than {settings.RERUN_SUBMISSION_LIMIT} submissions on a queue which is not yours! Contact us on `info@codalab.org` to request a rerun." # User can rerun submissions where he is owner or organizer else: diff --git a/src/settings/base.py b/src/settings/base.py index 1366a0cc8..b0751758e 100644 --- a/src/settings/base.py +++ b/src/settings/base.py @@ -447,3 +447,10 @@ # Django-Su (User impersonation) SU_LOGIN_CALLBACK = 'profiles.admin.su_login_callback' AJAX_LOOKUP_CHANNELS = {'django_su': dict(model='profiles.User', search_field='username')} + +# ============================================================================= +# Limit for re-running submission +# This is used to limit users to rerun submissions +# on default queue when number of submissions are < RERUN_SUBMISSION_LIMIT +# ============================================================================= +RERUN_SUBMISSION_LIMIT = os.environ.get('RERUN_SUBMISSION_LIMIT', 30) From 99c14b9e61a5c2cf9cdc84f344761e0626f2ae57 Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Tue, 19 Sep 2023 00:00:54 +0500 Subject: [PATCH 11/14] Phase dates ui bug removed --- .../riot/competitions/editor/_phases.tag | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/static/riot/competitions/editor/_phases.tag b/src/static/riot/competitions/editor/_phases.tag index 748c2609f..002bdf7a5 100644 --- a/src/static/riot/competitions/editor/_phases.tag +++ b/src/static/riot/competitions/editor/_phases.tag @@ -389,6 +389,25 @@ $(self.refs.modal).modal('hide') } + self.formatDateToYYYYMMDD = function(input) { + // This function formats date in the format YYYY-MM-DD + + // convert input to date + var dateObject = new Date(input) + + // check if date has a time + if (!isNaN(dateObject.getTime())) { + // Extract year + var year = dateObject.getFullYear() + // Extract Month + var month = (dateObject.getMonth() + 1).toString().padStart(2, '0') + // Extract day + var day = dateObject.getDate().toString().padStart(2, '0') + return `${year}-${month}-${day}` + } + return input + } + self.form_updated = function () { // This checks phases overall to make sure they are ready to go var is_valid = true @@ -406,9 +425,8 @@ }) _.forEach(_.range(self.phases.length), i => { if (i !== 0) { - let end = Date.parse(self.phases[i - 1].end) - let start = Date.parse(self.phases[i].start) - + let end = Date.parse(self.formatDateToYYYYMMDD(self.phases[i - 1].end)) + let start = Date.parse(self.formatDateToYYYYMMDD(self.phases[i].start)) if (end > start || !end) { let message = `Phase "${_.get(self.phases[i], 'name', i + 1)}" must start after phase "${_.get(self.phases[i - 1], 'name', i)}" ends` if (!self.warnings.includes(message)) { From 9b0d3ad3c9af3e986cabd92e521594bd4827582e Mon Sep 17 00:00:00 2001 From: dtuantran Date: Tue, 19 Sep 2023 16:01:44 +0200 Subject: [PATCH 12/14] cast str to int --- src/apps/api/views/competitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index a68052f5b..095eb7c9b 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -602,7 +602,7 @@ def rerun_submissions(self, request, pk): elif request.user in comp.all_organizers: # submissions are in limit - if len(submissions) <= settings.RERUN_SUBMISSION_LIMIT: + if len(submissions) <= int(settings.RERUN_SUBMISSION_LIMIT): can_re_run_submissions = True # submissions are not in limit From a93810c7ffd2cc89f8ad447f30dcb96be6b538b8 Mon Sep 17 00:00:00 2001 From: dtuantran Date: Wed, 20 Sep 2023 11:26:51 +0200 Subject: [PATCH 13/14] Update docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e4e9e8180..22311bb68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -126,7 +126,7 @@ services: # Rabbitmq & Flower monitoring tool #----------------------------------------------- rabbit: - image: rabbitmq:3.6-management + image: rabbitmq:management # setting hostname here makes data persist properly between # containers being destroyed..! hostname: rabbit From 134df42f9ee2c1ebc8be1f649270e3b8dd6c75ab Mon Sep 17 00:00:00 2001 From: Ihsan Ullah Date: Fri, 22 Sep 2023 19:43:15 +0500 Subject: [PATCH 14/14] clickable report link added --- src/static/img/paper.png | Bin 0 -> 3690 bytes src/static/riot/competitions/public-list.tag | 36 ++++++++++-------- .../competitions/tile/competition_tile.tag | 18 ++++++--- 3 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 src/static/img/paper.png diff --git a/src/static/img/paper.png b/src/static/img/paper.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf0231d8d43f6c174c32ae2fa2c1ff0f6c98264 GIT binary patch literal 3690 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 literal 0 HcmV?d00001 diff --git a/src/static/riot/competitions/public-list.tag b/src/static/riot/competitions/public-list.tag index 3b51ddbc9..8a3e09683 100644 --- a/src/static/riot/competitions/public-list.tag +++ b/src/static/riot/competitions/public-list.tag @@ -8,30 +8,33 @@
@@ -142,7 +145,10 @@ margin auto .link-no-deco + all unset text-decoration none + cursor pointer + width 100% .tile-wrapper border solid 1px gainsboro diff --git a/src/static/riot/competitions/tile/competition_tile.tag b/src/static/riot/competitions/tile/competition_tile.tag index 5d2dbbf7b..5c4fdcb7e 100644 --- a/src/static/riot/competitions/tile/competition_tile.tag +++ b/src/static/riot/competitions/tile/competition_tile.tag @@ -1,9 +1,10 @@ - +
+

{title} @@ -15,14 +16,17 @@ Organized by: {created_by}

-
+ +
{pretty_date(created_when)} -
-
+
+
+ + +
{participant_count} Participants
-