Skip to content

Commit

Permalink
[Frontend] Add tags and domains to challenge (#3981)
Browse files Browse the repository at this point in the history
* Added Tags and Domain

* Fixed Test Case

* Fixed Line issue

* Update frontend/src/views/web/challenge/edit-challenge/edit-challenge-tag.html

Co-authored-by: Gautam Jajoo <[email protected]>

* Addressing the comments

* Fix Domain and Tags validation

* Indentation Fix

* Fixed Migration

* Fixed Migration

* Fixed Migrations

* Fixes and Optimization

* Fix indentation

* Fixed test case

* Shifted the methods to utils

* Renamed Functions

* .

* Fix import issue

---------

Co-authored-by: Gunjan Chhablani <[email protected]>
Co-authored-by: Gautam Jajoo <[email protected]>
  • Loading branch information
3 people authored Jul 19, 2023
1 parent 98360ec commit 3d2c825
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 1 deletion.
22 changes: 21 additions & 1 deletion apps/challenges/challenge_config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.core.files.base import ContentFile

from os.path import basename, isfile, join
from challenges.models import ChallengePhase, ChallengePhaseSplit, DatasetSplit, Leaderboard
from challenges.models import ChallengePhase, ChallengePhaseSplit, DatasetSplit, Leaderboard, Challenge
from rest_framework import status

from yaml.scanner import ScannerError
Expand Down Expand Up @@ -277,6 +277,8 @@ def get_value_from_field(data, base_location, field_name):
"start_date_greater_than_end_date": "ERROR: Start date cannot be greater than end date.",
"missing_dates_challenge_phase": "ERROR: Please add the start_date and end_date in challenge phase {}.",
"start_date_greater_than_end_date_challenge_phase": "ERROR: Start date cannot be greater than end date in challenge phase {}.",
"extra_tags": "ERROR: Tags are limited to 4. Please remove extra tags then try again!",
"wrong_domain": "ERROR: Domain name is incorrect. Please enter correct domain name then try again!",
}


Expand Down Expand Up @@ -935,6 +937,24 @@ def validate_dataset_splits(self, current_dataset_config_ids):
].format(current_dataset_split_config_id)
self.error_messages.append(message)

# Check for Tags and Domain
def check_tags(self):
if "tags" in self.yaml_file_data:
tags_data = self.yaml_file_data["tags"]
# Verify Tags are limited to 4
if len(tags_data) > 4:
message = self.error_messages_dict["extra_tags"]
self.error_messages.append(message)

def check_domain(self):
# Verify Domain name is correct
if "domain" in self.yaml_file_data:
domain_value = self.yaml_file_data["domain"]
domain_choice = [option[0] for option in Challenge.DOMAIN_OPTIONS]
if domain_value not in domain_choice:
message = self.error_messages_dict["wrong_domain"]
self.error_messages.append(message)


def validate_challenge_config_util(
request,
Expand Down
24 changes: 24 additions & 0 deletions apps/challenges/migrations/0098_challenge_tags_domains.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 2.2.20 on 2023-07-16 15:41

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('challenges', '0097_challengephase_disable_logs'),
]

operations = [
migrations.AddField(
model_name='challenge',
name='domain',
field=models.CharField(blank=True, choices=[('CV', 'Computer Vision'), ('NLP', 'Natural Language Processing'), ('RL', 'Reinforcement Learning')], max_length=50, null=True),
),
migrations.AddField(
model_name='challenge',
name='list_tags',
field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), blank=True, default=list, size=None),
),
]
7 changes: 7 additions & 0 deletions apps/challenges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ def __init__(self, *args, **kwargs):
related_name="challenge_creator",
on_delete=models.CASCADE,
)
DOMAIN_OPTIONS = (
("CV", "Computer Vision"),
("NLP", "Natural Language Processing"),
("RL", "Reinforcement Learning"),
)
domain = models.CharField(max_length=50, choices=DOMAIN_OPTIONS, null=True, blank=True)
list_tags = ArrayField(models.TextField(null=True, blank=True), default=list, blank=True)
published = models.BooleanField(
default=False, verbose_name="Publicly Available", db_index=True
)
Expand Down
7 changes: 7 additions & 0 deletions apps/challenges/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
class ChallengeSerializer(serializers.ModelSerializer):

is_active = serializers.ReadOnlyField()
domain_name = serializers.SerializerMethodField()

def get_domain_name(self, obj):
return obj.get_domain_display()

def __init__(self, *args, **kwargs):
super(ChallengeSerializer, self).__init__(*args, **kwargs)
Expand All @@ -45,6 +49,9 @@ class Meta:
"start_date",
"end_date",
"creator",
"domain",
"domain_name",
"list_tags",
"published",
"submission_time_limit",
"is_registration_open",
Expand Down
10 changes: 10 additions & 0 deletions apps/challenges/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@
views.deregister_participant_team_from_challenge,
name="deregister_participant_team_from_challenge",
),
url(
r"^challenge/(?P<challenge_pk>[0-9]+)/update_challenge_tags_and_domain/$",
views.update_challenge_tags_and_domain,
name="update_challenge_tags_and_domain",
),
url(
r"^challenge/get_domain_choices/$",
views.get_domain_choices,
name="get_domain_choices",
),
]

app_name = "challenges"
34 changes: 34 additions & 0 deletions apps/challenges/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from django.conf import settings
from django.core.files.base import ContentFile
from moto import mock_ecr, mock_sts
from rest_framework.response import Response
from rest_framework import status

from base.utils import (
get_model_object,
Expand Down Expand Up @@ -477,3 +479,35 @@ def parse_submission_meta_attributes(submission):
meta_attribute["name"]
] = meta_attribute.get("value")
return submission_meta_attributes


def add_tags_to_challenge(yaml_file_data, challenge):
if "tags" in yaml_file_data:
tags_data = yaml_file_data["tags"]
new_tags = set(tags_data)
# Remove tags not present in the YAML file
challenge.list_tags = [tag for tag in challenge.list_tags if tag in new_tags]

# Add new tags to the challenge
for tag_name in new_tags:
if tag_name not in challenge.list_tags:
challenge.list_tags.append(tag_name)
else:
# Remove all existing tags if no tags are defined in the YAML file
challenge.list_tags = []


def add_domain_to_challenge(yaml_file_data, challenge):
if "domain" in yaml_file_data:
domain_value = yaml_file_data["domain"]
valid_domains = [choice[0] for choice in challenge.DOMAIN_OPTIONS]
if domain_value in valid_domains:
challenge.domain = domain_value
challenge.save()
else:
message = f"Invalid domain value: {domain_value}, valid values are: {valid_domains}"
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
else:
challenge.domain = None
challenge.save()
67 changes: 67 additions & 0 deletions apps/challenges/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
is_user_in_allowed_email_domains,
is_user_in_blocked_email_domains,
parse_submission_meta_attributes,
add_domain_to_challenge,
add_tags_to_challenge,
)
from challenges.challenge_config_utils import (
download_and_write_file,
Expand Down Expand Up @@ -1560,6 +1562,12 @@ def create_challenge_using_zip_file(request, challenge_host_team_pk):
# transaction.set_rollback(True)
# return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)

# Add Tags
add_tags_to_challenge(yaml_file_data, challenge)

# Add Domain
add_domain_to_challenge(yaml_file_data, challenge)

# Create Leaderboard
yaml_file_data_of_leaderboard = yaml_file_data["leaderboard"]
leaderboard_ids = {}
Expand Down Expand Up @@ -2568,6 +2576,53 @@ def get_or_update_challenge_phase_split(request, challenge_phase_split_pk):
return Response(response_data, status=status.HTTP_200_OK)


@api_view(["PATCH"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticatedOrReadOnly, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def update_challenge_tags_and_domain(request, challenge_pk):
"""
Returns or Updates challenge tags and domain
"""
challenge = get_challenge_model(challenge_pk)

if request.method == "PATCH":
new_tags = request.data.get("list_tags", [])
domain_value = request.data.get("domain")
# Remove tags not present in the YAML file
challenge.list_tags = [tag for tag in challenge.list_tags if tag in new_tags]
# Add new tags to the challenge
for tag_name in new_tags:
if tag_name not in challenge.list_tags:
challenge.list_tags.append(tag_name)
# Verifying Domain name
valid_domains = [choice[0] for choice in challenge.DOMAIN_OPTIONS]
if domain_value in valid_domains:
challenge.domain = domain_value
challenge.save()
return Response(status=status.HTTP_200_OK)
else:
message = f"Invalid domain value:{domain_value}"
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)


@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticatedOrReadOnly, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_domain_choices(request):
"""
Returns domain choices
"""
if request.method == "GET":
domain_choices = Challenge.DOMAIN_OPTIONS
return Response(domain_choices, status.HTTP_200_OK)
else:
response_data = {"error": "Method not allowed"}
return Response(response_data, status.HTTP_405_METHOD_NOT_ALLOWED)


@api_view(["GET", "POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticatedOrReadOnly, HasVerifiedEmail))
Expand Down Expand Up @@ -3549,6 +3604,12 @@ def create_or_update_github_challenge(request, challenge_host_team_pk):
challenge.queue = queue_name
challenge.save()

# Add Tags
add_tags_to_challenge(yaml_file_data, challenge)

# Add Domain
add_domain_to_challenge(yaml_file_data, challenge)

# Create Leaderboard
yaml_file_data_of_leaderboard = yaml_file_data[
"leaderboard"
Expand Down Expand Up @@ -3792,6 +3853,12 @@ def create_or_update_github_challenge(request, challenge_host_team_pk):
raise RuntimeError()
challenge = serializer.instance

# Add Tags
add_tags_to_challenge(yaml_file_data, challenge)

# Add Domain
add_domain_to_challenge(yaml_file_data, challenge)

# Updating Leaderboard object
leaderboard_ids = {}
yaml_file_data_of_leaderboard = yaml_file_data["leaderboard"]
Expand Down
75 changes: 75 additions & 0 deletions frontend/src/js/controllers/challengeCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2878,6 +2878,81 @@
}
};

vm.editchallengeTagDialog = function(ev) {
vm.tags = vm.page.list_tags;
vm.domain_choices();
$mdDialog.show({
scope: $scope,
preserveScope: true,
targetEvent: ev,
templateUrl: 'dist/views/web/challenge/edit-challenge/edit-challenge-tag.html',
escapeToClose: false
});
};

vm.editChallengeTag = function(editChallengeTagDomainForm) {
var new_tags;
if (!editChallengeTagDomainForm) {
$mdDialog.hide();
return;
}
new_tags = typeof vm.tags === 'string'
? vm.tags.split(",").map(item => item.trim())
: vm.page.list_tags.map(tag => tag);

if (typeof vm.tags === 'string' && (new_tags.length > 4 || new_tags.some(tag => tag.length > 15))) {
$rootScope.notify("error", "Invalid tags! Maximum 4 tags are allowed, and each tag must be 15 characters or less.");
return;
}
parameters.url = "challenges/challenge/" + vm.challengeId + "/update_challenge_tags_and_domain/";
parameters.method = 'PATCH';
parameters.data = {
"list_tags": new_tags,
"domain": vm.domain
};
parameters.callback = {
onSuccess: function(response) {
var status = response.status;
utilities.hideLoader();
if (status === 200) {
$rootScope.notify("success", "The challenge tags and domain is successfully updated!");
$state.reload();
$mdDialog.hide();
}
},
onError: function(response) {
utilities.hideLoader();
$mdDialog.hide();
var error = response.data;
$rootScope.notify("error", error);
}
};
utilities.showLoader();
utilities.sendRequest(parameters);
};

vm.domain_choices = function() {
parameters.url = "challenges/challenge/get_domain_choices/";
parameters.method = 'GET';
parameters.data = {};
parameters.callback = {
onSuccess: function(response) {
var domain_choices = response.data;
for (var i = 0; i < domain_choices.length; i++) {
if (domain_choices[i][0] == vm.page.domain) {
vm.domain = domain_choices[i][0];
}
}
vm.domainoptions = domain_choices;
},
onError: function(response) {
var error = response.data;
$rootScope.notify("error", error);
}
};
utilities.sendRequest(parameters);
};

$scope.$on('$destroy', function() {
vm.stopFetchingSubmissions();
vm.stopLeaderboard();
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/views/web/challenge-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<span class=" ev-card-title fs-14"><span><img ng-src="{{challenge.image}}" ></span>{{challenge.title}}</span>
</div>
<div class="card-content">
<li ng-repeat="tags in challenge.list_tags" class="list-inline-item chip orange white-text tag-size">{{tags}}</li>
<li ng-if="challenge.domain" class="list-inline-item chip light-blue white-text">{{challenge.domain}}</li>
<p><strong class="text-light-black fs-12">Organized by</strong>
<br> <strong>{{challenge.creator.team_name}}</strong></p>
<p><strong class="text-light-black fs-12">Starts on</strong>
Expand Down Expand Up @@ -51,6 +53,8 @@
<span class=" ev-card-title fs-14"><span><img ng-src="{{challenge.image}}" ></span>{{challenge.title}}</span>
</div>
<div class="card-content">
<li ng-repeat="tags in challenge.list_tags" class="list-inline-item chip orange white-text tag-size">{{tags}}</li>
<li ng-if="challenge.domain" class="list-inline-item chip light-blue white-text">{{challenge.domain}}</li>
<p><strong class="text-light-black fs-12">Organized by</strong>
<br> <strong>{{challenge.creator.team_name}}</strong></p>
<p><strong class="text-light-black fs-12">Starts on</strong>
Expand All @@ -75,6 +79,8 @@
<span class=" ev-card-title fs-14"><span><img ng-src="{{challenge.image}}" ></span>{{challenge.title}}</span>
</div>
<div class="card-content">
<li ng-repeat="tags in challenge.list_tags" class="list-inline-item chip orange white-text tag-size">{{tags}}</li>
<li ng-if="challenge.domain" class="list-inline-item chip light-blue white-text">{{challenge.domain}}</li>
<p><strong class="text-light-black fs-12">Organized by</strong>
<br> <strong>{{challenge.creator.team_name}}</strong></p>
<p><strong class="text-light-black fs-12">Starts on</strong>
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/views/web/challenge/challenge-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ <h4 class="challenge-step-title w-300">{{challenge.page.title}}
<i class="fa fa-pencil" aria-hidden="true"></i>
</a>
</span>
<ul class="list-unstyled list-inline">
<li ng-if="challenge.page.list_tags[0]" ng-repeat="tags in challenge.page.list_tags" class="list-inline-item chip orange white-text">{{tags}}</li>
<li ng-if="challenge.page.domain_name" class="list-inline-item chip light-blue white-text">{{challenge.page.domain_name}}</li>
<span ng-if="challenge.isChallengeHost">
<a class="pointer" ng-click="challenge.editchallengeTagDialog($event)">
<span ng-if="!(challenge.page.domain_name && challenge.page.list_tags)">Add Tags or Domain</span>
<i class="fa fa-pencil" aria-hidden="true"></i>
</a>
</span>
</ul>
</div>
</div>
<div class="col m2 l2 fs-16 w-300 center">
Expand Down
Loading

0 comments on commit 3d2c825

Please sign in to comment.