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

Feature/sup 1159 upload progress bar #646

Merged
merged 6 commits into from
Jan 8, 2016
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions app/directives/progress-bar/progress-bar.directive.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.progress-bar
.progress-bar__bar
.progress-bar__bar--completed
.progress-bar__summary
span(ng-bind="completed")
span % {{message}}
29 changes: 29 additions & 0 deletions app/directives/progress-bar/progress-bar.directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
(function() {
'use strict';

angular.module('tcUIComponents').directive('progressBar', progressBar);

progressBar.$inject = ['$timeout', '$parse'];

function progressBar($timeout, $parse) {
return {
restrict: 'E',
templateUrl: 'directives/progress-bar/progress-bar.directive.html',
link: function(scope, element, attr) {
var model = $parse(attr.completed);
var msg = attr.message;
var progress = angular.element(element[0].querySelector('.progress-bar__bar--completed'));

scope.$watch(model, function(newValue, oldValue) {
scope.completed = Math.round(newValue);
// console.log("Updating progress bar with " + scope.completed);
scope.message = msg;
progress.css('width', scope.completed + '%')
});
},
controller: ['$scope', function($scope) {

}]
};
}
})();
3 changes: 3 additions & 0 deletions app/index.jade
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ html
link(rel='stylesheet', href='../bower_components/intro.js/introjs.css')
link(rel='stylesheet', href='../bower_components/angularjs-toaster/toaster.css')
link(rel='stylesheet', href='../bower_components/react-select/dist/react-select.min.css')
link(rel='stylesheet', href='../bower_components/appirio-tech-ng-ui-components/dist/main.css')
link(rel='stylesheet', href='../bower_components/fontawesome/css/font-awesome.css')
link(rel='stylesheet', href='../bower_components/ng-notifications-bar/dist/ngNotificationsBar.min.css')
link(rel='stylesheet', href='../bower_components/ngDialog/css/ngDialog.css')
Expand Down Expand Up @@ -72,6 +73,7 @@ html
link(rel="stylesheet", href="assets/css/directives/srm-tile.css")
link(rel="stylesheet", href="assets/css/directives/skill-tile.css")
link(rel="stylesheet", href="assets/css/directives/responsive-carousel.css")
link(rel="stylesheet", href="assets/css/directives/progress-bar.directive.css")
link(rel="stylesheet", href="assets/css/directives/profile-widget.css")
link(rel="stylesheet", href="assets/css/directives/page-state-header.directive.css")
link(rel="stylesheet", href="assets/css/directives/ios-card.css")
Expand Down Expand Up @@ -221,6 +223,7 @@ html
script(src="directives/page-state-header/page-state-header.directive.js")
script(src="directives/preventEventPropagation.directive.js")
script(src="directives/profile-widget/profile-widget.directive.js")
script(src="directives/progress-bar/progress-bar.directive.js")
script(src="directives/responsive-carousel/responsive-carousel.directive.js")
script(src="directives/skill-tile/skill-tile.directive.js")
script(src="directives/slideable.directive.js")
Expand Down
39 changes: 30 additions & 9 deletions app/services/submissions.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@

return service;

function getPresignedURL(body, files) {
function getPresignedURL(body, files, progressCallback) {
console.log('Body of request for presigned url: ', body);

return api.all('submissions').customPOST(body)
.then(function(response) {
console.log('POST/Presigned URL Response: ', response.plain());

uploadSubmissionFileToS3(response, response.data.files, files);
progressCallback.call(progressCallback, 'PREPARE', 100);
uploadSubmissionFileToS3(response, response.data.files, files, progressCallback);
})
.catch(function(err) {
console.log(err);
$log.info('Error getting presigned url');
progressCallback.call(progressCallback, 'ERROR', err);
toaster.pop('error', 'Whoops!', 'There was an error uploading your submissions. Please try again later.');
});
}

function uploadSubmissionFileToS3(presignedURLResponse, filesWithPresignedURL, files) {
function uploadSubmissionFileToS3(presignedURLResponse, filesWithPresignedURL, files, progressCallback) {

var promises = filesWithPresignedURL.map(function(fileWithPresignedURL) {
var deferred = $q.defer();
Expand All @@ -42,6 +43,22 @@
xhr.open('PUT', fileWithPresignedURL.preSignedUploadUrl, true);
xhr.setRequestHeader('Content-Type', fileWithPresignedURL.mediaType);

xhr.upload.addEventListener("progress", function(oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total;
// console.log("Completed " + percentComplete);
if (progressCallback && typeof progressCallback === 'function') {
progressCallback.call(progressCallback, 'UPLOAD', {
file: fileWithPresignedURL.preSignedUploadUrl,
progress: percentComplete*100
});
}
// ...
} else {
// Unable to compute progress information since the total size is unknown
}
});

// xhr version of the success callback
xhr.onreadystatechange = function() {
var status = xhr.status;
Expand Down Expand Up @@ -74,17 +91,18 @@
.then(function(response) {
console.log('response from S3: ', response);
console.log('response to use .save restnagular with: ', presignedURLResponse);

progressCallback.call(progressCallback, 'UPLOAD', 100);
// Update and start processing
updateSubmissionStatus(presignedURLResponse.plain());
updateSubmissionStatus(presignedURLResponse.plain(), progressCallback);

})
.catch(function(err) {
progressCallback.call(progressCallback, 'ERROR', err);
console.log('error uploading to S3: ', err);
});
}

function updateSubmissionStatus(body) {
function updateSubmissionStatus(body, progressCallback) {
// Pass data from upload to S3
body.data.files.forEach(function(file) {
file.status = 'UPLOADED';
Expand All @@ -93,24 +111,27 @@
return api.one('submissions', body.id).customPUT(body)
.then(function(response) {
$log.info('Successfully updated file statuses');
recordCompletedSubmission(response.plain());
recordCompletedSubmission(response.plain(), progressCallback);
})
.catch(function(err) {
$log.info('Error updating file statuses');
$log.error(err);
progressCallback.call(progressCallback, 'ERROR', err);
});
}

function recordCompletedSubmission(body) {
function recordCompletedSubmission(body, progressCallback) {
// Once all uploaded, make record and begin processing
return api.one('submissions', body.id).customPOST(body, 'process')
.then(function(response) {
$log.info('Successfully made file record. Beginning processing');
console.log('response from process call: ', response);
progressCallback.call(progressCallback, 'FINISH', 100);
})
.catch(function(err) {
$log.info('Error in starting processing');
$log.error(err);
progressCallback.call(progressCallback, 'ERROR', err);
});
}
};
Expand Down
78 changes: 75 additions & 3 deletions app/submissions/submit-file/submit-file.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@

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

SubmitFileController.$inject = ['$stateParams', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];
SubmitFileController.$inject = ['$scope', '$stateParams', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];

function SubmitFileController($stateParams, UserService, SubmissionsService, challengeToSubmitTo) {
function SubmitFileController($scope, $stateParams, UserService, SubmissionsService, challengeToSubmitTo) {
var vm = this;
var files = {};
var fileUploadProgress = {};
vm.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/);
vm.rankRegEx = new RegExp(/^[1-9]\d*$/);
vm.comments = '';
vm.uploadProgress = 0;
vm.uploading = false;
vm.preparing = false;
vm.finishing = false;
vm.showProgress = false;
vm.errorInUpload = false;
vm.formFonts = [{
id: 0,
source: '',
Expand Down Expand Up @@ -69,6 +76,7 @@
vm.setFileReference = setFileReference;
vm.uploadSubmission = uploadSubmission;
vm.createAnotherStockArtFieldset = createAnotherStockArtFieldset;
vm.cancelRetry = cancelRetry;

activate();

Expand Down Expand Up @@ -128,6 +136,11 @@
}

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

Expand Down Expand Up @@ -161,7 +174,66 @@
}

console.log('Body for request: ', vm.submissionsBody);
SubmissionsService.getPresignedURL(vm.submissionsBody, files);
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;
console.log('Prapared');
}
} 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) {
console.log('Uploaded');
vm.uploading = false;
vm.finishing = true;
}
} else if (phase === 'FINISH') {
// we are concerned only for completion of the phase
if (args === 100) {
console.log('Finished');
vm.finishing = false;
vm.showProgress = false;

// TODO redirect to submission listing / challenge details page
}
} else { // assume it to be error condition
console.log("Else: " + phase);
vm.errorInUpload = true;
}
}

function cancelRetry() {
vm.showProgress = false;
// TODO redirect to submission listing / challenge details page
}
}
})();
22 changes: 22 additions & 0 deletions app/submissions/submit-file/submit-file.jade
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,25 @@
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="true", 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(ng-src="/images/robot.svg", ng-hide="vm.errorInUpload")
img.upload-progress__image--error(ng-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, you’ll loose all files!
p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! The file 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")
span Finishing...
.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
12 changes: 11 additions & 1 deletion app/submissions/submit-file/submit-file.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
describe('Submit File Controller', function() {
var controller;
var vm;
var scope;

var mockChallenge = {
challenge: {
Expand All @@ -21,13 +22,16 @@ describe('Submit File Controller', function() {

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

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

bard.verifyNoOutstandingHttpRequests();

beforeEach(function() {
controller = $controller('SubmitFileController', {
$scope: scope,
UserService: userService,
challengeToSubmitTo: mockChallenge
});
Expand All @@ -37,4 +41,10 @@ describe('Submit File Controller', function() {
it('should exist', function() {
expect(vm).to.exist;
});

describe('updateProgress ', function() {
it('should update PREPARE phase end ', function() {

});
});
});
21 changes: 21 additions & 0 deletions assets/css/directives/progress-bar.directive.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import 'topcoder/tc-includes';

.progress-bar {
.progress-bar__bar {
background-color: $gray-light;
height: 10px;

.progress-bar__bar--completed {
background-color: $dark-blue;
height: 100%;
}
}

.progress-bar__summary {
@include font-with-weight('Sofia Pro', 500);
font-size: 12px;
line-height: 14px;
color: $accent-gray-dark;
margin-top: 20px;
}
}
Loading