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

Commit b0d5df1

Browse files
committed
Merge pull request #646 from appirio-tech/feature/sup-1159-upload-progress-bar
Feature/sup 1159 upload progress bar
2 parents 83a3daa + 9f073ea commit b0d5df1

File tree

11 files changed

+286
-14
lines changed

11 files changed

+286
-14
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.progress-bar
2+
.progress-bar__bar
3+
.progress-bar__bar--completed
4+
.progress-bar__summary
5+
span(ng-bind="completed")
6+
span % {{message}}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
(function() {
2+
'use strict';
3+
4+
angular.module('tcUIComponents').directive('progressBar', progressBar);
5+
6+
progressBar.$inject = ['$timeout', '$parse'];
7+
8+
function progressBar($timeout, $parse) {
9+
return {
10+
restrict: 'E',
11+
templateUrl: 'directives/progress-bar/progress-bar.directive.html',
12+
link: function(scope, element, attr) {
13+
var model = $parse(attr.completed);
14+
var msg = attr.message;
15+
var progress = angular.element(element[0].querySelector('.progress-bar__bar--completed'));
16+
17+
scope.$watch(model, function(newValue, oldValue) {
18+
scope.completed = Math.round(newValue);
19+
// console.log("Updating progress bar with " + scope.completed);
20+
scope.message = msg;
21+
progress.css('width', scope.completed + '%')
22+
});
23+
},
24+
controller: ['$scope', function($scope) {
25+
26+
}]
27+
};
28+
}
29+
})();

app/index.jade

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ html
1717
link(rel='stylesheet', href='../bower_components/intro.js/introjs.css')
1818
link(rel='stylesheet', href='../bower_components/angularjs-toaster/toaster.css')
1919
link(rel='stylesheet', href='../bower_components/react-select/dist/react-select.min.css')
20+
link(rel='stylesheet', href='../bower_components/appirio-tech-ng-ui-components/dist/main.css')
2021
link(rel='stylesheet', href='../bower_components/fontawesome/css/font-awesome.css')
2122
link(rel='stylesheet', href='../bower_components/ng-notifications-bar/dist/ngNotificationsBar.min.css')
2223
link(rel='stylesheet', href='../bower_components/ngDialog/css/ngDialog.css')
@@ -72,6 +73,7 @@ html
7273
link(rel="stylesheet", href="assets/css/directives/srm-tile.css")
7374
link(rel="stylesheet", href="assets/css/directives/skill-tile.css")
7475
link(rel="stylesheet", href="assets/css/directives/responsive-carousel.css")
76+
link(rel="stylesheet", href="assets/css/directives/progress-bar.directive.css")
7577
link(rel="stylesheet", href="assets/css/directives/profile-widget.css")
7678
link(rel="stylesheet", href="assets/css/directives/page-state-header.directive.css")
7779
link(rel="stylesheet", href="assets/css/directives/ios-card.css")
@@ -221,6 +223,7 @@ html
221223
script(src="directives/page-state-header/page-state-header.directive.js")
222224
script(src="directives/preventEventPropagation.directive.js")
223225
script(src="directives/profile-widget/profile-widget.directive.js")
226+
script(src="directives/progress-bar/progress-bar.directive.js")
224227
script(src="directives/responsive-carousel/responsive-carousel.directive.js")
225228
script(src="directives/skill-tile/skill-tile.directive.js")
226229
script(src="directives/slideable.directive.js")

app/services/submissions.service.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,24 @@
1717

1818
return service;
1919

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

2323
return api.all('submissions').customPOST(body)
2424
.then(function(response) {
2525
console.log('POST/Presigned URL Response: ', response.plain());
26-
27-
uploadSubmissionFileToS3(response, response.data.files, files);
26+
progressCallback.call(progressCallback, 'PREPARE', 100);
27+
uploadSubmissionFileToS3(response, response.data.files, files, progressCallback);
2828
})
2929
.catch(function(err) {
3030
console.log(err);
3131
$log.info('Error getting presigned url');
32+
progressCallback.call(progressCallback, 'ERROR', err);
3233
toaster.pop('error', 'Whoops!', 'There was an error uploading your submissions. Please try again later.');
3334
});
3435
}
3536

36-
function uploadSubmissionFileToS3(presignedURLResponse, filesWithPresignedURL, files) {
37+
function uploadSubmissionFileToS3(presignedURLResponse, filesWithPresignedURL, files, progressCallback) {
3738

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

46+
xhr.upload.addEventListener("progress", function(oEvent) {
47+
if (oEvent.lengthComputable) {
48+
var percentComplete = oEvent.loaded / oEvent.total;
49+
// console.log("Completed " + percentComplete);
50+
if (progressCallback && typeof progressCallback === 'function') {
51+
progressCallback.call(progressCallback, 'UPLOAD', {
52+
file: fileWithPresignedURL.preSignedUploadUrl,
53+
progress: percentComplete*100
54+
});
55+
}
56+
// ...
57+
} else {
58+
// Unable to compute progress information since the total size is unknown
59+
}
60+
});
61+
4562
// xhr version of the success callback
4663
xhr.onreadystatechange = function() {
4764
var status = xhr.status;
@@ -74,17 +91,18 @@
7491
.then(function(response) {
7592
console.log('response from S3: ', response);
7693
console.log('response to use .save restnagular with: ', presignedURLResponse);
77-
94+
progressCallback.call(progressCallback, 'UPLOAD', 100);
7895
// Update and start processing
79-
updateSubmissionStatus(presignedURLResponse.plain());
96+
updateSubmissionStatus(presignedURLResponse.plain(), progressCallback);
8097

8198
})
8299
.catch(function(err) {
100+
progressCallback.call(progressCallback, 'ERROR', err);
83101
console.log('error uploading to S3: ', err);
84102
});
85103
}
86104

87-
function updateSubmissionStatus(body) {
105+
function updateSubmissionStatus(body, progressCallback) {
88106
// Pass data from upload to S3
89107
body.data.files.forEach(function(file) {
90108
file.status = 'UPLOADED';
@@ -93,24 +111,27 @@
93111
return api.one('submissions', body.id).customPUT(body)
94112
.then(function(response) {
95113
$log.info('Successfully updated file statuses');
96-
recordCompletedSubmission(response.plain());
114+
recordCompletedSubmission(response.plain(), progressCallback);
97115
})
98116
.catch(function(err) {
99117
$log.info('Error updating file statuses');
100118
$log.error(err);
119+
progressCallback.call(progressCallback, 'ERROR', err);
101120
});
102121
}
103122

104-
function recordCompletedSubmission(body) {
123+
function recordCompletedSubmission(body, progressCallback) {
105124
// Once all uploaded, make record and begin processing
106125
return api.one('submissions', body.id).customPOST(body, 'process')
107126
.then(function(response) {
108127
$log.info('Successfully made file record. Beginning processing');
109128
console.log('response from process call: ', response);
129+
progressCallback.call(progressCallback, 'FINISH', 100);
110130
})
111131
.catch(function(err) {
112132
$log.info('Error in starting processing');
113133
$log.error(err);
134+
progressCallback.call(progressCallback, 'ERROR', err);
114135
});
115136
}
116137
};

app/submissions/submit-file/submit-file.controller.js

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@
33

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

6-
SubmitFileController.$inject = ['$stateParams', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];
6+
SubmitFileController.$inject = ['$scope', '$stateParams', 'UserService', 'SubmissionsService', 'challengeToSubmitTo'];
77

8-
function SubmitFileController($stateParams, UserService, SubmissionsService, challengeToSubmitTo) {
8+
function SubmitFileController($scope, $stateParams, UserService, SubmissionsService, challengeToSubmitTo) {
99
var vm = this;
1010
var files = {};
11+
var fileUploadProgress = {};
1112
vm.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/);
1213
vm.rankRegEx = new RegExp(/^[1-9]\d*$/);
1314
vm.comments = '';
15+
vm.uploadProgress = 0;
16+
vm.uploading = false;
17+
vm.preparing = false;
18+
vm.finishing = false;
19+
vm.showProgress = false;
20+
vm.errorInUpload = false;
1421
vm.formFonts = [{
1522
id: 0,
1623
source: '',
@@ -69,6 +76,7 @@
6976
vm.setFileReference = setFileReference;
7077
vm.uploadSubmission = uploadSubmission;
7178
vm.createAnotherStockArtFieldset = createAnotherStockArtFieldset;
79+
vm.cancelRetry = cancelRetry;
7280

7381
activate();
7482

@@ -128,6 +136,11 @@
128136
}
129137

130138
function uploadSubmission() {
139+
vm.errorInUpload = false;
140+
vm.uploadProgress = 0;
141+
vm.fileUploadProgress = {};
142+
vm.showProgress = true;
143+
vm.preparing = true;
131144
vm.submissionsBody.data.submitterComments = vm.comments;
132145
vm.submissionsBody.data.submitterRank = vm.submissionForm.submitterRank;
133146

@@ -161,7 +174,66 @@
161174
}
162175

163176
console.log('Body for request: ', vm.submissionsBody);
164-
SubmissionsService.getPresignedURL(vm.submissionsBody, files);
177+
SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress);
178+
}
179+
180+
/**
181+
* Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH
182+
* of the submission upload and updates the progress UI accordingly.
183+
*/
184+
function updateProgress(phase, args) {
185+
// for PREPARE phase
186+
if (phase === 'PREPARE') {
187+
// we are concerned only for completion of the phase
188+
if (args === 100) {
189+
vm.preparing = false;
190+
vm.uploading = true;
191+
console.log('Prapared');
192+
}
193+
} else if (phase === 'UPLOAD') {
194+
// if args is object, this update is about XHRRequest's upload progress
195+
if (typeof args === 'object') {
196+
var requestId = args.file;
197+
var progress = args.progress;
198+
if (!fileUploadProgress[requestId] || fileUploadProgress[requestId] < progress) {
199+
fileUploadProgress[requestId] = progress;
200+
}
201+
var total = 0, count = 0;
202+
for(var requestId in fileUploadProgress) {
203+
var prog = fileUploadProgress[requestId];
204+
total += prog;
205+
count++;
206+
}
207+
vm.uploadProgress = total / count;
208+
// initiate digest cycle because this event (xhr event) is caused outside angular
209+
$scope.$apply();
210+
} else { // typeof args === 'number', mainly used a s fallback to mark completion of the UPLOAD phase
211+
vm.uploadProgress = args;
212+
}
213+
// start next phase when UPLOAD is done
214+
if (vm.uploadProgress == 100) {
215+
console.log('Uploaded');
216+
vm.uploading = false;
217+
vm.finishing = true;
218+
}
219+
} else if (phase === 'FINISH') {
220+
// we are concerned only for completion of the phase
221+
if (args === 100) {
222+
console.log('Finished');
223+
vm.finishing = false;
224+
vm.showProgress = false;
225+
226+
// TODO redirect to submission listing / challenge details page
227+
}
228+
} else { // assume it to be error condition
229+
console.log("Else: " + phase);
230+
vm.errorInUpload = true;
231+
}
232+
}
233+
234+
function cancelRetry() {
235+
vm.showProgress = false;
236+
// TODO redirect to submission listing / challenge details page
165237
}
166238
}
167239
})();

app/submissions/submit-file/submit-file.jade

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,25 @@
159159
label(for="agree-to-terms") I understand and agree
160160

161161
button.tc-btn.tc-btn-secondary(type="submit", ng-disabled="submissionForm.$invalid") Submit
162+
163+
modal.transition(show="vm.showProgress", background-click-close="true", style="background-color:white;")
164+
.upload-progress(ng-class="{'upload-progress--error': vm.errorInUpload}")
165+
.upload-progress__title
166+
p Uploading submission for
167+
p.upload-progress-title__challenge-name [Challenge name]
168+
img.upload-progress__image(ng-src="/images/robot.svg", ng-hide="vm.errorInUpload")
169+
img.upload-progress__image--error(ng-src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload")
170+
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!
171+
p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! The file couldn’t be uploaded, I’m so sorry.
172+
173+
progress-bar.upload-progress__progress-bar(completed="vm.uploadProgress", message="of 3 files uploaded")
174+
175+
.upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload")
176+
span Preparing...
177+
.upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload")
178+
span Finishing...
179+
.upload-progress__error(ng-show="vm.errorInUpload")
180+
span File upload failed
181+
.upload-progress__error-action(ng-show="vm.errorInUpload")
182+
button.tc-btn.tc-btn-s.tc-btn-ghost(type="button", ng-click="vm.cancelRetry()") Cancel
183+
button.tc-btn.tc-btn-s.tc-btn-secondary(type="button", ng-click="submissionForm.$valid && vm.uploadSubmission()") Try Again

app/submissions/submit-file/submit-file.spec.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
describe('Submit File Controller', function() {
33
var controller;
44
var vm;
5+
var scope;
56

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

2223
beforeEach(function() {
2324
bard.appModule('tc.submissions');
24-
bard.inject(this, '$controller');
25+
bard.inject(this, '$controller', '$rootScope');
26+
27+
scope = $rootScope.$new();
2528
});
2629

2730
bard.verifyNoOutstandingHttpRequests();
2831

2932
beforeEach(function() {
3033
controller = $controller('SubmitFileController', {
34+
$scope: scope,
3135
UserService: userService,
3236
challengeToSubmitTo: mockChallenge
3337
});
@@ -37,4 +41,10 @@ describe('Submit File Controller', function() {
3741
it('should exist', function() {
3842
expect(vm).to.exist;
3943
});
44+
45+
describe('updateProgress ', function() {
46+
it('should update PREPARE phase end ', function() {
47+
48+
});
49+
});
4050
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@import 'topcoder/tc-includes';
2+
3+
.progress-bar {
4+
.progress-bar__bar {
5+
background-color: $gray-light;
6+
height: 10px;
7+
8+
.progress-bar__bar--completed {
9+
background-color: $dark-blue;
10+
height: 100%;
11+
}
12+
}
13+
14+
.progress-bar__summary {
15+
@include font-with-weight('Sofia Pro', 500);
16+
font-size: 12px;
17+
line-height: 14px;
18+
color: $accent-gray-dark;
19+
margin-top: 20px;
20+
}
21+
}

0 commit comments

Comments
 (0)