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

Commit 3b5d6dd

Browse files
committed
October release 1
#44 #61 (Major requirement) #42 (Major requirement) #43 #77 (Major requirement) #66
1 parent 33fa57b commit 3b5d6dd

25 files changed

+615
-246
lines changed

configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The following config parameters are supported, they are defined in `src/config.j
2727
|ALLOWED_TOPCODER_ROLES| The allowed Topcoder role to use Topcoder X app| see configuration |
2828
|COPILOT_ROLE| The role to identify copilot|'copilot'|
2929
|HELP_LINK| The link for help| 'https://github.com/topcoder-platform/topcoder-x-ui/wiki'|
30+
|ADMINISTRATOR_ROLES| The array of roles to be considered as admin| `['administrator', 'admin']`|
3031

3132
## GitHub OAuth App Setup
3233

src/common/helper.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ async function getProviderType(repoUrl) {
211211
* @param {String} provider the git provider
212212
* @returns {Object} the owner/copilot for the project
213213
*/
214-
async function getProjectOwner(models, project, provider) {
214+
async function getProjectCopilot(models, project, provider) {
215215
const userMapping = await models.UserMapping.findOne({
216-
topcoderUsername: project.username,
216+
topcoderUsername: project.copilot,
217217
});
218218

219219
if (!userMapping || (provider === 'github' && !userMapping.githubUserId) || (provider === 'gitlab' && !userMapping.gitlabUserId)) {
@@ -246,5 +246,5 @@ module.exports = {
246246
ensureExists,
247247
generateIdentifier,
248248
getProviderType,
249-
getProjectOwner,
249+
getProjectCopilot,
250250
};

src/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ module.exports = {
3939
ALLOWED_TOPCODER_ROLES: process.env.ALLOWED_TOPCODER_ROLES || ['administrator', 'admin', 'connect manager', 'connect admin', 'copilot', 'connect copilot'],
4040
COPILOT_ROLE: process.env.COPILOT_ROLE || 'copilot',
4141
HELP_LINK: process.env.HELP_LINK || 'https://github.com/topcoder-platform/topcoder-x-ui/wiki',
42+
ADMINISTRATOR_ROLES: process.env.ADMINISTRATOR_ROLES || ['administrator', 'admin'],
4243
};

src/controllers/ProjectController.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async function create(req) {
2828
* @returns {Object} the result
2929
*/
3030
async function update(req) {
31-
return await ProjectService.update(req.body, req.currentUser.handle);
31+
return await ProjectService.update(req.body, req.currentUser);
3232
}
3333

3434
/**
@@ -37,7 +37,7 @@ async function update(req) {
3737
* @returns {Array} the result
3838
*/
3939
async function getAll(req) {
40-
return await ProjectService.getAll(req.query.status, req.currentUser.handle);
40+
return await ProjectService.getAll(req.query, req.currentUser);
4141
}
4242

4343
/**
@@ -47,7 +47,7 @@ async function getAll(req) {
4747
* @returns {Object} the result
4848
*/
4949
async function createLabel(req) {
50-
return await ProjectService.createLabel(req.body, req.currentUser.handle);
50+
return await ProjectService.createLabel(req.body, req.currentUser);
5151
}
5252

5353
/**
@@ -57,7 +57,7 @@ async function createLabel(req) {
5757
* @returns {Object} the result
5858
*/
5959
async function createHook(req) {
60-
return await ProjectService.createHook(req.body, req.currentUser.handle);
60+
return await ProjectService.createHook(req.body, req.currentUser);
6161
}
6262

6363
/**
@@ -67,7 +67,17 @@ async function createHook(req) {
6767
* @returns {Object} the result
6868
*/
6969
async function addWikiRules(req) {
70-
return await ProjectService.addWikiRules(req.body, req.currentUser.handle);
70+
return await ProjectService.addWikiRules(req.body, req.currentUser);
71+
}
72+
73+
/**
74+
* transfer the ownership of project
75+
* @param {Object} req the request
76+
* @param {Object} res the response
77+
* @returns {Object} the result
78+
*/
79+
async function transferOwnerShip(req) {
80+
return await ProjectService.transferOwnerShip(req.body, req.currentUser);
7181
}
7282

7383
module.exports = {
@@ -77,6 +87,7 @@ module.exports = {
7787
createLabel,
7888
createHook,
7989
addWikiRules,
90+
transferOwnerShip,
8091
};
8192

8293
helper.buildController(module.exports);

src/front/src/app/app.js

+19-13
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ angular.module('topcoderX', [
5353
}],
5454
currentUser: ['AuthService', function (AuthService) {
5555
return AuthService.getCurrentUser();
56-
}]
56+
}],
57+
5758
}
5859
})
5960
.state('app', {
@@ -63,8 +64,11 @@ angular.module('topcoderX', [
6364
resolve: {
6465
currentUser: ['AuthService', function (AuthService) {
6566
return AuthService.getCurrentUser();
66-
}]
67-
}
67+
}],
68+
AppConfig: ['AuthService', function (AuthService) {
69+
return AuthService.getAppConfig();
70+
}],
71+
},
6872
})
6973
.state('app.main', {
7074
url: '/main',
@@ -84,6 +88,8 @@ angular.module('topcoderX', [
8488
})
8589
.state('app.projects', {
8690
url: '/projects',
91+
controller: 'ProjectsController',
92+
controllerAs: 'vm',
8793
templateUrl: 'app/projects/projects.html',
8894
data: { pageTitle: 'Project Management' },
8995
resolve: { auth: authenticate }
@@ -130,18 +136,18 @@ angular.module('topcoderX', [
130136
controllerAs: 'vm',
131137
})
132138
.state('app.copilotPayments', {
133-
url: '/copilot-payments',
134-
templateUrl: 'app/copilot-payments/copilot-payments.html',
135-
controller: 'CopilotPaymentsController',
136-
controllerAs: 'vm',
137-
resolve: { auth: authenticate }
139+
url: '/copilot-payments',
140+
templateUrl: 'app/copilot-payments/copilot-payments.html',
141+
controller: 'CopilotPaymentsController',
142+
controllerAs: 'vm',
143+
resolve: { auth: authenticate }
138144
})
139145
.state('app.addPayment', {
140-
url: '/copilot-payment',
141-
templateUrl: 'app/add-copilot-payment/add-copilot-payment.html',
142-
controller: 'AddCopilotPaymentController',
143-
controllerAs: 'vm',
144-
resolve: { auth: authenticate }
146+
url: '/copilot-payment',
147+
templateUrl: 'app/add-copilot-payment/add-copilot-payment.html',
148+
controller: 'AddCopilotPaymentController',
149+
controllerAs: 'vm',
150+
resolve: { auth: authenticate }
145151
});
146152

147153

src/front/src/app/auth/auth.service.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,24 @@ angular.module('topcoderX')
272272
currentUser.token = tctV3;
273273

274274
return currentUser;
275-
}
275+
};
276+
277+
/**
278+
* gets the application configurations
279+
*/
280+
AuthService.getAppConfig = function () {
281+
var tctV3 = AuthService.getTokenV3();
282+
283+
if (!tctV3) {
284+
return null;
285+
}
286+
return $http.get(Helper.baseUrl + '/api/v1/appConfig').then(function (res) {
287+
$rootScope.appConfig = res.data;
288+
return $q.resolve(res.data);
289+
}).catch(function (err) {
290+
return $q.reject(err);
291+
});
292+
};
276293

277294
return AuthService;
278295

src/front/src/app/filters.js

+29
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,33 @@ angular.module('topcoderX')
1111
return function (input) {
1212
return (!!input) ? input.charAt(0).toUpperCase() + input.substr(1).toLowerCase() : '';
1313
};
14+
})
15+
.filter('hourSince', function () {
16+
return function (date) {
17+
if (!date) {
18+
return;
19+
}
20+
var time = Date.parse(date);
21+
var difference = new Date() - time;
22+
var hours = difference / (1000 * 60 * 60);
23+
if (hours < 1) {
24+
return hours.toFixed(1);
25+
}
26+
return parseInt(hours, 10);
27+
};
28+
})
29+
.filter('hourSinceClass', function () {
30+
return function (hour) {
31+
if (!hour) {
32+
return;
33+
}
34+
var hoursInt = parseInt(hour, 10);
35+
if (hoursInt < 18) {
36+
return 'text-info';
37+
} else if (hoursInt >= 18 && hoursInt < 24) {
38+
return 'text-warning';
39+
} else if (hoursInt >= 24) {
40+
return 'text-danger';
41+
}
42+
};
1443
});

src/front/src/app/helper.js

+39-20
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,46 @@
66
'use strict';
77

88
angular.module('topcoderX')
9-
.factory('Helper', ['$location', function ($location) {
10-
var baseUrl = $location.protocol() + '://' + $location.host();
9+
.factory('Helper', ['$rootScope', '$location', function ($rootScope, $location) {
10+
var baseUrl = $location.protocol() + '://' + $location.host();
1111

12-
var service = {};
12+
var service = {};
1313

14-
service.baseUrl = baseUrl;
14+
service.baseUrl = baseUrl;
1515

16-
/**
17-
* gets the config based on host env
18-
*/
19-
service.config = function () {
20-
var tcDomain = baseUrl.indexOf('topcoder.com') > -1 ? 'topcoder.com' : 'topcoder-dev.com';
21-
return {
22-
TC_LOGIN_URL: 'https://accounts.' + tcDomain + '/member',
23-
TC_USER_PROFILE_URL: 'http://api.' + tcDomain + '/v2/user/profile',
24-
API_URL: 'https://api.' + tcDomain + '',
25-
ADMIN_TOOL_URL: 'https://api.' + tcDomain + '/v2',
26-
ACCOUNTS_CONNECTOR_URL: 'https://accounts.' + tcDomain + '/connector.html',
27-
DIRECT_URL_BASE: 'https://www.' + tcDomain + '/direct/projectOverview?formData.projectId='
28-
}
29-
}
30-
return service;
16+
/**
17+
* gets the config based on host env
18+
*/
19+
service.config = function () {
20+
var tcDomain = baseUrl.indexOf('topcoder.com') > -1 ? 'topcoder.com' : 'topcoder-dev.com';
21+
return {
22+
TC_LOGIN_URL: 'https://accounts.' + tcDomain + '/member',
23+
TC_USER_PROFILE_URL: 'http://api.' + tcDomain + '/v2/user/profile',
24+
API_URL: 'https://api.' + tcDomain + '',
25+
ADMIN_TOOL_URL: 'https://api.' + tcDomain + '/v2',
26+
ACCOUNTS_CONNECTOR_URL: 'https://accounts.' + tcDomain + '/connector.html',
27+
DIRECT_URL_BASE: 'https://www.' + tcDomain + '/direct/projectOverview?formData.projectId='
28+
}
29+
}
30+
31+
service.isAdminUser = function (currentUser) {
32+
var userRoles = currentUser.roles.map(function (x) {
33+
return x.toUpperCase();
34+
});
35+
var administratorRoles = $rootScope.appConfig.administratorRoles.map(function (x) {
36+
return x.toUpperCase();
37+
});
38+
var t;
39+
if (administratorRoles.length > userRoles.length) {
40+
t = administratorRoles;
41+
administratorRoles = userRoles;
42+
userRoles = t; // indexOf to loop over shorter
43+
}
44+
var matchedRoles = userRoles.filter(function (e) {
45+
return administratorRoles.indexOf(e.toUpperCase()) > -1;
46+
});
47+
return matchedRoles.length > 0;
48+
};
49+
return service;
50+
}]);
3151

32-
}]);

src/front/src/app/main/main.html

+12-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ <h2>Dashboard</h2>
3636
Project name
3737
<span class="footable-sort-indicator"></span>
3838
</th>
39+
<th class="footable-sortable" ng-click="sort('assignedAt', 'readyForReview')" ng-class="{'footable-sorted': tableConfig.readyForReview.sortBy === 'assignedAt' && tableConfig.readyForReview.sortDir === 'asc', 'footable-sorted-desc': tableConfig.readyForReview.sortBy === 'assignedAt' && tableConfig.readyForReview.sortDir === 'desc'}">
40+
Hours Assigned
41+
<span class="footable-sort-indicator"></span>
42+
</th>
3943
<th class="col-lg-3" data-sort-ignore="true">Link to the ticket</th>
4044
</tr>
4145
</thead>
@@ -45,14 +49,15 @@ <h2>Dashboard</h2>
4549
<td class="col-lg-2 col-md-2">[${{item.prizes[0]}}] {{item.title}}</td>
4650
<td class="col-lg-2">{{item.assignee}}</td>
4751
<td class="col-lg-2">{{item.projectId.title}}</td>
52+
<td class="col-lg-2 {{item.assignedAt| hourSince |hourSinceClass}}">{{item.assignedAt|hourSince}}</td>
4853
<td class="col-lg-2">
4954
<a href="{{item.projectId.repoUrl}}/issues/{{item.number}}" target="_blank">{{item.number}}</a>
5055
</td>
5156
</tr>
5257
</tbody>
5358
<tfoot>
5459
<tr>
55-
<td colspan="5">
60+
<td colspan="6">
5661
<ul class="pagination pull-right">
5762
<li class="footable-page-arrow" ng-class="{'disabled': tableConfig.readyForReview.pageNumber === 1}">
5863
<a ng-click="changePage(1, 'readyForReview')">«</a>
@@ -101,6 +106,10 @@ <h2>Dashboard</h2>
101106
Project name
102107
<span class="footable-sort-indicator"></span>
103108
</th>
109+
<th class="footable-sortable" ng-click="sort('assignedAt', 'assigned')" ng-class="{'footable-sorted': tableConfig.assigned.sortBy === 'assignedAt' && tableConfig.assigned.sortDir === 'asc', 'footable-sorted-desc': tableConfig.assigned.sortBy === 'assignedAt' && tableConfig.assigned.sortDir === 'desc'}">
110+
Hours Assigned
111+
<span class="footable-sort-indicator"></span>
112+
</th>
104113
<th class="col-lg-3" data-sort-ignore="true">Link to the ticket</th>
105114
</tr>
106115
</thead>
@@ -110,14 +119,15 @@ <h2>Dashboard</h2>
110119
<td class="col-lg-2 col-md-2">[${{item.prizes[0]}}] {{item.title}}</td>
111120
<td class="col-lg-2">{{item.assignee}}</td>
112121
<td class="col-lg-2">{{item.projectId.title}}</td>
122+
<td class="col-lg-2 {{item.assignedAt| hourSince |hourSinceClass}}">{{item.assignedAt|hourSince}}</td>
113123
<td class="col-lg-2">
114124
<a href="{{item.projectId.repoUrl}}/issues/{{item.number}}" target="_blank">{{item.number}}</a>
115125
</td>
116126
</tr>
117127
</tbody>
118128
<tfoot>
119129
<tr>
120-
<td colspan="5">
130+
<td colspan="6">
121131
<ul class="pagination pull-right">
122132
<li class="footable-page-arrow" ng-class="{'disabled': tableConfig.assigned.pageNumber === 1}">
123133
<a ng-click="changePage(1, 'assigned')">«</a>

src/front/src/app/projects/project.service.js

+22-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ angular.module('topcoderX')
2222
/**
2323
* Get all projects
2424
*/
25-
ProjectService.getProjects = function (status) {
26-
return $http.get(Helper.baseUrl + '/api/v1/projects?status=' + status).then(function (response) {
25+
ProjectService.getProjects = function (status, showAll) {
26+
return $http.get(Helper.baseUrl + '/api/v1/projects?status=' + status + '&showAll=' + showAll).then(function (response) {
2727
return response;
2828
});
2929
};
@@ -104,5 +104,25 @@ angular.module('topcoderX')
104104
return response;
105105
});
106106
};
107+
108+
/**
109+
* transfers the ownership of project
110+
* @param {String} projectId the project id
111+
* @param {String} owner the topcoder handle of owner user
112+
*/
113+
ProjectService.transferOwnership = function (pId, ownerHandle) {
114+
var req = {
115+
method: 'POST',
116+
url: Helper.baseUrl + '/api/v1/projects/transferOwnership',
117+
data: {
118+
projectId: pId,
119+
owner: ownerHandle,
120+
},
121+
};
122+
return $http(req).then(function (response) {
123+
return response;
124+
});
125+
};
126+
107127
return ProjectService;
108128
}]);

0 commit comments

Comments
 (0)