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

Commit 9233e3a

Browse files
committed
Merge pull request #631 from appirio-tech/submissions-header
Submissions header
2 parents a285984 + 82a9a05 commit 9233e3a

34 files changed

+970
-200
lines changed

app/directives/on-file-change.directive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
return {
1212
restrict: 'A',
1313
link: function(scope, element, attr, ctrl) {
14-
element.bind("change", function() {
14+
element.bind('change', function() {
1515
scope.vm.onFileChange(element[0].files[0]);
1616
this.value = '';
1717
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
(function() {
2+
'use strict';
3+
4+
angular.module('tcUIComponents').directive('tcFileInput', tcFileInput);
5+
6+
function tcFileInput() {
7+
return {
8+
restrict: 'E',
9+
templateUrl: 'directives/tc-file-input/tc-file-input.html',
10+
scope: {
11+
labelText: '@',
12+
fieldId: '@',
13+
placeholder: '@',
14+
fileType: '@',
15+
mandatory: '=',
16+
buttonText: '@',
17+
setFileReference: '&'
18+
},
19+
link: function(scope, element, attrs) {
20+
scope.selectFile = selectFile;
21+
22+
// fieldId is not set on element at this point, so grabbing with class .none
23+
// which exists on the element right away
24+
var fileInput = $(element[0]).find('.none');
25+
var fileNameInput = $(element[0]).find('input[type=text]');
26+
27+
fileInput.bind('change', function() {
28+
var file = fileInput[0].files[0];
29+
30+
// Pass file object up through callback into controller
31+
scope.setFileReference({file: file, fieldId: scope.fieldId});
32+
33+
// Set the file name as the value of the disabled input
34+
fileNameInput[0].value = file.name;
35+
});
36+
37+
function selectFile() {
38+
fileInput.click();
39+
}
40+
}
41+
}
42+
}
43+
})();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.tc-file-field__label
2+
label.tc-label {{labelText}}
3+
span.lowercase(ng-if="fileType") {{fileType | addBeginningSpace}}
4+
5+
span.tc-label__mandatory.lowercase(ng-if="mandatory") #[span *]mandatory
6+
7+
.tc-file-field__inputs
8+
input.tc-file-field__input(type="text", placeholder="{{placeholder}}", required)
9+
10+
button.tc-btn(ng-click="selectFile()") {{buttonText}}
11+
12+
input.none(type="file", id="{{fieldId}}")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* jshint -W117, -W030 */
2+
describe('Topcoder File Input Directive', function() {
3+
var scope;
4+
5+
// USE AS TEMPLATE FOR DIRECTIVES
6+
7+
beforeEach(function() {
8+
bard.appModule('tcUIComponents');
9+
bard.inject(this, '$compile', '$rootScope');
10+
});
11+
12+
bard.verifyNoOutstandingHttpRequests();
13+
14+
xdescribe('', function() {
15+
beforeEach(function() {});
16+
17+
it('', function() {});
18+
});
19+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
(function() {
2+
'use strict';
3+
4+
angular.module('tcUIComponents').directive('tcInput', tcInput);
5+
6+
function tcInput() {
7+
return {
8+
restrict: 'E',
9+
templateUrl: 'directives/tc-input/tc-input.html',
10+
scope: {
11+
labelText: '@',
12+
placeholder: '@',
13+
inputValue: '='
14+
}
15+
}
16+
}
17+
})();

app/directives/tc-input/tc-input.jade

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
label.tc-label {{labelText}}
2+
3+
input(type="text", placeholder="{{placeholder}}", ng-model="inputValue")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(function() {
2+
'use strict';
3+
4+
angular.module('tcUIComponents').directive('tcTextarea', tcTextarea);
5+
6+
function tcTextarea() {
7+
return {
8+
restrict: 'E',
9+
templateUrl: 'directives/tc-textarea/tc-textarea.html',
10+
scope: {
11+
labelText: '@',
12+
placeholder: '@',
13+
characterCount: '=',
14+
characterCountMax: '@',
15+
value: '='
16+
}
17+
}
18+
}
19+
})();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
label.tc-label.tc-textarea__label {{labelText}}
2+
span.tc-textarea__char-count(ng-if="characterCount")
3+
span.tc-textarea__char-count--current {{value.length || 0}}
4+
5+
span {{' / ' + characterCountMax}}
6+
7+
textarea(placeholder="{{placeholder}}", ng-model="value", maxlength="{{characterCountMax}}")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(function() {
2+
'use strict';
3+
4+
angular.module('topcoder').filter('addBeginningSpace', addBeginningSpace);
5+
6+
function addBeginningSpace() {
7+
return function(input) {
8+
return ' ' + input;
9+
};
10+
};
11+
})();

app/filters/filters.spec.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ describe('filters', function() {
33

44
beforeEach(function() {
55
bard.appModule('topcoder');
6-
bard.inject(this, 'CONSTANTS', 'roleFilter', 'percentageFilter', 'ordinalFilter', 'displayLocationFilter', 'listRolesFilter', 'trackFilter', 'challengeLinksFilter', 'externalLinkColorFilter', 'emptyFilter', 'ternaryFilter', 'urlProtocolFilter');
6+
bard.inject(this, 'CONSTANTS', 'roleFilter', 'percentageFilter', 'ordinalFilter', 'displayLocationFilter', 'listRolesFilter', 'trackFilter', 'challengeLinksFilter', 'externalLinkColorFilter', 'emptyFilter', 'ternaryFilter', 'urlProtocolFilter', 'addBeginningSpaceFilter');
77
domain = CONSTANTS.domain;
88
});
99

@@ -100,7 +100,7 @@ describe('filters', function() {
100100
});
101101

102102
describe('externalLinkColorFilter', function() {
103-
103+
104104
it('should handle twitter and linkedin correctly', function() {
105105
expect(externalLinkColorFilter('el-twitter')).to.be.equal('#62AADC');
106106
expect(externalLinkColorFilter('el-linkedin')).to.be.equal('#127CB5');
@@ -150,4 +150,10 @@ describe('filters', function() {
150150
expect(urlProtocolFilter('https://google.com')).to.be.equal('https://google.com');
151151
});
152152
});
153+
154+
describe('addBeginningSpaceFilter', function() {
155+
it('should add a space to the beginning of the input', function() {
156+
expect(addBeginningSpaceFilter('some text')).to.equal(' some text');
157+
});
158+
});
153159
});

app/index.jade

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ html
1616
link(rel='stylesheet', href='../bower_components/angular-dropdowns/dist/angular-dropdowns.css')
1717
link(rel='stylesheet', href='../bower_components/intro.js/introjs.css')
1818
link(rel='stylesheet', href='../bower_components/angularjs-toaster/toaster.css')
19+
link(rel='stylesheet', href='../bower_components/react-select/dist/react-select.min.css')
1920
link(rel='stylesheet', href='../bower_components/fontawesome/css/font-awesome.css')
2021
link(rel='stylesheet', href='../bower_components/ng-notifications-bar/dist/ngNotificationsBar.min.css')
2122
link(rel='stylesheet', href='../bower_components/ngDialog/css/ngDialog.css')
@@ -26,8 +27,11 @@ html
2627
2728
// build:css /styles/app.css
2829
//- inject:css
30+
link(rel="stylesheet", href="assets/css/vendors/introjs.css")
2931
link(rel="stylesheet", href="assets/css/vendors/angucomplete.css")
3032
link(rel="stylesheet", href="assets/css/topcoder.css")
33+
link(rel="stylesheet", href="assets/css/submissions/submit-file.css")
34+
link(rel="stylesheet", href="assets/css/submissions/submissions.css")
3135
link(rel="stylesheet", href="assets/css/skill-picker/skill-picker.css")
3236
link(rel="stylesheet", href="assets/css/sitemap/sitemap.css")
3337
link(rel="stylesheet", href="assets/css/settings/update-password.css")
@@ -124,6 +128,7 @@ html
124128

125129
// build:js /js/vendor.js
126130
//- bower:js
131+
script(src='../bower_components/zepto/zepto.js')
127132
script(src='../bower_components/angular/angular.js')
128133
script(src='../bower_components/a0-angular-storage/dist/angular-storage.js')
129134
script(src='../bower_components/angucomplete-alt/angucomplete-alt.js')
@@ -143,9 +148,18 @@ html
143148
script(src='../bower_components/angular-animate/angular-animate.js')
144149
script(src='../bower_components/angularjs-toaster/toaster.js')
145150
script(src='../bower_components/appirio-tech-ng-iso-constants/dist/ng-iso-constants.js')
151+
script(src='../bower_components/angular-resource/angular-resource.js')
152+
script(src='../bower_components/moment/moment.js')
153+
script(src='../bower_components/angular-scroll/angular-scroll.js')
154+
script(src='../bower_components/react/react.js')
155+
script(src='../bower_components/react/react-dom.js')
156+
script(src='../bower_components/classnames/index.js')
157+
script(src='../bower_components/react-input-autosize/dist/react-input-autosize.min.js')
158+
script(src='../bower_components/react-select/dist/react-select.min.js')
159+
script(src='../bower_components/ngReact/ngReact.js')
160+
script(src='../bower_components/appirio-tech-ng-ui-components/dist/main.js')
146161
script(src='../bower_components/d3/d3.js')
147162
script(src='../bower_components/jstzdetect/jstz.min.js')
148-
script(src='../bower_components/moment/moment.js')
149163
script(src='../bower_components/ng-busy/build/angular-busy.js')
150164
script(src='../bower_components/ng-notifications-bar/dist/ngNotificationsBar.min.js')
151165
script(src='../bower_components/ngDialog/js/ngDialog.js')
@@ -212,13 +226,17 @@ html
212226
script(src="directives/slideable.directive.js")
213227
script(src="directives/srm-tile/srm-tile.directive.js")
214228
script(src="directives/tc-endless-paginator/tc-endless-paginator.directive.js")
229+
script(src="directives/tc-file-input/tc-file-input.directive.js")
230+
script(src="directives/tc-input/tc-input.directive.js")
215231
script(src="directives/tc-paginator/tc-paginator.directive.js")
216232
script(src="directives/tc-section/tc-section.directive.js")
217233
script(src="directives/tc-sticky/tc-sticky.directive.js")
218234
script(src="directives/tc-tabs/tc-tabs.directive.js")
235+
script(src="directives/tc-textarea/tc-textarea.directive.js")
219236
script(src="directives/tc-transclude.directive.js")
220237
script(src="directives/track-toggle/track-toggle.directive.js")
221238
script(src="topcoder.module.js")
239+
script(src="filters/add-beginning-space.filter.js")
222240
script(src="filters/challengeLinks.filter.js")
223241
script(src="filters/deadline-msg.filter.js")
224242
script(src="filters/empty.filter.js")
@@ -291,6 +309,7 @@ html
291309
script(src="services/scorecard.service.js")
292310
script(src="services/srm.service.js")
293311
script(src="services/statistics.service.js")
312+
script(src="services/submissions.service.js")
294313
script(src="services/tags.service.js")
295314
script(src="services/tcAuth.service.js")
296315
script(src="services/user.service.js")
@@ -309,6 +328,7 @@ html
309328
script(src="submissions/submissions.module.js")
310329
script(src="submissions/submissions.controller.js")
311330
script(src="submissions/submissions.routes.js")
331+
script(src="submissions/submit-file/submit-file.controller.js")
312332
script(src="topcoder.constants.js")
313333
script(src="topcoder.controller.js")
314334
script(src="topcoder.interceptors.js")

app/services/api.service.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@
9393
param: element
9494
};
9595
}
96+
97+
if (url.indexOf('submissions') > -1 && (operation.toLowerCase() === 'put' || operation.toLowerCase() === 'post')) {
98+
return {
99+
param: element
100+
};
101+
}
102+
96103
return element;
97104
})
98105
.addResponseInterceptor(function(data, operation, what, url, response, deferred) {

app/services/submissions.service.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
(function() {
2+
'use strict';
3+
4+
angular.module('tc.services').factory('SubmissionsService', SubmissionsService);
5+
6+
SubmissionsService.$inject = ['CONSTANTS', 'ApiService', '$q', '$log', 'toaster'];
7+
8+
function SubmissionsService(CONSTANTS, ApiService, $q, $log, toaster) {
9+
var api = ApiService.restangularV3;
10+
11+
var service = {
12+
getPresignedURL: getPresignedURL,
13+
uploadSubmissionFileToS3: uploadSubmissionFileToS3,
14+
updateSubmissionStatus: updateSubmissionStatus,
15+
recordCompletedSubmission: recordCompletedSubmission
16+
};
17+
18+
return service;
19+
20+
function getPresignedURL(body, files) {
21+
console.log('Body of request for presigned url: ', body);
22+
23+
return api.all('submissions').customPOST(body)
24+
.then(function(response) {
25+
console.log('POST/Presigned URL Response: ', response.plain());
26+
27+
uploadSubmissionFileToS3(response, response.data.files, files);
28+
})
29+
.catch(function(err) {
30+
console.log(err);
31+
$log.info('Error getting presigned url');
32+
toaster.pop('error', 'Whoops!', 'There was an error uploading your submissions. Please try again later.');
33+
});
34+
}
35+
36+
function uploadSubmissionFileToS3(presignedURLResponse, files) {
37+
var filesWithPresignedURL = presignedURLResponse.data.files;
38+
39+
var promises = filesWithPresignedURL.map(function(fileWithPresignedURL) {
40+
var deferred = $q.defer();
41+
var xhr = new XMLHttpRequest();
42+
43+
xhr.open('PUT', fileWithPresignedURL.preSignedUploadUrl, true);
44+
xhr.setRequestHeader('Content-Type', fileWithPresignedURL.mediaType);
45+
46+
// xhr version of the success callback
47+
xhr.onreadystatechange = function() {
48+
var status = xhr.status;
49+
if (((status >= 200 && status < 300) || status === 304) && xhr.readyState === 4) {
50+
$log.info('Successfully uploaded file');
51+
console.log('xhr response: ', xhr.responseText);
52+
53+
// updateSubmissionStatus and then resolve?
54+
deferred.resolve();
55+
56+
} else if (status >= 400) {
57+
$log.error('Error uploading to S3 with status: ' + status);
58+
toaster.pop('error', 'Whoops!', 'There was an error uploading your files. Please try again later.');
59+
deferred.reject(err);
60+
}
61+
};
62+
63+
xhr.onerror = function(err) {
64+
$log.info('Error uploading to s3');
65+
toaster.pop('error', 'Whoops!', 'There was an error uploading your files. Please try again later.');
66+
deferred.reject(err);
67+
}
68+
69+
xhr.send(files[fileWithPresignedURL.type]);
70+
71+
return deferred.promise;
72+
});
73+
74+
return $q.all(promises)
75+
.then(function(response) {
76+
console.log('response from S3: ', response);
77+
console.log('response to use .save restnagular with: ', presignedURLResponse);
78+
79+
// Update and start processing
80+
updateSubmissionStatus(presignedURLResponse.plain());
81+
82+
})
83+
.catch(function(err) {
84+
console.log('error uploading to S3: ', err);
85+
});
86+
}
87+
88+
function updateSubmissionStatus(body) {
89+
// Pass data from upload to S3
90+
body.data.files.forEach(function(file) {
91+
file.status = 'UPLOADED';
92+
});
93+
94+
return api.one('submissions', body.id).customPUT(body)
95+
.then(function(response) {
96+
$log.info('Successfully updated file statuses');
97+
recordCompletedSubmission(response.plain());
98+
})
99+
.catch(function(err) {
100+
$log.info('Error updating file statuses');
101+
$log.error(err);
102+
});
103+
}
104+
105+
function recordCompletedSubmission(body) {
106+
// Once all uploaded, make record and begin processing
107+
return api.one('submissions', body.id).customPOST(body, 'process')
108+
.then(function(response) {
109+
$log.info('Successfully made file record. Beginning processing');
110+
console.log('response from process call: ', response);
111+
})
112+
.catch(function(err) {
113+
$log.info('Error in starting processing');
114+
$log.error(err);
115+
});
116+
}
117+
};
118+
})();

0 commit comments

Comments
 (0)