diff --git a/apps/challenges/serializers.py b/apps/challenges/serializers.py index 6e28744f12..4fa6a24bee 100644 --- a/apps/challenges/serializers.py +++ b/apps/challenges/serializers.py @@ -103,6 +103,7 @@ class Meta: "allowed_submission_file_types", "default_submission_meta_attributes", "allowed_email_ids", + "is_submission_public", ) diff --git a/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.html b/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.html index 6af6dac21a..924da573c7 100644 --- a/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.html +++ b/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.html @@ -76,6 +76,7 @@ +
Submissions/day @@ -134,6 +135,43 @@ >
+
+ +
+
+
+ Max Concurrent Submissions Allowed + +
+
+ +
+
+
Allowed Submission File Types
+ +
+
+
diff --git a/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.ts b/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.ts index 043db1c87c..b74a9fab77 100644 --- a/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.ts +++ b/frontend_v2/src/app/components/challenge/challengephases/editphasemodal/editphasemodal.component.ts @@ -27,6 +27,11 @@ export class EditphasemodalComponent implements OnInit { * Challenge phase name */ name = ''; + + /** + * Challenge phase allowed submission file types + */ + allowedSubmissionFileTypes = ''; /** * Challenge phase description @@ -58,6 +63,11 @@ export class EditphasemodalComponent implements OnInit { */ maxSubmissions: number; + /** + * Challenge phase max concurrent submissions allowed + */ + maxConcurrentSubmissionsAllowed: number; + /** * If editor error message */ @@ -141,7 +151,9 @@ export class EditphasemodalComponent implements OnInit { * Constructor. * @param globalService GlobalService Injection. */ - constructor(private globalService: GlobalService) {} + constructor( + private globalService: GlobalService, + ) {} ngOnInit() { if (this.params) { @@ -172,6 +184,12 @@ export class EditphasemodalComponent implements OnInit { if (this.params['maxSubmissions']) { this.maxSubmissions = this.params['maxSubmissions']; } + if (this.params['maxConcurrentSubmissionsAllowed']) { + this.maxConcurrentSubmissionsAllowed = this.params['maxConcurrentSubmissionsAllowed']; + } + if (this.params['allowedSubmissionFileTypes']) { + this.allowedSubmissionFileTypes = this.params['allowedSubmissionFileTypes']; + } if (this.params['confirm']) { this.confirm = this.params['confirm']; } diff --git a/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.html b/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.html index d69aa6a310..796a9092f8 100644 --- a/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.html +++ b/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.html @@ -106,16 +106,41 @@
Challenge Settings

- -
-
+
+ + +
+ +
+ + Is Public + + + +      + + Submission Visibility + + + + +
+
+
diff --git a/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.scss b/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.scss index 0de5a6dbb1..543e84f175 100644 --- a/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.scss +++ b/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.scss @@ -13,6 +13,14 @@ } } +.phase-card { + width:50vw; +} + +.phase-button { + margin-left: 25px; +} + @include screen-medium { .settings-section { padding: 10px 20px !important; diff --git a/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.ts b/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.ts index 924a462226..96265fcc37 100644 --- a/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.ts +++ b/frontend_v2/src/app/components/challenge/challengesettings/challengesettings.component.ts @@ -89,13 +89,44 @@ export class ChallengesettingsComponent implements OnInit, OnDestroy { * Email error message */ message: string; + + /** + * If the submission is public + */ + isSubmissionPublic : boolean = false; + + /** + * If the phase is public + */ + isPhasePublic : boolean = false; + + /** + * If leaderboard is public + */ + isLeaderboardPublic : boolean = false; + + /** + * phase visibility state and it's icon + */ + phaseVisibility = { + state: 'Private', + icon: 'fa fa-toggle-off', + }; + + /** + * submission visibility state and it's icon + */ + submissionVisibility = { + state: 'Private', + icon: 'fa fa-toggle-off', + }; /** * publish challenge state and it's icon */ publishChallenge = { state: 'Not Published', - icon: 'fa fa-eye-slash red-text', + icon: 'fa fa-toggle-off', }; /** @@ -188,49 +219,180 @@ export class ChallengesettingsComponent implements OnInit, OnDestroy { const SELF = this; return (phase) => { SELF.selectedPhase = phase; + SELF.isPhasePublic = SELF.selectedPhase['is_public']; + SELF.isSubmissionPublic = SELF.selectedPhase['is_submission_public']; + SELF.isLeaderboardPublic = SELF.selectedPhase['leaderboard_public']; + if (SELF.isPhasePublic) { + SELF.phaseVisibility.state = 'Public'; + SELF.phaseVisibility.icon = 'fa fa-toggle-on green-text'; + } + else { + SELF.phaseVisibility.state = 'Private'; + SELF.phaseVisibility.icon = 'fa fa-toggle-off grey-text text-darken-1'; + } + if (SELF.isSubmissionPublic) { + SELF.submissionVisibility.state = 'Public'; + SELF.submissionVisibility.icon = 'fa fa-toggle-on green-text'; + } + else { + SELF.submissionVisibility.state = 'Private'; + SELF.submissionVisibility.icon = 'fa fa-toggle-off grey-text text-darken-1'; + } + }; + } - SELF.apiCall = (params) => { - const FORM_DATA: FormData = new FormData(); - for (const key in params) { - if (params[key]) { - FORM_DATA.append(key, params[key]); - } + editPhaseDetails() { + const SELF = this; + SELF.apiCall = (params) => { + const FORM_DATA: FormData = new FormData(); + for (const key in params) { + if (params[key]) { + FORM_DATA.append(key, params[key]); } + } + SELF.apiService + .patchFileUrl( + SELF.endpointsService.updateChallengePhaseDetailsURL(SELF.challenge.id, SELF.selectedPhase['id']), + FORM_DATA + ) + .subscribe( + (data) => { + SELF.selectedPhase = data; + SELF.challengeService.fetchPhases(SELF.challenge['id']); + SELF.challengeService.changePhaseSelected(true); + SELF.selectedPhase = false; + SELF.globalService.showToast('success', 'The challenge phase details are successfully updated!'); + }, + (err) => { + SELF.globalService.showToast('error', err); + }, + () => {this.logger.info('PHASE-UPDATE-FINISHED')} + ); + }; + + const PARAMS = { + title: 'Edit Challenge Phase Details', + name: SELF.selectedPhase['name'], + label: 'description', + description: SELF.selectedPhase['description'], + startDate: SELF.selectedPhase['start_date'], + endDate: SELF.selectedPhase['end_date'], + maxSubmissionsPerDay: SELF.selectedPhase['max_submissions_per_day'], + maxSubmissionsPerMonth: SELF.selectedPhase['max_submissions_per_month'], + maxSubmissions: SELF.selectedPhase['max_submissions'], + maxConcurrentSubmissionsAllowed: SELF.selectedPhase['max_concurrent_submissions_allowed'], + allowedSubmissionFileTypes: SELF.selectedPhase['allowed_submission_file_types'], + confirm: 'Submit', + deny: 'Cancel', + confirmCallback: SELF.apiCall, + }; + SELF.globalService.showEditPhaseModal(PARAMS); +} + + /** + * Phase Visibility toggle function + */ + togglePhaseVisibility() { + const SELF = this; + let togglePhaseVisibilityState, isPublic; + if (SELF.phaseVisibility.state === 'Public') { + togglePhaseVisibilityState = 'private'; + isPublic = false; + SELF.phaseVisibility.state = 'Private'; + SELF.phaseVisibility.icon = 'fa fa-toggle-off'; + } else { + togglePhaseVisibilityState = 'public'; + isPublic = true; + SELF.phaseVisibility.state = 'Public'; + SELF.phaseVisibility.icon = 'fa fa-toggle-on green-text'; + } + const BODY: FormData = new FormData(); + BODY.append("is_public", isPublic); + SELF.apiService + .patchFileUrl( + SELF.endpointsService.updateChallengePhaseDetailsURL(SELF.selectedPhase['challenge'], SELF.selectedPhase['id']), + BODY + ) + .subscribe( + (data) => { + SELF.challengeService.fetchPhases(SELF.selectedPhase['challenge']); + SELF.challengeService.changePhaseSelected(true); + SELF.selectedPhase = false; + SELF.globalService.showToast( + 'success', + 'The phase was successfully made ' + togglePhaseVisibilityState, + 5 + ); + }, + (err) => { + SELF.globalService.handleApiError(err, true); + SELF.globalService.showToast('error', err); + if (isPublic) { + SELF.phaseVisibility.state = 'Private'; + SELF.phaseVisibility.icon = 'fa fa-toggle-off'; + } else { + SELF.phaseVisibility.state = 'Public'; + SELF.phaseVisibility.icon = 'fa fa-toggle-on green-text'; + } + }, + () => this.logger.info('PHASE-VISIBILITY-UPDATE-FINISHED') + ); + } + + /** + * Submission Visibility toggle function + */ + toggleSubmissionVisibility() { + const SELF = this; + if(SELF.isLeaderboardPublic == true) { + let toggleSubmissionVisibilityState, isSubmissionPublic; + if (SELF.submissionVisibility.state === 'Public') { + toggleSubmissionVisibilityState = 'private'; + isSubmissionPublic = false; + SELF.submissionVisibility.state = 'Private'; + SELF.submissionVisibility.icon = 'fa fa-toggle-off'; + } else { + toggleSubmissionVisibilityState = 'public'; + isSubmissionPublic = true; + SELF.submissionVisibility.state = 'Public'; + SELF.submissionVisibility.icon = 'fa fa-toggle-on green-text'; + } + const BODY: FormData = new FormData(); + BODY.append("is_submission_public", isSubmissionPublic); SELF.apiService - .patchFileUrl( - SELF.endpointsService.updateChallengePhaseDetailsURL(SELF.challenge.id, SELF.selectedPhase['id']), - FORM_DATA - ) + .patchFileUrl( + SELF.endpointsService.updateChallengePhaseDetailsURL(SELF.selectedPhase['challenge'], SELF.selectedPhase['id']), + BODY + ) .subscribe( (data) => { - SELF.selectedPhase = data; - SELF.challengeService.fetchPhases(SELF.challenge['id']); - SELF.globalService.showToast('success', 'The challenge phase details are successfully updated!'); + SELF.challengeService.fetchPhases(SELF.selectedPhase['challenge']); + SELF.challengeService.changePhaseSelected(true); + SELF.selectedPhase = false; + SELF.globalService.showToast( + 'success', + 'The submissions were successfully made ' + toggleSubmissionVisibilityState, + 5 + ); }, (err) => { + SELF.globalService.handleApiError(err, true); SELF.globalService.showToast('error', err); + if (isSubmissionPublic) { + SELF.submissionVisibility.state = 'Private'; + SELF.submissionVisibility.icon = 'fa fa-toggle-off'; + } else { + SELF.submissionVisibility.state = 'Public'; + SELF.submissionVisibility.icon = 'fa fa-toggle-on green-text'; + } }, - () => {this.logger.info('PHASE-UPDATE-FINISHED')} + () => this.logger.info('SUBMISSION-VISIBILITY-UPDATE-FINISHED') ); - }; - - const PARAMS = { - title: 'Edit Challenge Phase Details', - name: SELF.selectedPhase['name'], - label: 'description', - description: SELF.selectedPhase['description'], - startDate: SELF.selectedPhase['start_date'], - endDate: SELF.selectedPhase['end_date'], - maxSubmissionsPerDay: SELF.selectedPhase['max_submissions_per_day'], - maxSubmissionsPerMonth: SELF.selectedPhase['max_submissions_per_month'], - maxSubmissions: SELF.selectedPhase['max_submissions'], - confirm: 'Submit', - deny: 'Cancel', - confirmCallback: SELF.apiCall, - }; - SELF.globalService.showEditPhaseModal(PARAMS); - }; - } + } + else { + SELF.globalService.showToast('error', "Leaderboard is private, please make the leaderbaord public"); + } + } /** * Remove banned email chip diff --git a/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html b/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html index 5b61795a72..e633b57a7b 100644 --- a/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html +++ b/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html @@ -139,7 +139,7 @@
My Submissions
Show on leaderboard diff --git a/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.html b/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.html index 458ae03599..c18a55e034 100644 --- a/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.html +++ b/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.html @@ -286,13 +286,14 @@
Make Submission
diff --git a/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.ts b/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.ts index 6a5746b031..b19619d9dd 100644 --- a/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.ts +++ b/frontend_v2/src/app/components/challenge/challengesubmit/challengesubmit.component.ts @@ -53,6 +53,11 @@ export class ChallengesubmitComponent implements OnInit { */ isPublicSubmission:boolean = true; + /** + * Is submittion allowed by host + */ + isSubmissionPublic:boolean = false; + /** * Challenge object */ @@ -467,6 +472,7 @@ export class ChallengesubmitComponent implements OnInit { const SELF = this; return (phase) => { SELF.selectedPhase = phase; + SELF.isSubmissionPublic = phase['is_submission_public']; if (SELF.challenge['id'] && phase['id']) { SELF.getMetaDataDetails(SELF.challenge['id'], phase['id']); SELF.fetchRemainingSubmissions(SELF.challenge['id'], phase['id']); diff --git a/frontend_v2/src/app/components/utility/selectphase/selectphase.component.ts b/frontend_v2/src/app/components/utility/selectphase/selectphase.component.ts index e24b401af4..6abdfe0c55 100644 --- a/frontend_v2/src/app/components/utility/selectphase/selectphase.component.ts +++ b/frontend_v2/src/app/components/utility/selectphase/selectphase.component.ts @@ -46,6 +46,11 @@ export class SelectphaseComponent implements OnInit, OnChanges { */ phaseVisibility = false; + /** + * If phase selected + */ + isPhaseSelected : boolean = false; + /** * Currently selected phase */ @@ -86,7 +91,15 @@ export class SelectphaseComponent implements OnInit, OnChanges { * Component on changes detected in Input. * @param change changes detected */ - ngOnChanges(change) {} + ngOnChanges(change) { + this.challengeService.isPhaseSelected.subscribe((isPhaseSelected) => { + this.isPhaseSelected = isPhaseSelected; + }); + if(this.isPhaseSelected == true) { + this.challengeService.changePhaseSelected(false); + this.phaseName = ''; + } + } /** * Select a particular phase. diff --git a/frontend_v2/src/app/services/challenge.service.ts b/frontend_v2/src/app/services/challenge.service.ts index 3e78be9cf6..942b3317b8 100644 --- a/frontend_v2/src/app/services/challenge.service.ts +++ b/frontend_v2/src/app/services/challenge.service.ts @@ -33,6 +33,8 @@ export class ChallengeService { currentHostTeam = this.hostTeamSource.asObservable(); private challengeHostSource = new BehaviorSubject(false); isChallengeHost = this.challengeHostSource.asObservable(); + private phaseSelected = new BehaviorSubject(false); + isPhaseSelected = this.phaseSelected.asObservable(); private challengePublishSource = new BehaviorSubject(this.defaultPublishChallenge); currentChallengePublishState = this.challengePublishSource.asObservable(); @@ -66,6 +68,14 @@ export class ChallengeService { this.challengeHostSource.next(isChallengeHost); } + /** + * Update the status for selectPhase component after details are updated + * @param selectedPhase new updated phase details status + */ + changePhaseSelected(selectedPhase: boolean) { + this.phaseSelected.next(selectedPhase); + } + /** * Update challenge publish state and icon for current challenge. * @param publishChallenge new challenge publish status and icon. diff --git a/tests/unit/challenges/test_views.py b/tests/unit/challenges/test_views.py index 6a3f95923b..e2d7a4d846 100644 --- a/tests/unit/challenges/test_views.py +++ b/tests/unit/challenges/test_views.py @@ -2057,6 +2057,7 @@ def test_get_challenge_phase(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, }, { "id": self.private_challenge_phase.id, @@ -2084,6 +2085,7 @@ def test_get_challenge_phase(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.private_challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, }, ] @@ -2119,6 +2121,7 @@ def test_get_challenge_phase_when_user_is_not_authenticated(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, } ] self.client.force_authenticate(user=None) @@ -2164,6 +2167,7 @@ def test_get_challenge_phase_when_user_is_host(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, }, { "id": self.private_challenge_phase.id, @@ -2191,6 +2195,7 @@ def test_get_challenge_phase_when_user_is_host(self): "is_partial_submission_evaluation_enabled": self.private_challenge_phase.is_partial_submission_evaluation_enabled, "default_submission_meta_attributes": self.private_challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.private_challenge_phase.allowed_email_ids, + "is_submission_public": self.private_challenge_phase.is_submission_public, }, ] @@ -2540,6 +2545,7 @@ def test_get_particular_challenge_phase_if_user_is_participant(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, } self.client.force_authenticate(user=self.participant_user) response = self.client.get(self.url, {}) @@ -2640,6 +2646,7 @@ def test_update_challenge_phase_when_user_is_its_creator(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, } response = self.client.put( self.url, {"name": new_name, "description": new_description} @@ -2740,6 +2747,7 @@ def test_particular_challenge_phase_partial_update(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, } response = self.client.patch(self.url, self.partial_update_data) self.assertEqual(response.data, expected) @@ -4257,6 +4265,7 @@ def test_get_challenge_phase_by_pk(self): "allowed_submission_file_types": self.challenge_phase.allowed_submission_file_types, "default_submission_meta_attributes": self.challenge_phase.default_submission_meta_attributes, "allowed_email_ids": self.challenge_phase.allowed_email_ids, + "is_submission_public": self.challenge_phase.is_submission_public, } response = self.client.get(self.url, {}) self.assertEqual(response.data, expected)