Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
96b2d1d
Update version.json for release 1.17.0
actions-user Feb 10, 2025
e7c007f
Merge pull request #1746 from codalab/update-version-1.17.0
Didayolo Feb 10, 2025
9872f6b
Updated release-version PR title to distinguish from other PRs (#1747)
ihsaan-ullah Feb 11, 2025
fd77f26
styling added for featured comps, featured comps moved to popular and…
ihsaan-ullah Feb 13, 2025
a1c0306
unused comments removed
ihsaan-ullah Feb 13, 2025
6f7a484
resolved issue that made the page tabs unclickable
ihsaan-ullah Feb 13, 2025
0a1fd31
Merge pull request #1751 from codalab/competition_pages_click
Didayolo Feb 13, 2025
10655d5
Fix recent competitions
Didayolo Feb 13, 2025
44c7d7f
Merge pull request #1750 from codalab/home_page_competitions
Didayolo Feb 13, 2025
b5e9aec
task list pagination issues resolved
ihsaan-ullah Feb 19, 2025
d97e3ef
condition added to avoid double approve or deny participants status
ihsaan-ullah Feb 19, 2025
90f666d
results api fixed
ihsaan-ullah Feb 20, 2025
152c56b
Merge pull request #1757 from codalab/competition_results_api
Didayolo Feb 20, 2025
367f94a
Merge pull request #1754 from codalab/task_list
Didayolo Feb 20, 2025
6374a70
Merge pull request #1756 from codalab/accept_participant
Didayolo Feb 20, 2025
5108700
exclude submissions from the leaderboard that are soft deleted
ihsaan-ullah Mar 5, 2025
0d4970f
Merge pull request #1770 from codalab/soft_delete_submission_update
Didayolo Mar 7, 2025
ec6314f
show server error instead of fronend parsing error
ihsaan-ullah Mar 13, 2025
da3ac84
show back button in edit only. Show help in create only
ihsaan-ullah Mar 14, 2025
fd27649
do not allow special chars in usernames
ihsaan-ullah Mar 14, 2025
be03c81
Merge pull request #1781 from codalab/submission_upload
Didayolo Mar 20, 2025
7203cae
Enable/Disable competition forum (#1774)
ihsaan-ullah Mar 21, 2025
087232d
Merge pull request #1783 from codalab/create_comp_ui
Didayolo Mar 21, 2025
197f334
Merge pull request #1784 from codalab/username_special_chars
Didayolo Mar 21, 2025
661a680
Email in lowercase (#1769)
ihsaan-ullah Mar 21, 2025
5b3fbd8
latest competition fields added to dump (#1786)
ihsaan-ullah Mar 21, 2025
2883349
User quota is updated to GB from Bytes (#1749)
ihsaan-ullah Mar 23, 2025
68a1e08
File Sizes cleanup (#1752)
ihsaan-ullah Mar 24, 2025
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
4 changes: 2 additions & 2 deletions .github/workflows/release-version-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ jobs:
curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/pulls \
-d '{
"title": "Update version.json for release ${{ env.tag_name }}",
"body": "This PR updates version.json with the latest release information.",
"title": "[RELEASE ${{ env.tag_name }}] Update version.json for release ${{ env.tag_name }}",
"body": "This PR updates version.json with the latest release information: ${{ env.tag_name }}",
"head": "update-version-${{ env.tag_name }}",
"base": "develop"
}'
2 changes: 1 addition & 1 deletion compute_worker/compute_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def get_folder_size_in_gb(folder):
total_size += os.path.getsize(path)
elif os.path.isdir(path):
total_size += get_folder_size_in_gb(path)
return total_size / 1024 / 1024 / 1024
return total_size / 1000 / 1000 / 1000 # GB: decimal system (1000^3)


def delete_files_in_folder(folder):
Expand Down
18 changes: 18 additions & 0 deletions src/apps/analytics/migrations/0002_auto_20250218_1143.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2025-02-18 11:43

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('analytics', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='adminstoragedatapoint',
name='backups_total',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True),
),
]
4 changes: 2 additions & 2 deletions src/apps/analytics/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class UserStorageDataPoint(models.Model):

class AdminStorageDataPoint(models.Model):
backups_total = models.DecimalField(
max_digits=14, decimal_places=2, null=True, blank=True
)
max_digits=20, decimal_places=2, null=True, blank=True
) # stores bytes
at_date = models.DateTimeField()
created_at = models.DateTimeField(auto_now_add=True)
24 changes: 12 additions & 12 deletions src/apps/analytics/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ def create_storage_analytics_snapshot():
for dataset in Data.objects.filter(Q(file_size__isnull=True) | Q(file_size__lt=0)):
try:
dataset.file_size = Decimal(
dataset.data_file.size / 1024
) # file_size is in KiB
dataset.data_file.size
) # file_size is in Bytes
except Exception:
dataset.file_size = Decimal(-1)
finally:
Expand All @@ -56,8 +56,8 @@ def create_storage_analytics_snapshot():
):
try:
submission.prediction_result_file_size = Decimal(
submission.prediction_result.size / 1024
) # prediction_result_file_size is in KiB
submission.prediction_result.size
) # prediction_result_file_size is in Bytes
except Exception:
submission.prediction_result_file_size = Decimal(-1)
finally:
Expand All @@ -68,8 +68,8 @@ def create_storage_analytics_snapshot():
):
try:
submission.scoring_result_file_size = Decimal(
submission.scoring_result.size / 1024
) # scoring_result_file_size is in KiB
submission.scoring_result.size
) # scoring_result_file_size is in Bytes
except Exception:
submission.scoring_result_file_size = Decimal(-1)
finally:
Expand All @@ -80,8 +80,8 @@ def create_storage_analytics_snapshot():
):
try:
submission.detailed_result_file_size = Decimal(
submission.detailed_result.size / 1024
) # detailed_result_file_size is in KiB
submission.detailed_result.size
) # detailed_result_file_size is in Bytes
except Exception:
submission.detailed_result_file_size = Decimal(-1)
finally:
Expand All @@ -92,8 +92,8 @@ def create_storage_analytics_snapshot():
):
try:
submissiondetails.file_size = Decimal(
submissiondetails.data_file.size / 1024
) # file_size is in KiB
submissiondetails.data_file.size
) # file_size is in Bytes
except Exception:
submissiondetails.file_size = Decimal(-1)
finally:
Expand Down Expand Up @@ -277,7 +277,7 @@ def create_storage_analytics_snapshot():
admin_storage_at_date[date] += size

for date in admin_storage_day_range:
defaults = {"backups_total": admin_storage_at_date[date] / 1024.0}
defaults = {"backups_total": admin_storage_at_date[date]}
lookup_params = {"at_date": date}
AdminStorageDataPoint.objects.update_or_create(
defaults=defaults, **lookup_params
Expand Down Expand Up @@ -528,7 +528,7 @@ def create_storage_analytics_snapshot():
)
admin_data_point = AdminStorageDataPoint.objects.filter(at_date=date).first()
admin_usage = (admin_data_point.backups_total or 0) if admin_data_point else 0
orphaned_file_usage = Decimal(orphaned_files_size_per_date[date] / 1024)
orphaned_file_usage = Decimal(orphaned_files_size_per_date[date])
total_usage = (
users_usage + admin_usage + orphaned_file_usage
) # competitions_usage is included inside users_usage
Expand Down
7 changes: 5 additions & 2 deletions src/apps/api/serializers/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ class Meta:
'reward',
'contact_email',
'report',
'whitelist_emails'
'whitelist_emails',
'forum_enabled'
)

def validate_phases(self, phases):
Expand Down Expand Up @@ -391,6 +392,7 @@ class Meta:
'contact_email',
'report',
'whitelist_emails',
'forum_enabled'
)

def get_leaderboards(self, instance):
Expand Down Expand Up @@ -449,6 +451,7 @@ class Meta:
'reward',
'contact_email',
'report',
'is_featured',
)

def get_created_by(self, obj):
Expand Down Expand Up @@ -494,7 +497,7 @@ class Meta:

class FrontPageCompetitionsSerializer(serializers.Serializer):
popular_comps = CompetitionSerializerSimple(many=True)
featured_comps = CompetitionSerializerSimple(many=True)
recent_comps = CompetitionSerializerSimple(many=True)


class PhaseResultsSubmissionSerializer(serializers.Serializer):
Expand Down
1 change: 1 addition & 0 deletions src/apps/api/serializers/leaderboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def get_submissions(self, instance):
ordering = [f'{"-" if primary_col.sorting == "desc" else ""}primary_col']
submissions = Submission.objects.filter(
phase=instance,
is_soft_deleted=False,
has_children=False,
is_specific_task_re_run=False,
leaderboard__isnull=False, ) \
Expand Down
21 changes: 15 additions & 6 deletions src/apps/api/tests/test_datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework.test import APITestCase
from datasets.models import Data
from factories import UserFactory, DataFactory
from utils.data import pretty_bytes
from utils.data import pretty_bytes, gb_to_bytes


faker = Factory.create()
Expand All @@ -12,7 +12,7 @@
class DatasetAPITests(APITestCase):
def setUp(self):
self.creator = UserFactory(username='creator', password='creator')
self.existing_dataset = DataFactory(created_by=self.creator, name="Test!", file_size=1024)
self.existing_dataset = DataFactory(created_by=self.creator, name="Test!", file_size=1000)

def test_dataset_api_checks_duplicate_names_for_same_user(self):
self.client.login(username='creator', password='creator')
Expand All @@ -23,7 +23,7 @@ def test_dataset_api_checks_duplicate_names_for_same_user(self):
'type': Data.COMPETITION_BUNDLE,
'request_sassy_file_name': faker.file_name(),
'file_name': faker.file_name(),
'file_size': 1024,
'file_size': 1000,
})

assert resp.status_code == 400
Expand All @@ -34,7 +34,7 @@ def test_dataset_api_checks_duplicate_names_for_same_user(self):
'name': 'Test!',
'type': Data.COMPETITION_BUNDLE,
'request_sassy_file_name': faker.file_name(),
'file_size': 1024,
'file_size': 1000,
})
assert resp.status_code == 200

Expand All @@ -50,10 +50,19 @@ def test_dataset_api_checks_for_authentication(self):
def test_dataset_api_check_quota(self):
self.client.login(username='creator', password='creator')

# User quota is in GB
quota = float(self.creator.quota)
# Convert to bytes to compute available space
quota = gb_to_bytes(quota)
# Used storage is in bytes
storage_used = float(self.creator.get_used_storage_space())

available_space = quota - storage_used
file_size = 1024 * 1024 * 1024 * 1024

# 1 GB = 1,000,000,000 Bytes
# 1 TB = 1,000 GB = 1,000,000,000,000 Bytes
# Using a big file size of 1 TB to run the test
file_size = 1000 * 1000 * 1000 * 1000

# Fake upload a very big dataset
resp = self.client.post(reverse("data-list"), {
Expand All @@ -68,7 +77,7 @@ def test_dataset_api_check_quota(self):
assert resp.data["data_file"][0] == f'Insufficient space. Your available space is {pretty_bytes(available_space)}. The file size is {pretty_bytes(file_size)}. Please free up some space and try again. You can manage your files in the Resources page.'

# Fake upload a small file
file_size = available_space - 1024
file_size = available_space - 1000
resp = self.client.post(reverse("data-list"), {
'name': 'new-file-test',
'type': Data.COMPETITION_BUNDLE,
Expand Down
43 changes: 26 additions & 17 deletions src/apps/api/views/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from competitions.models import Competition, Phase, CompetitionCreationTaskStatus, CompetitionParticipant, Submission
from datasets.models import Data
from competitions.tasks import batch_send_email, manual_migration, create_competition_dump
from competitions.utils import get_popular_competitions, get_featured_competitions
from competitions.utils import get_popular_competitions, get_recent_competitions
from leaderboards.models import Leaderboard
from utils.data import make_url_sassy
from api.permissions import IsOrganizerOrCollaborator
Expand Down Expand Up @@ -304,7 +304,8 @@ def update(self, request, *args, **kwargs):
data.pop('whitelist_emails', None)
# Loop over whitelist emails and add them back to whitelist emails in dict format
for email in whitelist_emails:
data.setdefault('whitelist_emails', []).append({'email': email})
# user lower case email because some emails in the whitelist may have upper case letters
data.setdefault('whitelist_emails', []).append({'email': email.lower()})

serializer = self.get_serializer(instance, data=data, partial=partial)
serializer.is_valid(raise_exception=True)
Expand Down Expand Up @@ -350,7 +351,8 @@ def register(self, request, pk):
send_participation_accepted_emails(participant)
else:
# check if user is in whitelist emails then approve directly
if user.email in list(competition.whitelist_emails.values_list('email', flat=True)):
# Using lower case because some users have used uppercased emails addresses
if user.email.lower() in list(competition.whitelist_emails.values_list('email', flat=True)):
participant.status = 'approved'
send_participation_accepted_emails(participant)
else:
Expand Down Expand Up @@ -432,7 +434,7 @@ def collect_leaderboard_data(self, competition, phase_pk=None):
return leaderboard_data

@action(detail=True, methods=['GET'], renderer_classes=[JSONRenderer, CSVRenderer, ZipRenderer])
def results(self, request, pk, format=None):
def results(self, request, pk, format='json'):
competition = self.get_object()
if not competition.user_has_admin_permission(request.user):
raise PermissionDenied("You are not a competition admin or superuser")
Expand Down Expand Up @@ -507,12 +509,12 @@ def creation_status(self, request, pk):
@action(detail=False, methods=('GET',), permission_classes=(AllowAny,))
def front_page(self, request):
popular_comps = get_popular_competitions()
featured_comps = get_featured_competitions()
recent_comps = get_recent_competitions(exclude_comps=popular_comps)
popular_comps_serializer = CompetitionSerializerSimple(popular_comps, many=True)
featured_comps_serializer = CompetitionSerializerSimple(featured_comps, many=True)
recent_comps_serializer = CompetitionSerializerSimple(recent_comps, many=True)
return Response(data={
"popular_comps": popular_comps_serializer.data,
"featured_comps": featured_comps_serializer.data
"recent_comps": recent_comps_serializer.data
})

@swagger_auto_schema(request_body=no_body, responses={201: CompetitionCreationTaskStatusSerializer()})
Expand Down Expand Up @@ -836,16 +838,23 @@ def get_queryset(self):
return CompetitionParticipant.objects.none()

def update(self, request, *args, **kwargs):
if request.method == 'PATCH':
if 'status' in request.data:
participation_status = request.data['status']
participant = self.get_object()
emails = {
'approved': send_participation_accepted_emails,
'denied': send_participation_denied_emails,
}
if participation_status in emails:
emails[participation_status](participant)
if request.method == 'PATCH' and 'status' in request.data:
participation_status = request.data['status']
participant = self.get_object()

# Check if the new status is the same as the current status
if participation_status == participant.status:
return Response(
{"error": f"Status is already set to `{participation_status}`"},
status=status.HTTP_400_BAD_REQUEST
)

emails = {
'approved': send_participation_accepted_emails,
'denied': send_participation_denied_emails,
}
if participation_status in emails:
emails[participation_status](participant)

return super().update(request, *args, **kwargs)

Expand Down
3 changes: 2 additions & 1 deletion src/apps/api/views/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from api.serializers import datasets as serializers
from datasets.models import Data, DataGroup
from competitions.models import CompetitionCreationTaskStatus
from utils.data import make_url_sassy, pretty_bytes
from utils.data import make_url_sassy, pretty_bytes, gb_to_bytes


class DataViewSet(ModelViewSet):
Expand Down Expand Up @@ -90,6 +90,7 @@ def create(self, request, *args, **kwargs):
# Check User quota
storage_used = float(request.user.get_used_storage_space())
quota = float(request.user.quota)
quota = gb_to_bytes(quota)
file_size = float(request.data['file_size'])
if storage_used + file_size > quota:
available_space = pretty_bytes(quota - storage_used)
Expand Down
5 changes: 4 additions & 1 deletion src/apps/api/views/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from profiles.models import User
from tasks.models import Task
from datasets.models import Data
from utils.data import pretty_bytes
from utils.data import pretty_bytes, gb_to_bytes


# TODO:// TaskViewSimple uses simple serializer from tasks, which exists purely for the use of Select2 on phase modal
Expand Down Expand Up @@ -210,7 +210,10 @@ def upload_task(self, request):

# Check if user has enough quota to proceed
storage_used = float(request.user.get_used_storage_space())
# User quota is in GB
quota = float(request.user.quota)
# Convert user quota to bytes
quota = gb_to_bytes(quota)
file_size = uploaded_file.size
if storage_used + file_size > quota:
file_size = pretty_bytes(file_size)
Expand Down
2 changes: 1 addition & 1 deletion src/apps/competitions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class CompetitionAdmin(admin.ModelAdmin):
search_fields = ['title', 'docker_image', 'created_by__username']
list_display = ['title', 'created_by', 'is_featured']
list_display = ['id', 'title', 'created_by', 'is_featured']
list_filter = ['is_featured']


Expand Down
28 changes: 28 additions & 0 deletions src/apps/competitions/migrations/0053_auto_20250218_1151.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 2.2.28 on 2025-02-18 11:51

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('competitions', '0052_auto_20250129_1058'),
]

operations = [
migrations.AlterField(
model_name='submission',
name='detailed_result_file_size',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=15, null=True),
),
migrations.AlterField(
model_name='submission',
name='prediction_result_file_size',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=15, null=True),
),
migrations.AlterField(
model_name='submission',
name='scoring_result_file_size',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=15, null=True),
),
]
Loading