Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

dev to QA #691

Merged
merged 6 commits into from
Jan 26, 2016
Merged
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions app/index.jade
Original file line number Diff line number Diff line change
@@ -156,8 +156,6 @@ html
script(src='../bower_components/react/react.js')
script(src='../bower_components/react/react-dom.js')
script(src='../bower_components/classnames/index.js')
script(src='../bower_components/classnames/bind.js')
script(src='../bower_components/classnames/dedupe.js')
script(src='../bower_components/react-input-autosize/dist/react-input-autosize.min.js')
script(src='../bower_components/react-select/dist/react-select.min.js')
script(src='../bower_components/ngReact/ngReact.js')
@@ -336,6 +334,7 @@ html
script(src="submissions/submissions.controller.js")
script(src="submissions/submissions.routes.js")
script(src="submissions/submit-design-files/submit-design-files.controller.js")
script(src="submissions/submit-develop-files/submit-develop-files.controller.js")
script(src="topcoder.constants.js")
script(src="topcoder.controller.js")
script(src="topcoder.interceptors.js")
2 changes: 1 addition & 1 deletion app/profile/subtrack/design/design-challenges.jade
Original file line number Diff line number Diff line change
@@ -8,4 +8,4 @@
.no-challenges(ng-show="!vm.challenges || vm.challenges.length == 0")
| Sorry, no successful challenges found.

tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
2 changes: 1 addition & 1 deletion app/profile/subtrack/develop/develop-challenges.jade
Original file line number Diff line number Diff line change
@@ -10,4 +10,4 @@
.no-challenges(ng-show="!vm.challenges || vm.challenges.length == 0")
| Sorry, no successful challenges found.

tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
tc-endless-paginator(state="vm.status.challenges", page-params="vm.pageParams")
3 changes: 3 additions & 0 deletions app/submissions/submission-error/submission-error.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.panel-body
p.tc-error-messages.submissions-access-error(ng-bind="submissions.errorMessage")

33 changes: 20 additions & 13 deletions app/submissions/submissions.controller.js
Original file line number Diff line number Diff line change
@@ -6,23 +6,30 @@
SubmissionsController.$inject = ['challengeToSubmitTo', '$state'];

function SubmissionsController(challengeToSubmitTo, $state) {

var vm = this;

var challenge = challengeToSubmitTo.challenge;
vm.challengeTitle = challenge.name;
vm.challengeId = challenge.id;
vm.track = challenge.track.toLowerCase();

activate();
vm.error = !!challengeToSubmitTo.error;

function activate() {
var track = challengeToSubmitTo.challenge.track;
if (vm.error) {
vm.errorType = challengeToSubmitTo.error.type;
vm.errorMessage = challengeToSubmitTo.error.message;
vm.challengeError = vm.errorType === 'challenge';
}

if (track === 'DESIGN') {
$state.go('submissions.file.design');
} else if (track === 'DEVELOP') {
$state.go('submissions.file.develop')
if (challengeToSubmitTo.challenge) {
var challenge = challengeToSubmitTo.challenge;
vm.challengeTitle = challenge.name;
vm.challengeId = challenge.id;
vm.track = challenge.track.toLowerCase();

if (challengeToSubmitTo.error) {
$state.go('submissions.file.error');
} else {
if (challenge.track === 'DESIGN') {
$state.go('submissions.file.design');
} else if (challenge.track === 'DEVELOP') {
$state.go('submissions.file.develop')
}
}
}
}
2 changes: 1 addition & 1 deletion app/submissions/submissions.jade
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.panel-page
.panel-header.flex.space-between
.panel-header.flex.space-between(ng-if="!submissions.challengeError")
a.panel-header__back-button.flex.space-between.middle(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}")

//- TODO: Replace below with svg tag
46 changes: 36 additions & 10 deletions app/submissions/submissions.routes.js
Original file line number Diff line number Diff line change
@@ -29,13 +29,18 @@

var userHandle = UserService.getUserIdentity().handle;

var error = null;

return ChallengeService.getUserChallenges(userHandle, params)
.then(function(challenge) {
challenge = challenge[0];
challenge = challenge[0].plain();

if (!challenge) {
// TODO: There should be a challenge, redirect?
alert('User is not associated with this challenge.');
setErrorMessage('challenge', 'This is not a valid challenge. Use your browser\'s back button to return.');
return {
error: error,
challenge: null
};
}
var phaseType;
var phaseId;
@@ -57,26 +62,43 @@
return false;
});

if (!isPhaseSubmission) {
setErrorMessage('phase', 'Submission phases are not currently open for this challenge.')
}

var isSubmitter = _.some(challenge.userDetails.roles, function(role) {
return role === 'Submitter';
});

if (!isPhaseSubmission || !isSubmitter) {
// TODO: Where do we redirect if you can't submit?
alert('You should not have access to this page');
if (!isSubmitter) {
setErrorMessage('submitter', 'You do not have a submitter role for this challenge.')
}

return {
error: error,
challenge: challenge,
phaseType: phaseType,
phaseId: phaseId
};
})
.catch(function(err) {
console.log('ERROR GETTING CHALLENGE: ', err);
alert('There was an error accessing this page');
// TODO: Where do we redirect if there is an error?
setErrorMessage('challenge', 'There was an error getting information for this challenge.');

return {
error: error,
challenge: null
};
});

function setErrorMessage(type, message) {
// Sets the error as the first error encountered
if (!error) {
error = {
type: type,
message: message
};
}
}
}]
}
},
@@ -85,6 +107,10 @@
abstract: true,
template: '<ui-view/>'
},
'submissions.file.error': {
url: '',
templateUrl: 'submissions/submission-error/submission-error.html',
},
'submissions.file.design': {
url:'',
templateUrl: 'submissions/submit-design-files/submit-design-files.html',
@@ -96,7 +122,7 @@
templateUrl: 'submissions/submit-develop-files/submit-develop-files.html',
controller: 'SubmitDevelopFilesController',
controllerAs: 'vm',
},
}
};

for (var name in states) {
41 changes: 39 additions & 2 deletions app/submissions/submissions.spec.js
Original file line number Diff line number Diff line change
@@ -33,20 +33,38 @@ describe('Submissions Controller', function() {
expect(vm).to.exist;
});

it('has properties on vm from the routes resolve', function() {
it('sets error properties when there is an error passed down', function() {
controller = $controller('SubmissionsController', {
challengeToSubmitTo: {
challenge: null,
error: {
type: 'challenge',
message: 'error getting challenge information'
}
},
$state: state
});
vm = controller;

expect(vm.errorType).to.equal('challenge');
expect(vm.errorMessage).to.equal('error getting challenge information');
expect(vm.challengeError).to.be.true;
});

it('sets challenge properties when there is a challenge from the routes resolve', function() {
expect(vm.challengeTitle).to.equal(mockChallenge.challenge.name);
expect(vm.challengeId).to.equal(30049240);
expect(vm.track).to.equal(mockChallenge.challenge.track.toLowerCase());
});


describe('routes to the correct child state for', function() {
it('design challenges', function() {

expect(state.go).calledWith('submissions.file.design');
});

it('develop challenges', function() {

controller = $controller('SubmissionsController', {
challengeToSubmitTo: {
challenge: {
@@ -62,5 +80,24 @@ describe('Submissions Controller', function() {
expect(state.go).calledWith('submissions.file.develop');
});

it('errors', function() {
controller = $controller('SubmissionsController', {
challengeToSubmitTo: {
challenge: {
name: 'Challenge Name',
track: 'DEVELOP',
id: 30049240
},
error: {
type: 'phase',
message: 'No open submissions phase'
}
},
$state: state
});
vm = controller;

expect(state.go).calledWith('submissions.file.error');
});
});
});
Original file line number Diff line number Diff line change
@@ -163,10 +163,8 @@
SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress);
}

/*
* Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH
* of the submission upload and updates the progress UI accordingly.
*/
// Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH
// of the submission upload and updates the progress UI accordingly.
function updateProgress(phase, args) {
// for PREPARE phase
if (phase === 'PREPARE') {
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
(function () {
'use strict';

angular.module('tc.submissions').controller('SubmitDevelopFilesController', SubmitDevelopFilesController);

SubmitDevelopFilesController.$inject = ['$scope','$window', '$stateParams', '$log', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];

function SubmitDevelopFilesController($scope, $window, $stateParams, $log, UserService, SubmissionsService, challengeToSubmitTo) {
var vm = this;
$log = $log.getInstance('SubmitDevelopFilesController');
var files = {};
var fileUploadProgress = {};
vm.comments = '';
vm.uploadProgress = 0;
vm.uploading = false;
vm.preparing = false;
vm.finishing = false;
vm.showProgress = false;
vm.errorInUpload = false;
vm.submissionForm = {
files: [],

// use develop name
sourceZip: null,

submitterComments: '',
hasAgreedToTerms: false
};

var userId = parseInt(UserService.getUserIdentity().userId);

vm.submissionsBody = {
reference: {
type: 'CHALLENGE',
id: $stateParams.challengeId,
phaseType: challengeToSubmitTo.phaseType,
phaseId: challengeToSubmitTo.phaseId
},
userId: userId,
data: {
method: challengeToSubmitTo.challenge.track.toUpperCase() + '_CHALLENGE_ZIP_FILE',

// Can delete below since they are processed and added later?
files: [],
submitterComments: '',
}
};

vm.setFileReference = setFileReference;
vm.uploadSubmission = uploadSubmission;
vm.refreshPage = refreshPage;
vm.cancelRetry = cancelRetry;

activate();

function activate() {}

function setFileReference(file, fieldId) {
// Can clean up since fileValue on tcFileInput has file reference?
files[fieldId] = file;

var fileObject = {
name: file.name,
type: fieldId,
status: 'PENDING'
};

// TODO: Refactor or develop
switch(fieldId) {
case 'SUBMISSION_ZIP':
fileObject.mediaType = 'application/octet-stream';
break;
case 'SOURCE_ZIP':
fileObject.mediaType = 'application/octet-stream';
break;
default:
fileObject.mediaType = file.type;
}

// If user changes a file input's file, update the file details
var isFound = vm.submissionsBody.data.files.reduce(function(isFound, file, i, filesArray) {
if (isFound) { return true; }

if (file.type === fileObject.type) {
filesArray[i] = fileObject;
return true;
}

return false;
}, false);

// Add new files to the list
if (!isFound) {
vm.submissionsBody.data.files.push(fileObject);
}
}

function uploadSubmission() {
vm.errorInUpload = false;
vm.uploadProgress = 0;
vm.fileUploadProgress = {};
vm.showProgress = true;
vm.preparing = true;
vm.uploading = false;
vm.finishing = false;
vm.submissionsBody.data.submitterComments = vm.comments;

$log.debug('Body for request: ', vm.submissionsBody);
SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress);
}

// Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH
// of the submission upload and updates the progress UI accordingly.
function updateProgress(phase, args) {
// for PREPARE phase
if (phase === 'PREPARE') {
// we are concerned only for completion of the phase
if (args === 100) {
vm.preparing = false;
vm.uploading = true;
$log.debug('Prepared for upload.');
}
} else if (phase === 'UPLOAD') {
// if args is object, this update is about XHRRequest's upload progress
if (typeof args === 'object') {
var requestId = args.file;
var progress = args.progress;
if (!fileUploadProgress[requestId] || fileUploadProgress[requestId] < progress) {
fileUploadProgress[requestId] = progress;
}
var total = 0, count = 0;
for(var requestId in fileUploadProgress) {
var prog = fileUploadProgress[requestId];
total += prog;
count++;
}
vm.uploadProgress = total / count;

// initiate digest cycle because this event (xhr event) is caused outside angular
$scope.$apply();
} else { // typeof args === 'number', mainly used a s fallback to mark completion of the UPLOAD phase
vm.uploadProgress = args;
}

// start next phase when UPLOAD is done
if (vm.uploadProgress == 100) {
$log.debug('Uploaded files.');
vm.uploading = false;
vm.finishing = true;
}
} else if (phase === 'FINISH') {
// we are concerned only for completion of the phase
if (args === 100) {
$log.debug('Finished upload.');
}
} else {
// assume it to be error condition
$log.debug("Error Condition: " + phase);
vm.errorInUpload = true;
}
}

function refreshPage() {
$window.location.reload(true);
}

function cancelRetry() {
vm.showProgress = false;
}
}
})();
90 changes: 90 additions & 0 deletions app/submissions/submit-develop-files/submit-develop-files.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.mobile-redirect
.mobile-redirect__title File upload is not available for mobile

.mobile-redirect__body
p Our team is working hard on new features, and file upload currently only works on the web. Please open this page on your desktop if you want to create a submission.

a(ng-href="http://help.{{DOMAIN}}/design/submitting-to-a-design-challenge/formatting-your-submission-for-design-challenges/") TODO: same link as below for help on formatting submission

a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to Challenge Details

.panel-body
form.form-blocks(name="submissionForm", role="form", ng-submit="submissionForm.$valid && vm.uploadSubmission()", novalidate)
.form-block.flex
.form-block__instructions
.form-block__title Files

.form-block__text
p Need text from PMs.

p Need text from PMs.

p Need text from PMs.

a(ng-href="http://help.{{DOMAIN}}/design/submitting-to-a-design-challenge/formatting-your-submission-for-design-challenges/") Need link from PMs

.form-block__fields
.fieldset
tc-file-input.tc-file-field(
label-text="Preview Image",
field-id="DESIGN_COVER",
button-text="Add File",
file-type="jpg,jpeg,png"
placeholder="Image file as .jpg or .png",
mandatory="true",
set-file-reference="vm.setFileReference(file, fieldId)",
ng-model="vm.submissionForm.designCover"
)

.tc-error-messages(
ng-show="submissionForm['DESIGN_COVER'].$touched && submissionForm['DESIGN_COVER'].$invalid",
ng-messages="submissionForm['DESIGN_COVER'].$error"
)
p(ng-message="filesize") File size may not exceed 500MB.

p(ng-message="required") This is not the correct file format. Please select a .jpg or .png file.

.panel-footer
p Submitting your files means you hereby agree to the #[a(ng-href="https://www.{{DOMAIN}}/community/how-it-works/terms/", target="_blank") Topcoder terms of use] and to the extent your uploaded file wins a Topcoder Competition, you hereby assign, grant, and transfer to Topcoder all rights in and title to the Winning Submission (as further described in the terms of use).

p.tc-error-messages(ng-show="vm.submissionForm.hasAgreedToTerms && submissionForm.$invalid") There are outstanding problems with this page. You must fix them before you can upload your submission.

.checkbox.flex.center
input(type="checkbox", ng-model="vm.submissionForm.hasAgreedToTerms", id="agree-to-terms", required)

label(for="agree-to-terms") I understand and agree

button.tc-btn.tc-btn-secondary(type="submit", ng-disabled="submissionForm.$invalid") Submit

modal.transition(show="vm.showProgress", background-click-close="false", style="background-color:white;")
.upload-progress(ng-class="{'upload-progress--error': vm.errorInUpload}")
.upload-progress__title
p Uploading submission for

p.upload-progress-title__challenge-name [Challenge name]

img.upload-progress__image(src="/images/robot.svg", ng-hide="vm.errorInUpload")
img.upload-progress__image--error(src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload")

p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working or you’ll loose all files!

p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! One of the files couldn’t be uploaded, I’m so sorry.

progress-bar.upload-progress__progress-bar(completed="vm.uploadProgress", message="of 3 files uploaded")

.upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload") #[span Preparing...]
.upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload")
p Finished!

.upload-progess__links
a.tc-btn.tc-btn-s(ng-href="https://www.{{DOMAIN}}/challenge-details/{{submissions.challengeId}}/?type={{submissions.track}}") Back to the challenge

a.tc-btn.tc-btn-s.tc-btn-ghost(ng-click="vm.refreshPage()") Submit another


.upload-progress__error(ng-show="vm.errorInUpload") #[span File upload failed]

.upload-progress__error-action(ng-show="vm.errorInUpload")
button.tc-btn.tc-btn-s.tc-btn-ghost(type="button", ng-click="vm.cancelRetry()") Cancel

button.tc-btn.tc-btn-s.tc-btn-secondary(type="button", ng-click="submissionForm.$valid && vm.uploadSubmission()") Try Again
191 changes: 191 additions & 0 deletions app/submissions/submit-develop-files/submit-develop-files.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/* jshint -W117, -W030 */
describe('Submit Develop Files Controller', function() {
var controller, vm, scope;

var mockChallenge = {
challenge: {
name: 'Challenge Name',
track: 'DEVELOP',
id: 30049240
}
};

var userService = {
getUserIdentity: function() {
return {
userId: 123456
};
}
};

var submissionsService = {
getPresignedURL: function() {}
};

var mockWindow = {
location: {
reload: function(val) { return val; }
}
};

beforeEach(function() {
bard.appModule('tc.submissions');
bard.inject(this, '$controller', '$rootScope');

scope = $rootScope.$new();
});

bard.verifyNoOutstandingHttpRequests();

beforeEach(function() {
controller = $controller('SubmitDevelopFilesController', {
$scope: scope,
UserService: userService,
challengeToSubmitTo: mockChallenge,
SubmissionsService: submissionsService,
$window: mockWindow
});
vm = controller;
});

it('exists', function() {
expect(vm).to.exist;
});

it('sets the right track for the method', function() {
controller = $controller('SubmitDevelopFilesController', {
$scope: scope,
UserService: userService,
challengeToSubmitTo: {
challenge: {
name: 'Challenge Name',
track: 'DESIGN',
id: 30049240
}
},
SubmissionsService: submissionsService,
$window: mockWindow
});
vm = controller;
scope.$digest();

expect(vm.submissionsBody.data.method).to.equal('DESIGN_CHALLENGE_ZIP_FILE');
});

describe('setFileReference', function() {
var file, fieldId;

beforeEach(function() {
// TODO: change to be more relevant to develop
file = {
name: 'Dashboard 2.png',
size: 575548,
type: 'image/png'
};
fieldId = 'DESIGN_COVER';

vm.setFileReference(file, fieldId);
scope.$digest();
});

afterEach(function() {
file = undefined;
fieldId = undefined;
});

it('adds a file object to the submissions body', function() {
expect(vm.submissionsBody.data.files).to.have.length(1);
});

it('replaces a file object with a new one if it has the same fieldId', function() {
expect(vm.submissionsBody.data.files).to.have.length(1);

// TODO: change to be more relevant to develop submissions
var newFile = {
name: 'different_image.png',
size: 4321,
type: 'image/png'
};

vm.setFileReference(newFile, fieldId);
scope.$digest();

expect(vm.submissionsBody.data.files).to.have.length(1);
expect(vm.submissionsBody.data.files[0].name).to.equal('different_image.png');
});

it('sets the correct mediaTypes on the fileObject', function() {
// TODO: change to be more relevant to develop
expect(vm.submissionsBody.data.files[0].mediaType).to.equal('image/png');

var newFile = {
name: 'submission.zip',
size: 43121,
type: 'application/zip'
};
var newFieldId = 'SUBMISSION_ZIP';

vm.setFileReference(newFile, newFieldId);
scope.$digest();

expect(vm.submissionsBody.data.files[1].mediaType).to.equal('application/octet-stream');

var newFile2 = {
name: 'source.zip',
size: 2314,
type: 'application/zip'
};
var newFieldId2 = 'SOURCE_ZIP';

vm.setFileReference(newFile2, newFieldId2);
scope.$digest();

expect(vm.submissionsBody.data.files[2].mediaType).to.equal('application/octet-stream');
});
});

describe('uploadSubmission', function() {
it('adds comments to the submissions body', function() {
vm.comments = 'test comments';
scope.$digest();

vm.uploadSubmission();
scope.$digest();

expect(vm.submissionsBody.data.submitterComments).to.equal('test comments');
});

it('calls the submission service', function() {
var mockAPICall = sinon.spy(submissionsService, 'getPresignedURL');

vm.uploadSubmission();
scope.$digest();

expect(mockAPICall).calledOnce;
});
});

describe('refreshPage', function() {
it('reloads the page', function() {
var mockRefresh = sinon.spy(mockWindow.location, 'reload');

vm.refreshPage();
scope.$digest();

expect(mockRefresh).calledWith(true);
expect(mockRefresh).calledOnce;
});
});

describe('cancelRetry', function() {
it('sets showProgress to false', function() {
vm.showProgress = true;
scope.$digest();
expect(vm.showProgress).to.be.true;

vm.cancelRetry();
scope.$digest();
expect(vm.showProgress).to.be.false;
});
});
});
6 changes: 6 additions & 0 deletions assets/css/submissions/submissions.scss
Original file line number Diff line number Diff line change
@@ -44,3 +44,9 @@
}
}
}

.submissions-access-error {
text-align: center;
margin-left: auto;
margin-right: auto;
}