diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 4356a1da4..d8af58683 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -21,6 +21,9 @@ import { ChallengeparticipateComponent } from './components/challenge/challengep
import { ChallengeleaderboardComponent } from './components/challenge/challengeleaderboard/challengeleaderboard.component';
import { ChallengesubmitComponent } from './components/challenge/challengesubmit/challengesubmit.component';
import { ChallengesubmissionsComponent } from './components/challenge/challengesubmissions/challengesubmissions.component';
+import {
+ ChallengeviewallsubmissionsComponent
+} from './components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component';
import { ChallengeCreateComponent } from './components/challenge-create/challenge-create.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { ProfileComponent } from './components/profile/profile.component';
@@ -67,6 +70,7 @@ const routes: Routes = [
{path: 'my-submissions', component: ChallengesubmissionsComponent},
{path: 'my-submissions/:phase', component: ChallengesubmissionsComponent},
{path: 'mysubmissions/:phase/:submission', component: ChallengesubmissionsComponent},
+ {path: 'view-all-submissions', component: ChallengeviewallsubmissionsComponent},
{path: 'leaderboard', component: ChallengeleaderboardComponent},
{path: 'leaderboard/:split', component: ChallengeleaderboardComponent},
{path: 'leaderboard/:split/:entry', component: ChallengeleaderboardComponent}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 700f2c948..2a3c6942b 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -68,6 +68,9 @@ import { RulesComponent } from './components/home/rules/rules.component';
import { TestimonialsComponent } from './components/home/testimonials/testimonials.component';
import { FeaturedChallengesComponent } from './components/home/featured-challenges/featured-challenges.component';
import { EditphasemodalComponent } from './components/challenge/challengephases/editphasemodal/editphasemodal.component';
+import {
+ ChallengeviewallsubmissionsComponent
+} from './components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component';
@NgModule({
declarations: [
@@ -116,7 +119,8 @@ import { EditphasemodalComponent } from './components/challenge/challengephases/
RulesComponent,
TestimonialsComponent,
FeaturedChallengesComponent,
- EditphasemodalComponent
+ EditphasemodalComponent,
+ ChallengeviewallsubmissionsComponent
],
imports: [
BrowserModule,
diff --git a/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.html b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.html
new file mode 100644
index 000000000..832d4cf9f
--- /dev/null
+++ b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.html
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+ Please select a phase to get list of submissions!
+
+
+
+
+
+
+
+ File
+
+
+ {{key.name}}
+
+
+
+
+
+
+
+
+
+
+
+
No phase selected.
+
No results found.
+
0" class="table-scroll">
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.scss b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.scss
new file mode 100644
index 000000000..240bc99f3
--- /dev/null
+++ b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.scss
@@ -0,0 +1,47 @@
+@import './variables.scss';
+@import './mixins.scss';
+
+.ev-md-container {
+ .phase-title {
+ margin-bottom: 20px;
+ }
+}
+
+.show-submission-count {
+ background: #252833;
+ text-align: center;
+ color: white;
+ padding: 2px 5px 2px 5px;
+ border-radius: 3px 3px 3px 3px;
+ font-weight: 300;
+ transition: all 0.2s ease-in-out;
+}
+
+@include screen-small-medium {
+ .download-submissions {
+ margin-left: 20px;
+ margin-right: 20px;
+ }
+}
+
+.add-line-height {
+ line-height: 51px;
+}
+
+.result-wrn {
+ margin-top: 15px;
+ margin-bottom: 20px;
+}
+
+table {
+ width: 160% !important;
+}
+
+.cbx-label {
+ margin-left: 55px;
+ margin-right: 55px;
+}
+
+.cbx:checked ~ .cbx-label {
+ background: #4CAF50;
+}
diff --git a/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.spec.ts b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.spec.ts
new file mode 100644
index 000000000..19363652d
--- /dev/null
+++ b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.spec.ts
@@ -0,0 +1,38 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ChallengeviewallsubmissionsComponent } from './challengeviewallsubmissions.component';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ChallengeService } from '../../../services/challenge.service';
+import { GlobalService } from '../../../services/global.service';
+import { AuthService } from '../../../services/auth.service';
+import { ApiService } from '../../../services/api.service';
+import { EndpointsService } from '../../../services/endpoints.service';
+import { WindowService } from '../../../services/window.service';
+import { HttpClientModule } from '@angular/common/http';
+import { RouterTestingModule } from '@angular/router/testing';
+
+describe('ChallengeviewallsubmissionsComponent', () => {
+ let component: ChallengeviewallsubmissionsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ChallengeviewallsubmissionsComponent ],
+ providers: [ ChallengeService, GlobalService, AuthService, ApiService,
+ EndpointsService, WindowService ],
+ imports: [ HttpClientModule, RouterTestingModule ],
+ schemas: [ NO_ERRORS_SCHEMA ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ChallengeviewallsubmissionsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.ts b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.ts
new file mode 100644
index 000000000..f99632b57
--- /dev/null
+++ b/src/app/components/challenge/challengeviewallsubmissions/challengeviewallsubmissions.component.ts
@@ -0,0 +1,382 @@
+import { Component, OnInit, QueryList, ViewChildren, ViewChild, AfterViewInit } from '@angular/core';
+import { AuthService } from '../../../services/auth.service';
+import { ApiService } from '../../../services/api.service';
+import { WindowService } from '../../../services/window.service';
+import { GlobalService } from '../../../services/global.service';
+import { ChallengeService } from '../../../services/challenge.service';
+import { EndpointsService } from '../../../services/endpoints.service';
+import { Router, ActivatedRoute } from '@angular/router';
+import { SelectphaseComponent } from '../../utility/selectphase/selectphase.component';
+import { environment } from '../../../../environments/environment.staging';
+
+/**
+ * Component Class
+ */
+@Component({
+ selector: 'app-challengeviewallsubmissions',
+ templateUrl: './challengeviewallsubmissions.component.html',
+ styleUrls: ['./challengeviewallsubmissions.component.scss']
+})
+export class ChallengeviewallsubmissionsComponent implements OnInit, AfterViewInit {
+
+ /**
+ * Phase select card components
+ */
+ @ViewChildren('phaseselect')
+ components: QueryList;
+
+ /**
+ * Is user logged in
+ */
+ isLoggedIn = false;
+
+ /**
+ * Has view been initialized
+ */
+ viewInit = false;
+
+ /**
+ * Challenge object
+ */
+ challenge: any;
+
+ /**
+ * Router's public instance
+ */
+ routerPublic: any;
+
+ /**
+ * User participated
+ */
+ isParticipated: any;
+
+ /**
+ * Is user a challenge host
+ */
+ isChallengeHost = false;
+
+ /**
+ * Submissions list
+ */
+ submissions = [];
+
+ /**
+ * Total submissions
+ */
+ submissionCount = 0;
+
+ /**
+ * Challenge phase list
+ */
+ phases = [];
+
+ /**
+ * Challenge phases filtered
+ */
+ filteredPhases = [];
+
+ /**
+ * Currently selected phase's id
+ */
+ selectedPhaseId: any;
+
+ /**
+ * Currently selected phase
+ */
+ selectedPhase: any = null;
+
+ /**
+ * Is phase selected
+ */
+ isPhaseSelected = false;
+
+ /**
+ * Download file types
+ */
+ fileTypes = [{ 'name': 'csv' }];
+
+ /**
+ * Selected file type
+ */
+ fileSelected = '';
+
+ /**
+ * Phase selection type (radio button or select box)
+ */
+ phaseSelectionType = 'selectBox';
+
+ /**
+ * Fields to be exported
+ */
+ fieldsToGetExport: any = [];
+
+ /**
+ * @param showPagination Is pagination
+ * @param paginationMessage Pagination message
+ * @param isPrev Previous page state
+ * @param isNext Next page state
+ * @param currentPage Current Page number
+ */
+ paginationDetails: any = {};
+
+ /**
+ * Constructor.
+ * @param route ActivatedRoute Injection.
+ * @param router GlobalService Injection.
+ * @param authService AuthService Injection.
+ * @param globalService GlobalService Injection.
+ * @param apiService Router Injection.
+ * @param endpointsService EndpointsService Injection.
+ * @param challengeService ChallengeService Injection.
+ */
+ constructor(private authService: AuthService, private router: Router, private route: ActivatedRoute,
+ private challengeService: ChallengeService, private globalService: GlobalService,
+ private apiService: ApiService, private windowService: WindowService, private endpointsService: EndpointsService) { }
+
+ /**
+ * Component after view initialized.
+ */
+ ngAfterViewInit() {
+ this.viewInit = true;
+ }
+
+ /**
+ * Component on initialized.
+ */
+ ngOnInit() {
+ if (this.authService.isLoggedIn()) {
+ this.isLoggedIn = true;
+ }
+ this.routerPublic = this.router;
+ this.challengeService.currentChallenge.subscribe(challenge => {
+ this.challenge = challenge;
+ });
+ this.challengeService.currentParticipationStatus.subscribe(status => {
+ this.isParticipated = status;
+ if (!status) {
+ this.globalService.storeData(this.globalService.redirectStorageKey, {path: this.routerPublic.url});
+ let redirectToPath = '';
+ if (this.router.url.split('/').length === 4) {
+ redirectToPath = '../participate';
+ } else if (this.router.url.split('/').length === 5) {
+ redirectToPath = '../../participate';
+ } else if (this.router.url.split('/').length === 6) {
+ redirectToPath = '../../../participate';
+ }
+ this.router.navigate([redirectToPath], {relativeTo: this.route});
+ }
+ });
+ this.challengeService.currentPhases.subscribe(
+ phases => {
+ this.phases = phases;
+ for (let i = 0; i < this.phases.length; i++) {
+ if (this.phases[i].is_public === false) {
+ this.phases[i].showPrivate = true;
+ }
+ }
+ this.filteredPhases = this.phases;
+ });
+
+ this.challengeService.isChallengeHost.subscribe(status => {
+ this.isChallengeHost = status;
+ });
+ }
+
+ /**
+ * Called when a phase is selected (from child component)
+ */
+ phaseSelected() {
+ const SELF = this;
+ return (phase) => {
+ SELF.selectedPhase = phase;
+ SELF.isPhaseSelected = true;
+ SELF.submissionCount = 0;
+ if (SELF.challenge['id'] && phase['id']) {
+ SELF.fetchSubmissions(SELF.challenge['id'], phase['id']);
+ SELF.fetchSubmissionCounts(this.challenge['id'], phase['id']);
+ }
+ };
+ }
+
+ /**
+ * Fetch submissions from API.
+ * @param challenge challenge id
+ * @param phase phase id
+ */
+ fetchSubmissions(challenge, phase) {
+ const API_PATH = this.endpointsService.allChallengeSubmissionURL(challenge, phase);
+ const SELF = this;
+ this.apiService.getUrl(API_PATH).subscribe(
+ data => {
+ SELF.submissions = data['results'];
+ SELF.paginationDetails.next = data.next;
+ SELF.paginationDetails.previous = data.previous;
+ SELF.paginationDetails.totalPage = Math.ceil(data.count / 100);
+
+ if (data.count === 0) {
+ SELF.paginationDetails.showPagination = false;
+ SELF.paginationDetails.paginationMessage = 'No results found';
+ } else {
+ SELF.paginationDetails.showPagination = true;
+ SELF.paginationDetails.paginationMessage = '';
+ }
+
+ // condition for pagination
+ if (data.next === null) {
+ SELF.paginationDetails.isNext = 'disabled';
+ SELF.paginationDetails.currentPage = 1;
+ } else {
+ SELF.paginationDetails.isNext = '';
+ SELF.paginationDetails.currentPage = Math.ceil(data.next.split('page=')[1] - 1);
+ }
+ if (data.previous === null) {
+ SELF.paginationDetails.isPrev = 'disabled';
+ } else {
+ SELF.paginationDetails.isPrev = '';
+ }
+ },
+ err => {
+ SELF.globalService.handleApiError(err);
+ },
+ () => {
+ console.log('Fetched submissions', challenge, phase);
+ }
+ );
+ }
+
+ /**
+ * Download Submission csv.
+ */
+ downloadSubmission() {
+ if (this.challenge['id'] && this.selectedPhase && this.fileSelected) {
+ const API_PATH = this.endpointsService.challengeSubmissionDownloadURL(
+ this.challenge['id'], this.selectedPhase['id'], this.fileSelected
+ );
+ const SELF = this;
+ if (SELF.fieldsToGetExport.length === 0 || SELF.fieldsToGetExport === undefined) {
+ SELF.apiService.getUrl(API_PATH, false).subscribe(
+ data => {
+ SELF.windowService.downloadFile(data, 'all_submissions.csv');
+ },
+ err => {
+ SELF.globalService.handleApiError(err);
+ },
+ () => {
+ console.log('Download complete.', SELF.challenge['id'], SELF.selectedPhase['id']);
+ }
+ );
+ }
+ } else {
+ if (this.selectedPhase === null) {
+ this.globalService.showToast('error', 'Please select a challenge phase!');
+ } else if (this.fileSelected === '') {
+ this.globalService.showToast('error', 'The file type requested is not valid!');
+ }
+ }
+ }
+
+ /**
+ * load data with pagination
+ */
+ loadPaginationData(url) {
+ if (url !== null) {
+ const SELF = this;
+ const API_PATH = url.split(environment.api_endpoint)[1];
+
+ SELF.apiService.getUrl(API_PATH, true).subscribe(
+ data => {
+ SELF.submissions = data['results'];
+ SELF.paginationDetails.next = data.next;
+ SELF.paginationDetails.previous = data.previous;
+
+ // condition for pagination
+ if (data.next === null) {
+ SELF.paginationDetails.isNext = 'disabled';
+ SELF.paginationDetails.currentPage = Math.ceil(data.count / 100);
+ } else {
+ SELF.paginationDetails.isNext = '';
+ SELF.paginationDetails.currentPage = Math.ceil(data.next.split('page=')[1] - 1);
+ }
+
+ if (data.previous === null) {
+ SELF.paginationDetails.isPrev = 'disabled';
+ } else {
+ SELF.paginationDetails.isPrev = '';
+ }
+ },
+ err => {
+ SELF.globalService.handleApiError(err);
+ },
+ () => {
+ console.log('Fetched pagination submissions');
+ }
+ );
+ }
+ }
+
+ /**
+ * Update submission's leaderboard visibility.
+ * @param id Submission id
+ */
+ updateSubmissionVisibility(id) {
+ for (let i = 0; i < this.submissions.length; i++) {
+ if (this.submissions[i]['id'] === id) {
+ this.submissions[i]['is_public'] = !this.submissions[i]['is_public'];
+ break;
+ }
+ }
+ }
+
+ /**
+ * Change Submission's leaderboard visibility API.
+ * @param id Submission id
+ * @param is_public visibility boolean flag
+ */
+ changeSubmissionVisibility(id, is_public) {
+ is_public = !is_public;
+ this.updateSubmissionVisibility(id);
+ if (this.challenge['id'] && this.selectedPhase && this.selectedPhase['id'] && id) {
+ const API_PATH = this.endpointsService.challengeSubmissionUpdateURL(this.challenge['id'], this.selectedPhase['id'], id);
+ const SELF = this;
+ const BODY = JSON.stringify({is_public: is_public});
+ this.apiService.patchUrl(API_PATH, BODY).subscribe(
+ () => {
+ if (is_public) {
+ SELF.globalService.showToast('success', 'The submission is made public');
+ } else {
+ SELF.globalService.showToast('success', 'The submission is made private');
+ }
+ },
+ err => {
+ SELF.globalService.handleApiError(err);
+ },
+ () => {
+ console.log('Updated submission visibility', id);
+ }
+ );
+ }
+ }
+
+ /**
+ * Fetch number of submissions for a challenge phase.
+ * @param challenge challenge id
+ * @param phase phase id
+ */
+ fetchSubmissionCounts(challenge, phase) {
+ const API_PATH = this.endpointsService.challengeSubmissionCountURL(challenge, phase);
+ const SELF = this;
+ this.apiService.getUrl(API_PATH).subscribe(
+ data => {
+ if (data['participant_team_submission_count']) {
+ SELF.submissionCount = data['participant_team_submission_count'];
+ }
+ },
+ err => {
+ SELF.globalService.handleApiError(err);
+ },
+ () => {
+ console.log('Fetched submission counts', challenge, phase);
+ }
+ );
+ }
+}
diff --git a/src/app/services/endpoints.service.ts b/src/app/services/endpoints.service.ts
index 54b8fc7c1..3dd184ac5 100644
--- a/src/app/services/endpoints.service.ts
+++ b/src/app/services/endpoints.service.ts
@@ -193,6 +193,15 @@ export class EndpointsService {
return `${this.jobs}${this.challenge}${challenge}/challenge_phase/${phase}/submission/`;
}
+ /**
+ * Get all Challenge Submission
+ * @param challenge challenge id
+ * @param phase phase id
+ */
+ allChallengeSubmissionURL(challenge, phase) {
+ return `${this.challenges}${challenge}/challenge_phase/${phase}/submissions`;
+ }
+
/**
* Challenge Submission Download
* @param challenge challenge id
diff --git a/src/styles/base.scss b/src/styles/base.scss
index d54bd45ce..c3f4c3cbc 100644
--- a/src/styles/base.scss
+++ b/src/styles/base.scss
@@ -202,6 +202,13 @@ mat-label {
}
}
+.mat-select-trigger {
+ line-height: 24px;
+ letter-spacing: 0.1px;
+ font-size: 15px;
+ color: rgba(0, 0, 0, 0.87);
+}
+
@include screen-medium {
.phase-select-box {
.mat-form-field-infix {
@@ -232,6 +239,55 @@ mat-label {
}
}
+.all-submissions-file {
+ .mat-form-field-infix {
+ width: 144px !important;
+ }
+}
+
+.custom-field-export {
+ .mat-form-field-infix {
+ width: 311px !important;
+ }
+}
+
+@include screen-medium {
+ .download-submissions {
+ margin-left: 50px;
+ }
+
+ .custom-field-export {
+ .mat-form-field-infix {
+ width: 270px !important;
+ }
+ }
+
+ .all-submissions-file {
+ .mat-form-field-infix {
+ width: 110px !important;
+ }
+ }
+}
+
+@include screen-small-medium {
+ .download-submissions {
+ margin-left: 40px;
+ }
+
+ .custom-field-export {
+ .mat-form-field-infix {
+ width: 195px !important;
+ }
+ }
+
+ .all-submissions-file {
+ .mat-form-field-infix {
+ width: 75px !important;
+ }
+ }
+}
+
+
/**Rich text editor styles*/
.reusable-editor.fr-box.fr-basic .fr-element {
height: 320px;