diff --git a/config.json b/config.json index 1f9b42f..3c099af 100644 --- a/config.json +++ b/config.json @@ -3,6 +3,7 @@ "ES_PROJECT_API_URL": "http://127.0.0.1:8553", "API_URL" : "https://127.0.0.1:8443", "WORK_API_URL" : "https://127.0.0.1:8443", + "ADMIN_TOOL_URL" : "http://localhost:8080/api/v2", "API_VERSION_PATH" : "v3", "AUTH0_CLIENT_ID" : "JFDo7HMkf0q2CkVFHojy3zHWafziprhT", "AUTH0_DOMAIN" : "127.0.0.1:8443", @@ -13,6 +14,7 @@ "ES_PROJECT_API_URL": "https://internal-api.topcoder-dev.com", "API_URL" : "https://api.topcoder-dev.com", "WORK_API_URL" : "https://api-work.topcoder-dev.com", + "ADMIN_TOOL_URL" : "https://api.topcoder-dev.com/v2", "API_VERSION_PATH" : "v3", "AUTH0_CLIENT_ID" : "JFDo7HMkf0q2CkVFHojy3zHWafziprhT", "AUTH0_DOMAIN" : "topcoder-dev.auth0.com", @@ -23,6 +25,7 @@ "ES_PROJECT_API_URL": "https://internal-api.topcoder-qa.com", "API_URL" : "https://api.topcoder-qa.com", "WORK_API_URL" : "https://api-work.topcoder-qa.com", + "ADMIN_TOOL_URL" : "https://api.topcoder-qa.com/v2", "API_VERSION_PATH" : "v3", "AUTH0_CLIENT_ID" : "EVOgWZlCtIFlbehkq02treuRRoJk12UR", "AUTH0_DOMAIN" : "topcoder-qa.auth0.com", @@ -33,6 +36,7 @@ "API_URL" : "https://api.topcoder.com", "WORK_API_URL" : "https://api-work.topcoder.com", "ES_PROJECT_API_URL": "https://internal-api.topcoder.com", + "ADMIN_TOOL_URL" : "https://api.topcoder.com/v2", "API_VERSION_PATH" : "v3", "AUTH0_CLIENT_ID" : "6ZwZEUo2ZK4c50aLPpgupeg5v2Ffxp9P", "AUTH0_DOMAIN" : "topcoder.auth0.com", diff --git a/src/app/admintool/admintool.controller.js b/src/app/admintool/admintool.controller.js new file mode 100644 index 0000000..b2126db --- /dev/null +++ b/src/app/admintool/admintool.controller.js @@ -0,0 +1,397 @@ +'use strict'; + +var module = angular.module('supportAdminApp'); + +module.controller('admintool.AdminToolController', [ + '$scope', '$timeout', '$uibModal', 'AuthService', 'AdminToolService', 'Alert', 'admintool.Constants', + function ($scope, $timeout, $modal, $authService, $adminToolService, $alert, $const) { + $scope.users = []; + $scope.reviewBoardProjectCategories = []; + $scope.v2Token = null; + $scope.constRoles = $const.Roles; + // search + $scope.formSearch = { + handle: '', + role: '', + categoryId: '', + isLoading: false, + usersFound: false, + reviewBoardProjectCategoriesFound: false, + isAdmin: function () { + return this.role === $const.Roles.Admin; + }, + isCopilot: function () { + return this.role === $const.Roles.Copilot; + }, + isReviewer: function () { + return this.role === $const.Roles.Reviewer; + }, + setLoading: function (loading) { + this.isLoading = loading; + } + }; + + + $scope.$on('adminTools.TableDataUpdated', function () { + $timeout(function () { + $('.footable').trigger('footable_redraw'); + }, 100); + }); + + + // auth + $scope.authorized = function () { + return $authService.isLoggedIn(); + }; + + $scope.changeRole = function () { + $scope.users = []; + $scope.formSearch.usersFound = false; + $alert.clear(); + }; + + // search users with role input and will use client side filter for handle + $scope.search = function (notClear) { + if (!notClear) { + $alert.clear(); + } + + var handle = $scope.formSearch.handle, role = $scope.formSearch.role; + + $scope.formSearch.setLoading(true); + var searchMethod = $scope.formSearch.isReviewer() ? + $adminToolService.findReviewers($scope.v2Token, $scope.formSearch.categoryId) + : $adminToolService['find' + role + 's']($scope.v2Token); + searchMethod.then( + function (users) { + if (handle) { + users = users.filter(function (user) { + // filter handle with case insensitive + return angular.lowercase(user.name) === angular.lowercase(handle); + }); + } + $scope.users = users; + $scope.formSearch.setLoading(false); + $scope.formSearch.usersFound = true; + $scope.$broadcast('adminTools.TableDataUpdated'); + }, + function (error) { + $alert.error(error.error, $scope); + $scope.formSearch.setLoading(false); + } + ); + }; + + // exchange v3 token with v2 token + $scope.formSearch.setLoading(true); + $adminToolService.getV2Token().then(function (token) { + $scope.v2Token = token; + $adminToolService.findReviewBoardProjectCategories($scope.v2Token).then(function (projectCategories) { + projectCategories = projectCategories.filter(function (projectCategory) { + return $const.ExcludeCategories.indexOf(angular.lowercase(projectCategory.name)) === -1; + }); + projectCategories = projectCategories.sort(function (p1, p2) { + return p1.name.localeCompare(p2.name); + }); + $scope.reviewBoardProjectCategories = projectCategories; + $scope.formSearch.setLoading(false); + }, + function (error) { + $alert.error(error.error, $scope); + $scope.formSearch.setLoading(false); + }) + }, + function (error) { + $alert.error(error.error, $scope); + $scope.formSearch.setLoading(false); + }); + + // open remove user role dialog + $scope.openRemoveUserRoleDialog = function (index) { + $modal.open({ + size: 'sm', + templateUrl: 'app/admintool/remove-user-role-dialog.html', + controller: 'admintool.RemoveUserRoleDialogController', + resolve: { + v2Token: function () { + return $scope.v2Token; + }, + user: function () { + return $scope.users[index]; + }, + role: function () { + return $scope.formSearch.role; + } + } + }).result.then(function (result) { + $alert.info(result.message); + $scope.search(true); + }); + }; + + // open new user role dialog + $scope.openNewUserRoleDialog = function () { + $modal.open({ + size: 'sm', + templateUrl: 'app/admintool/user-role-dialog.html', + controller: 'admintool.NewUserRoleDialogController', + resolve: { + v2Token: function () { + return $scope.v2Token; + }, + role: function () { + return $scope.formSearch.role; + }, + categoryId: function () { + return $scope.formSearch.categoryId; + }, + reviewBoardProjectCategories: function () { + return $scope.reviewBoardProjectCategories; + } + } + }).result.then(function (result) { + $scope.formSearch.role = result.role; + if ($scope.formSearch.isReviewer()) { + $scope.formSearch.categoryId = result.categoryId; + } + $alert.info(result.message); + $scope.search(true); + }); + }; + + // open edit user role dialog + $scope.openEditUserRoleDialog = function (index) { + $modal.open({ + size: 'sm', + templateUrl: 'app/admintool/user-role-dialog.html', + controller: 'admintool.EditUserRoleDialogController', + resolve: { + v2Token: function () { + return $scope.v2Token; + }, + user: function () { + return $scope.users[index]; + }, + role: function () { + return $scope.formSearch.role; + }, + categoryId: function () { + return $scope.formSearch.categoryId; + }, + reviewBoardProjectCategories: function () { + return $scope.reviewBoardProjectCategories; + } + } + }).result.then(function (result) { + if ($scope.formSearch.isReviewer()) { + $scope.formSearch.categoryId = result.categoryId; + } + $alert.info($scope.formSearch.role + ' ' + result.name + ' has been successfully updated!'); + $scope.search(true); + }); + }; + + } +]); +module.controller('admintool.RemoveUserRoleDialogController', [ + '$scope', '$uibModalInstance', 'AdminToolService', 'Alert', 'v2Token', 'user', 'role', 'admintool.Constants', + function ($scope, $modalInstance, $adminToolService, $alert, v2Token, user, role, $const) { + $scope.form = { + user: user, + role: role, + isLoading: false, + setLoading: function (loading) { + this.isLoading = loading; + }, + }; + + $scope.cancel = function () { + $modalInstance.dismiss(); + }; + + $scope.remove = function () { + $alert.clear(); + $scope.form.setLoading(true); + var data = { + username: user.name + }; + if (role === $const.Roles.Reviewer) { + data.categoryId = user.projectCategoryId; + } + $adminToolService['delete' + role](v2Token, data).then( + function (result) { + $scope.form.setLoading(false); + $modalInstance.close(result); + }, + function (error) { + $alert.error(error.error, $scope); + $scope.form.setLoading(false); + } + ); + }; + } +]); + +module.controller('admintool.NewUserRoleDialogController', [ + '$scope', '$uibModalInstance', 'AdminToolService', 'Alert', 'v2Token', 'role', 'categoryId', 'reviewBoardProjectCategories', + 'admintool.Constants', + function ($scope, $modalInstance, $adminToolService, $alert, v2Token, role, categoryId, reviewBoardProjectCategories, $const) { + $scope.reviewBoardProjectCategories = reviewBoardProjectCategories; + $scope.constRoles = $const.Roles; + $scope.form = { + role: role, + user: { + username: '' + }, + isCopilot: function () { + return this.role === $const.Roles.Copilot; + }, + isReviewer: function () { + return this.role === $const.Roles.Reviewer; + }, + isLoading: false, + setLoading: function (loading) { + this.isLoading = loading; + } + }; + if ($scope.form.isCopilot()) { + $scope.form.user.isSoftwareCopilot = false; + $scope.form.user.isStudioCopilot = false; + } else if ($scope.form.isReviewer()) { + $scope.form.user.categoryId = categoryId; + $scope.form.editImmune = false; + } + + $scope.cancel = function () { + $modalInstance.dismiss(); + }; + + $scope.ok = function (form) { + if (!form.$valid) { + return; + } + $alert.clear(); + $scope.form.setLoading(true); + var newUser = angular.copy($scope.form.user); + if ($scope.form.isCopilot()) { + newUser.isSoftwareCopilot = !!newUser.isSoftwareCopilot; + newUser.isStudioCopilot = !!newUser.isStudioCopilot; + } else if ($scope.form.isReviewer()) { + if ($scope.form.editImmune) { + newUser.immune = !!newUser.immune; + } else { + delete newUser.immune; + } + } + + $adminToolService['create' + $scope.form.role](v2Token, newUser).then( + function (result) { + $scope.form.setLoading(false); + result.role = $scope.form.role; + if ($scope.form.isReviewer()) { + result.categoryId = newUser.categoryId; + } + $modalInstance.close(result); + }, + function (error) { + $alert.error(error.error, $scope); + $scope.form.setLoading(false); + } + ); + }; + } +]); + +module.controller('admintool.EditUserRoleDialogController', [ + '$scope', '$uibModalInstance', 'AdminToolService', 'Alert', 'v2Token', 'user', 'role', 'categoryId', 'reviewBoardProjectCategories', 'admintool.Constants', + function ($scope, $modalInstance, $adminToolService, $alert, v2Token, user, role, categoryId, reviewBoardProjectCategories, $const) { + $scope.reviewBoardProjectCategories = reviewBoardProjectCategories; + $scope.constRoles = $const.Roles; + $scope.form = { + role: role, + user: { + id: user.id, + username: user.name + }, + isCopilot: function () { + return this.role === $const.Roles.Copilot; + }, + isReviewer: function () { + return this.role === $const.Roles.Reviewer; + }, + isLoading: false, + setLoading: function (loading) { + this.isLoading = loading; + } + }; + + if ($scope.form.isCopilot()) { + $scope.form.user.isSoftwareCopilot = user.softwareCopilot; + $scope.form.user.isStudioCopilot = user.studioCopilot; + } else if ($scope.form.isReviewer()) { + $scope.form.user.categoryId = categoryId; + $scope.form.user.immune = user.immune; + $scope.form.editImmune = true; + } + + $scope.cancel = function () { + $modalInstance.dismiss(); + }; + + $scope.ok = function (form) { + if (!form.$valid) { + return; + } + $alert.clear(); + $scope.form.setLoading(true); + var updatedUser = angular.copy($scope.form.user); + var updateMethod = null; + if ($scope.form.isCopilot()) { + if (updatedUser.isSoftwareCopilot === user.softwareCopilot + && updatedUser.isStudioCopilot === user.studioCopilot) { + // no updates + $modalInstance.close(user); + return; + } + updateMethod = $adminToolService.updateCopilot(v2Token, updatedUser); + } + if ($scope.form.isReviewer()) { + if ($scope.form.editImmune) { + updatedUser.immune = !!updatedUser.immune; + } else { + // reset to old value + updatedUser.immune = user.immune; + } + if (updatedUser.categoryId === categoryId + && updatedUser.immune === user.immune) { + user.categoryId = categoryId; + // no updates + $modalInstance.close(user); + return; + } + updateMethod = $adminToolService.updateReviewer(v2Token, { + username: user.name, + categoryId: categoryId + }, updatedUser); + } + updateMethod.then( + function () { + $scope.form.setLoading(false); + if ($scope.form.isCopilot()) { + user.softwareCopilot = updatedUser.isSoftwareCopilot; + user.studioCopilot = updatedUser.isStudioCopilot; + } else if ($scope.form.isReviewer()) { + user.immune = updatedUser.immune; + user.categoryId = updatedUser.categoryId; + } + $modalInstance.close(user); + }, + function (error) { + $scope.form.setLoading(false); + $alert.error(error.error, $scope); + } + ); + }; + } +]); + diff --git a/src/app/admintool/admintool.html b/src/app/admintool/admintool.html new file mode 100644 index 0000000..f2107ac --- /dev/null +++ b/src/app/admintool/admintool.html @@ -0,0 +1,142 @@ +
+
+

Admins / Copilots / Reviewers

+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+

+ Tips:
+

+

- Please choose role to find users first.

+

- Please choose challenge category.

+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+

No users found.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
User IDHandleIs In Admin Group?Has Admin Role?Is Manager (Online Review)?Is Software Copilot?Is Studio Copilot?Is Immune?Action
{{user.id}}{{user.name}}{{user.adminGroup}}{{user.adminRole}}{{user.managerResource}}{{user.softwareCopilot}}{{user.studioCopilot}}{{user.immune}} + + Edit + + + Remove + +
+
    +
    +
    +
    +
    +
    +
    +
    diff --git a/src/app/admintool/admintool.service.js b/src/app/admintool/admintool.service.js new file mode 100644 index 0000000..2cc400b --- /dev/null +++ b/src/app/admintool/admintool.service.js @@ -0,0 +1,307 @@ +'use strict'; + +angular.module('supportAdminApp') + .factory('AdminToolService', ['$log', '$q', '$http', 'ADMIN_TOOL_URL', 'API_URL', 'API_VERSION_PATH', + function ($log, $q, $http, ADMIN_TOOL_URL, API_URL, API_VERSION_PATH) { + var service = { + getV2Token: getV2Token, + findAdmins: findAdmins, + createAdmin: createAdmin, + deleteAdmin: deleteAdmin, + findCopilots: findCopilots, + createCopilot: createCopilot, + deleteCopilot: deleteCopilot, + updateCopilot: updateCopilot, + findReviewers: findReviewers, + createReviewer: createReviewer, + deleteReviewer: deleteReviewer, + updateReviewer: updateReviewer, + findReviewBoardProjectCategories: findReviewBoardProjectCategories + }; + + /** + * Get v2 token with v3 token + */ + function getV2Token() { + return $http({ + method: 'GET', + url: API_URL + '/' + API_VERSION_PATH + '/authorizations/1' + }).then( + function (response) { + $log.debug(response); + if (response.data && response.data.result && response.data.result.content + && response.data.result.content.externalToken) { + return response.data.result.content.externalToken; + } else { + return $q.reject({ + error: 'Cannot find v2 token in response.' + }); + } + }, + function (error) { + $log.error(error); + var err; + if (error && error.data && error.data.result) { + err = { + status: error.status, + error: error.data.result.content + }; + } + if (!err) { + err = { + status: error.status, + error: error.statusText + }; + } + return $q.reject(err); + } + ); + } + + /** + * helper function to process http request + */ + var _processRequest = function (request, key) { + return request.then( + function (response) { + var data = response.data && key && response.data[key] ? response.data[key] : response.data; + // only two requests to get review board categories actually + if (Array.isArray(response) && response.length === 2 && Array.isArray(response[0].data) && Array.isArray(response[1].data)) { + data = response[0].data.concat(response[1].data); + } + if (data && data.success === false) { + return $q.reject({ + error: data.message || 'Unknown server error' + }); + } else if (data && data.error) { + return $q.reject({ + error: data.error + }); + } else { + return data; + } + }, + function (error) { + $log.error(error); + var err; + if (error && error.data && error.data.error) { + err = { + status: error.status, + error: error.data.error.details || error.data.error.description || error.data.error.name + }; + } + if (!err) { + err = { + status: error.status, + error: error.statusText || 'Please make sure tc api server is running' + }; + } + return $q.reject(err); + } + ); + }; + + + /** + * Find admins + */ + function findAdmins(token) { + var request = $http({ + method: 'GET', + url: ADMIN_TOOL_URL + '/admin/admins', + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + } + }); + return _processRequest(request, 'allAdmins'); + } + + /** + * creates admin user + */ + function createAdmin(token, data) { + var request = $http({ + method: 'POST', + url: ADMIN_TOOL_URL + '/admin/admins', + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + }, + data: angular.toJson(data) + }); + return _processRequest(request); + } + + /** + * Delete an existing admin user + */ + function deleteAdmin(token, data) { + var request = $http({ + method: "DELETE", + url: ADMIN_TOOL_URL + "/admin/admins", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + }, + data: angular.toJson(data) + }); + return _processRequest(request); + } + + + /** + * Find copilots + */ + function findCopilots(token) { + var request = $http({ + method: 'GET', + url: ADMIN_TOOL_URL + '/admin/copilots', + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + } + }); + return _processRequest(request, 'allCopilots'); + } + + /** + * creates copilot + */ + function createCopilot(token, data) { + var request = $http({ + method: 'POST', + url: ADMIN_TOOL_URL + '/admin/copilots', + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + }, + data: angular.toJson(data) + }); + return _processRequest(request); + } + + /** + * Delete copilot + */ + function deleteCopilot(token, data) { + var request = $http({ + method: "DELETE", + url: ADMIN_TOOL_URL + "/admin/copilots", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + }, + data: angular.toJson(data) + }); + return _processRequest(request); + } + + /** + * Update copilot + */ + function updateCopilot(token, data) { + return deleteCopilot(token, data).then(function () { + return createCopilot(token, data); + }); + } + + + /** + * Find Reviewers + */ + function findReviewers(token, categoryId) { + var request = $http({ + method: 'GET', + url: ADMIN_TOOL_URL + '/admin/reviewers', + params: { + categoryId: categoryId + }, + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + } + }); + return _processRequest(request, 'reviewers'); + } + + + /** + * creates Reviewer + */ + function createReviewer(token, data) { + var request = $http({ + method: 'POST', + url: ADMIN_TOOL_URL + '/admin/reviewers', + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + }, + data: angular.toJson(data) + }); + return _processRequest(request); + } + + /** + * Delete Reviewer + */ + function deleteReviewer(token, data) { + var request = $http({ + method: "DELETE", + url: ADMIN_TOOL_URL + "/admin/reviewers", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + }, + data: angular.toJson(data) + }); + return _processRequest(request); + } + + + /** + * Update Reviewer + */ + function updateReviewer(token, oldReviewer, newReviewer) { + var requests = [deleteReviewer(token, oldReviewer)]; + if (oldReviewer.categoryId !== newReviewer.categoryId) { + requests.push(deleteReviewer(token, { + username: newReviewer.username, + categoryId: newReviewer.categoryId + }).catch(function (error) { + // new reviewer role may not exist + if (error.status !== 404) { + return $q.reject(error); + } + })); + } + return $q.all(requests).then(function () { + delete newReviewer.id; + return createReviewer(token, newReviewer) + }); + } + + /** + * Find review board project categories + */ + function findReviewBoardProjectCategories(token) { + var request = $q.all([$http({ + method: 'GET', + url: ADMIN_TOOL_URL + '/develop/challengetypes', + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + } + }), $http({ + method: 'GET', + url: ADMIN_TOOL_URL + '/design/challengetypes', + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer " + token + } + })]); + return _processRequest(request); + } + + return service; + }]); diff --git a/src/app/admintool/constants.js b/src/app/admintool/constants.js new file mode 100644 index 0000000..392c79d --- /dev/null +++ b/src/app/admintool/constants.js @@ -0,0 +1,13 @@ +'use strict'; + +var module = angular.module('supportAdminApp'); + +module.constant('admintool.Constants', { + // excluding challenge categories for reviewer role must use lower case + ExcludeCategories: [angular.lowercase('Copilot Posting'), angular.lowercase('Other'), angular.lowercase('Studio Other')], + Roles: { + Admin: 'Admin', + Copilot: 'Copilot', + Reviewer: 'Reviewer', + } +}); diff --git a/src/app/admintool/remove-user-role-dialog.html b/src/app/admintool/remove-user-role-dialog.html new file mode 100644 index 0000000..0d05369 --- /dev/null +++ b/src/app/admintool/remove-user-role-dialog.html @@ -0,0 +1,26 @@ +
    + + + +
    + +
    +
    diff --git a/src/app/admintool/user-role-dialog.html b/src/app/admintool/user-role-dialog.html new file mode 100644 index 0000000..d91d3df --- /dev/null +++ b/src/app/admintool/user-role-dialog.html @@ -0,0 +1,72 @@ +
    + + + +
    + +
    +
    diff --git a/src/app/app.js b/src/app/app.js index 1eeebef..b591c20 100755 --- a/src/app/app.js +++ b/src/app/app.js @@ -61,6 +61,11 @@ angular.module('supportAdminApp', [ templateUrl: 'app/users/users.html', data: { pageTitle: 'User Management' } }) + .state('index.admintool', { + url: '/admintool', + templateUrl: 'app/admintool/admintool.html', + data: { pageTitle: 'Admins / Copilots / Reviewers Management' } + }) .state('index.sso', { url: '/sso', templateUrl: 'app/sso/sso.html', diff --git a/src/app/login/login.html b/src/app/login/login.html index 704a04f..0af6d1c 100755 --- a/src/app/login/login.html +++ b/src/app/login/login.html @@ -2,7 +2,7 @@
    - +

    Admin app login

    @@ -30,4 +30,4 @@

    Admin app login

    - \ No newline at end of file + diff --git a/src/assets/images/appirio-logo.png b/src/assets/images/appirio-logo.png deleted file mode 100644 index b55ac75..0000000 Binary files a/src/assets/images/appirio-logo.png and /dev/null differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000..09a6cb8 Binary files /dev/null and b/src/assets/images/logo.png differ diff --git a/src/components/common/navigation.html b/src/components/common/navigation.html index 3d9c94c..55c8b1f 100755 --- a/src/components/common/navigation.html +++ b/src/components/common/navigation.html @@ -2,7 +2,7 @@