-/*-\r
- * ================================================================================\r
- * ECOMP Portal\r
- * ================================================================================\r
- * Copyright (C) 2017 AT&T Intellectual Property\r
- * ================================================================================\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- * \r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- * \r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- * ================================================================================\r
- */\r
-/**\r
- * bulk user upload controller\r
- */\r
-'use strict';\r
-(function () {\r
- class BulkUserModalCtrl {\r
- constructor($scope, $log, $filter, $q, usersService, applicationsService, confirmBoxService, functionalMenuService, ngDialog) {\r
- \r
- // Set to true for copious console output\r
- var debug = false;\r
- // Roles fetched from app service\r
- var appRolesResult = [];\r
- // Users fetched from user service\r
- var userCheckResult = [];\r
- // Requests for user-role assignment built by validator\r
- var appUserRolesRequest = [];\r
- \r
- let init = () => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::init');\r
- // Angular insists on this.\r
- $scope.fileModel = {};\r
- // Model for drop-down\r
- $scope.adminApps = [];\r
- // Enable modal controls\r
- this.step1 = true;\r
- this.fileSelected = false;\r
-\r
- // Flag that indicates background work is proceeding\r
- $scope.isProcessing = true;\r
-\r
- // Load user's admin applications\r
- applicationsService.getAdminApps().promise().then(apps => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::init: getAdminApps returned' + JSON.stringify(apps));\r
- if (!apps || typeof(apps) != 'object') {\r
- $log.error('BulkUserModalCtrl::init: getAdminApps returned unexpected data');\r
- }\r
- else {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::init: admin apps length is ', apps.length);\r
- \r
- // Sort app names and populate the drop-down model\r
- let sortedApps = apps.sort(getSortOrder('name', true));\r
- for (let i = 0; i < sortedApps.length; ++i) {\r
- $scope.adminApps.push({\r
- index: i,\r
- id: sortedApps[i].id,\r
- value: sortedApps[i].name,\r
- title: sortedApps[i].name\r
- });\r
- }\r
- // Pick the first one in the list\r
- $scope.selectedApplication = $scope.adminApps[0];\r
- }\r
- $scope.isProcessing = false;\r
- }).catch(err => {\r
- $log.error('BulkUserModalCtrl::init: getAdminApps threw', err);\r
- $scope.isProcessing = false;\r
- });\r
- \r
- }; // init\r
- \r
- // Answers a function that compares properties with the specified name.\r
- let getSortOrder = (prop, foldCase) => {\r
- return function(a, b) {\r
- let aProp = foldCase ? a[prop].toLowerCase() : a[prop];\r
- let bProp = foldCase ? b[prop].toLowerCase() : b[prop];\r
- if (aProp > bProp)\r
- return 1;\r
- else if (aProp < bProp) \r
- return -1;\r
- else\r
- return 0;\r
- }\r
- }\r
- \r
- //This is a fix for dropdown selection, due to b2b dropdown only update value field\r
- $scope.$watch('selectedApplication.value', (newVal, oldVal) => {\r
- for(var i=0;i<$scope.adminApps.length;i++){ \r
- if($scope.adminApps[i].value==newVal){\r
- $scope.selectedApplication=angular.copy($scope.adminApps[i]);;\r
- }\r
- }\r
- });\r
-\r
- // Invoked when user picks an app on the drop-down.\r
- $scope.appSelected = () => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::appSelected: selectedApplication.id is ' + $scope.selectedApplication.id);\r
- this.appSelected = true;\r
- }\r
- \r
- // Caches the file name supplied by the event handler.\r
- $scope.fileChangeHandler = (event, files) => {\r
- this.fileSelected = true;\r
- this.fileToRead = files[0];\r
- if (debug)\r
- $log.debug("BulkUserModalCtrl::fileChangeHandler: file is ", this.fileToRead);\r
- }; // file change handler\r
- \r
- /**\r
- * Reads the contents of the file, calls portal endpoints\r
- * to validate roles, userIds and existing role assignments;\r
- * ultimately builds array of requests to be sent.\r
- * Creates scope variable with input file contents for\r
- * communication with functions.\r
- * \r
- * This function performs a synchronous step-by-step process\r
- * using asynchronous promises. The code could all be inline\r
- * here but the nesting becomes unwieldy.\r
- */\r
- $scope.readValidateFile = () => {\r
- $scope.isProcessing = true;\r
- $scope.progressMsg = 'Reading upload file..';\r
- var reader = new FileReader();\r
- reader.onload = function(event) {\r
- $scope.uploadFile = $filter('csvToObj')(reader.result);\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::readValidateFile onload: data length is ' + $scope.uploadFile.length);\r
- // sort input by orgUserId\r
- $scope.uploadFile.sort(getSortOrder('orgUserId', true));\r
- \r
- let appid = $scope.selectedApplication.id;\r
- $scope.progressMsg = 'Fetching application roles..';\r
- functionalMenuService.getManagedRolesMenu(appid).then(function (rolesObj) {\r
- if (debug)\r
- $log.debug("BulkUserModalCtrl::readValidateFile: managedRolesMenu returned " + JSON.stringify(rolesObj));\r
- appRolesResult = rolesObj;\r
- $scope.progressMsg = 'Validating application roles..';\r
- $scope.verifyRoles();\r
- \r
- let userPromises = $scope.buildUserChecks();\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::readValidateFile: userPromises length is ' + userPromises.length);\r
- $scope.progressMsg = 'Validating Org Users..';\r
- $q.all(userPromises).then(function() {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::readValidateFile: userCheckResult length is ' + userCheckResult.length);\r
- $scope.evalUserCheckResults();\r
- \r
- let appPromises = $scope.buildAppRoleChecks();\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::readValidateFile: appPromises length is ' + appPromises.length);\r
- $scope.progressMsg = 'Querying application for user roles..';\r
- $q.all(appPromises).then( function() {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::readValidateFile: appUserRolesRequest length is ' + appUserRolesRequest.length);\r
- $scope.evalAppRoleCheckResults();\r
- \r
- // Re sort by line for the confirmation dialog\r
- $scope.uploadFile.sort(getSortOrder('line', false));\r
- // We're done, confirm box may show the table\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::readValidateFile inner-then ends');\r
- $scope.progressMsg = 'Done.';\r
- $scope.isProcessing = false;\r
- },\r
- function(error) {\r
- $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user-app roles');\r
- $scope.isProcessing = false;\r
- }\r
- ); // then of app promises\r
- },\r
- function(error) {\r
- $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user info');\r
- $scope.isProcessing = false;\r
- }\r
- ); // then of user promises\r
- },\r
- function(error) {\r
- $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app role info');\r
- $scope.isProcessing = false;\r
- }\r
- ); // then of role promise\r
- \r
- } // onload\r
- \r
- // Invoke the reader on the selected file\r
- reader.readAsText(this.fileToRead);\r
- }; \r
- \r
- /**\r
- * Evaluates the result set returned by the app role service.\r
- * Sets an uploadFile array element status if a role is not defined.\r
- * Reads and writes scope variable uploadFile.\r
- * Reads closure variable appRolesResult.\r
- */\r
- $scope.verifyRoles = () => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::verifyRoles: appRoles is ' + JSON.stringify(appRolesResult));\r
- // check roles in upload file against defined app roles\r
- $scope.uploadFile.forEach( function (uploadRow) {\r
- // skip rows that already have a defined status: headers etc.\r
- if (uploadRow.status) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::verifyRoles: skip row ' + uploadRow.line);\r
- return;\r
- }\r
- uploadRow.role = uploadRow.role.trim();\r
- var foundRole=false;\r
- for (var i=0; i < appRolesResult.length; i++) {\r
- if (uploadRow.role.toUpperCase() === appRolesResult[i].rolename.trim().toUpperCase()) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::verifyRoles: match on role ' + uploadRow.role);\r
- foundRole=true;\r
- break;\r
- }\r
- };\r
- if (!foundRole) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::verifyRoles: NO match on role ' + uploadRow.role);\r
- uploadRow.status = 'Invalid role';\r
- };\r
- }); // foreach\r
- }; // verifyRoles\r
- \r
- /**\r
- * Builds and returns an array of promises to invoke the \r
- * searchUsers service for each unique Org User UID in the input.\r
- * Reads and writes scope variable uploadFile, which must be sorted by Org User UID.\r
- * The promise function writes to closure variable userCheckResult\r
- */\r
- $scope.buildUserChecks = () => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildUserChecks: uploadFile length is ' + $scope.uploadFile.length);\r
- userCheckResult = [];\r
- let promises = [];\r
- let prevRow = null;\r
- $scope.uploadFile.forEach(function (uploadRow) {\r
- if (uploadRow.status) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildUserChecks: skip row ' + uploadRow.line);\r
- return;\r
- };\r
- // detect repeated UIDs\r
- if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildUserChecks: create request for orgUserId ' + uploadRow.orgUserId);\r
- let userPromise = usersService.searchUsers(uploadRow.orgUserId).promise().then( (usersList) => {\r
- if (typeof usersList[0] !== "undefined") {\r
- userCheckResult.push({ \r
- orgUserId: usersList[0].orgUserId,\r
- firstName: usersList[0].firstName,\r
- lastName: usersList[0].lastName,\r
- jobTitle: usersList[0].jobTitle\r
- });\r
- }\r
- else {\r
- // User not found.\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildUserChecks: searchUsers returned null');\r
- }\r
- }, function(error){\r
- $log.error('BulkUserModalCtrl::buildUserChecks: searchUsers failed ' + JSON.stringify(error));\r
- }); \r
- promises.push(userPromise);\r
- }\r
- else {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildUserChecks: skip repeated orgUserId ' + uploadRow.orgUserId); \r
- }\r
- prevRow = uploadRow;\r
- }); // foreach\r
- return promises;\r
- }; // buildUserChecks\r
- \r
- /**\r
- * Evaluates the result set returned by the user service to set\r
- * the uploadFile array element status if the user was not found.\r
- * Reads and writes scope variable uploadFile.\r
- * Reads closure variable userCheckResult.\r
- */\r
- $scope.evalUserCheckResults = () => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalUserCheckResult: uploadFile length is ' + $scope.uploadFile.length);\r
- $scope.uploadFile.forEach(function (uploadRow) {\r
- if (uploadRow.status) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalUserCheckResults: skip row ' + uploadRow.line);\r
- return;\r
- };\r
- let foundorgUserId = false;\r
- userCheckResult.forEach(function(userItem) {\r
- if (uploadRow.orgUserId.toLowerCase() === userItem.orgUserId.toLowerCase()) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalUserCheckResults: found orgUserId ' + uploadRow.orgUserId);\r
- foundorgUserId=true;\r
- };\r
- });\r
- if (!foundorgUserId) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalUserCheckResults: NO match on orgUserId ' + uploadRow.orgUserId);\r
- uploadRow.status = 'Invalid orgUserId';\r
- }\r
- }); // foreach\r
- }; // evalUserCheckResults\r
-\r
- /**\r
- * Builds and returns an array of promises to invoke the getUserAppRoles\r
- * service for each unique Org User in the input file.\r
- * Each promise creates an update to be sent to the remote application\r
- * with all role names.\r
- * Reads scope variable uploadFile, which must be sorted by Org User.\r
- * The promise function writes to closure variable appUserRolesRequest\r
- */\r
- $scope.buildAppRoleChecks = () => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildAppRoleChecks: uploadFile length is ' + $scope.uploadFile.length); \r
- appUserRolesRequest = [];\r
- let appId = $scope.selectedApplication.id;\r
- let promises = [];\r
- let prevRow = null;\r
- $scope.uploadFile.forEach( function (uploadRow) {\r
- if (uploadRow.status) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildAppRoleChecks: skip row ' + uploadRow.line);\r
- return;\r
- }\r
- // Because the input is sorted, generate only one request for each Org User\r
- if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildAppRoleChecks: create request for orgUserId ' + uploadRow.orgUserId);\r
- let appPromise = usersService.getUserAppRoles(appId, uploadRow.orgUserId).promise().then( (userAppRolesResult) => {\r
- // Reply for unknown user has all defined roles with isApplied=false on each. \r
- if (typeof userAppRolesResult[0] !== "undefined") {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildAppRoleChecks: adding result ' \r
- + JSON.stringify(userAppRolesResult));\r
- appUserRolesRequest.push({\r
- orgUserId: uploadRow.orgUserId,\r
- userAppRoles: userAppRolesResult \r
- });\r
- } else {\r
- $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles returned ' + JSON.stringify(userAppRolesResult));\r
- };\r
- }, function(error){\r
- $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles failed ', error);\r
- });\r
- promises.push(appPromise);\r
- } else {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::buildAppRoleChecks: duplicate orgUserId, skip: '+ uploadRow.orgUserId);\r
- }\r
- prevRow = uploadRow;\r
- }); // foreach\r
- return promises;\r
- }; // buildAppRoleChecks\r
- \r
- /**\r
- * Evaluates the result set returned by the app service and adjusts \r
- * the list of updates to be sent to the remote application by setting\r
- * isApplied=true for each role name found in the upload file.\r
- * Reads and writes scope variable uploadFile.\r
- * Reads closure variable appUserRolesRequest.\r
- */\r
- $scope.evalAppRoleCheckResults = () => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: uploadFile length is ' + $scope.uploadFile.length);\r
- $scope.uploadFile.forEach(function (uploadRow) {\r
- if (uploadRow.status) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: skip row ' + uploadRow.line);\r
- return;\r
- }\r
- // Search for the match in the app-user-roles array\r
- appUserRolesRequest.forEach( function (appUserRoleObj) {\r
- if (uploadRow.orgUserId.toLowerCase() === appUserRoleObj.orgUserId.toLowerCase()) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: match on orgUserId ' + uploadRow.orgUserId);\r
- let roles = appUserRoleObj.userAppRoles;\r
- roles.forEach(function (appRoleItem) {\r
- //if (debug)\r
- // $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: checking uploadRow.role='\r
- // + uploadRow.role + ', appRoleItem.roleName= ' + appRoleItem.roleName);\r
- if (uploadRow.role === appRoleItem.roleName) {\r
- if (appRoleItem.isApplied) {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: existing role ' \r
- + appRoleItem.roleName);\r
- uploadRow.status = 'Role exists';\r
- }\r
- else {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: new role ' \r
- + appRoleItem.roleName);\r
- // After much back-and-forth I decided a clear indicator\r
- // is better than blank in the table status column.\r
- uploadRow.status = 'OK';\r
- appRoleItem.isApplied = true;\r
- }\r
- // This count is not especially interesting.\r
- // numberUserRolesSucceeded++;\r
- }\r
- }); // for each role\r
- }\r
- }); // for each result \r
- }); // for each row\r
- }; // evalAppRoleCheckResults\r
- \r
- /**\r
- * Sends requests to Portal requesting user role assignment.\r
- * That endpoint handles creation of the user at the remote app if necessary.\r
- * Reads closure variable appUserRolesRequest.\r
- * Invoked by the Next button on the confirmation dialog.\r
- */\r
- $scope.updateDB = () => {\r
- $scope.isProcessing = true;\r
- $scope.progressMsg = 'Sending requests to application..';\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::updateDB: request length is ' + appUserRolesRequest.length);\r
- var numberUsersSucceeded = 0;\r
- let promises = [];\r
- appUserRolesRequest.forEach(function(appUserRoleObj) {\r
- if (debug) \r
- $log.debug('BulkUserModalCtrl::updateDB: appUserRoleObj is ' + JSON.stringify(appUserRoleObj));\r
- let updateRequest = {\r
- orgUserId: appUserRoleObj.orgUserId, \r
- appId: $scope.selectedApplication.id, \r
- appRoles: appUserRoleObj.userAppRoles\r
- };\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::updateDB: updateRequest is ' + JSON.stringify(updateRequest));\r
- let updatePromise = usersService.updateUserAppRoles(updateRequest).promise().then(res => {\r
- if (debug)\r
- $log.debug('BulkUserModalCtrl::updateDB: updated successfully: ' + JSON.stringify(res));\r
- numberUsersSucceeded++;\r
- }).catch(err => {\r
- // What to do if one of many fails??\r
- $log.error('BulkUserModalCtrl::updateDB failed: ', err);\r
- confirmBoxService.showInformation(\r
- 'Failed to update the user application roles. ' +\r
- 'Error: ' + err.status).then(isConfirmed => { });\r
- }).finally( () => {\r
- // $log.debug('BulkUserModalCtrl::updateDB: finally()');\r
- });\r
- promises.push(updatePromise);\r
- }); // for each\r
- \r
- // Run all the promises\r
- $q.all(promises).then(function(){\r
- $scope.isProcessing = false;\r
- confirmBoxService.showInformation('Processed ' + numberUsersSucceeded + ' users.').then(isConfirmed => {\r
- // Close the upload-confirm dialog\r
- ngDialog.close();\r
- });\r
- });\r
- }; // updateDb\r
- \r
- // Sets the variable that hides/reveals the user controls\r
- $scope.step2 = () => {\r
- this.fileSelected = false;\r
- $scope.selectedFile = null;\r
- $scope.fileModel = null;\r
- this.step1 = false; \r
- }\r
- \r
- // Navigate between dialog screens using step number: 1,2,...\r
- $scope.navigateBack = () => {\r
- this.step1 = true;\r
- this.fileSelected = false;\r
- };\r
- \r
- // Opens a dialog to show the data to be uploaded.\r
- // Invoked by the upload button on the bulk user dialog.\r
- $scope.confirmUpload = () => {\r
- // Start the process\r
- $scope.readValidateFile();\r
- // Dialog shows progress\r
- ngDialog.open({\r
- templateUrl: 'app/views/users/new-user-dialogs/bulk-user.confirm.html',\r
- scope: $scope\r
- });\r
- };\r
-\r
- // Invoked by the Cancel button on the confirmation dialog.\r
- $scope.cancelUpload = () => {\r
- ngDialog.close();\r
- };\r
- \r
- init();\r
- } // constructor\r
- } // class\r
- BulkUserModalCtrl.$inject = ['$scope', '$log', '$filter', '$q', 'usersService', 'applicationsService', 'confirmBoxService', 'functionalMenuService', 'ngDialog']; \r
- angular.module('ecompApp').controller('BulkUserModalCtrl', BulkUserModalCtrl);\r
-\r
- angular.module('ecompApp').directive('fileChange', ['$parse', function($parse){\r
- return {\r
- require: 'ngModel',\r
- restrict: 'A',\r
- link : function($scope, element, attrs, ngModel) {\r
- var attrHandler = $parse(attrs['fileChange']);\r
- var handler=function(e) {\r
- $scope.$apply(function() {\r
- attrHandler($scope, { $event:e, files:e.target.files } );\r
- $scope.selectedFile = e.target.files[0].name;\r
- });\r
- };\r
- element[0].addEventListener('change',handler,false);\r
- }\r
- }\r
- }]);\r
-\r
- angular.module('ecompApp').filter('csvToObj',function() {\r
- return function(input) {\r
- var result = [];\r
- var len, i, line, o;\r
- var lines = input.split('\n');\r
- // Need 1-based index below\r
- for (len = lines.length, i = 1; i <= len; ++i) {\r
- // Use 0-based index for array\r
- line = lines[i - 1].trim();\r
- if (line.length == 0) {\r
- // console.log("Skipping blank line");\r
- result.push({\r
- line: i,\r
- orgUserId: '',\r
- role: '',\r
- status: 'Blank line'\r
- });\r
- continue;\r
- }\r
- o = line.split(',');\r
- if (o.length !== 2) {\r
- // other lengths not valid for upload\r
- result.push({\r
- line: i,\r
- orgUserId: line, \r
- role: '',\r
- status: 'Failed to find 2 comma-separated values'\r
- });\r
- }\r
- else {\r
- // console.log("Valid line: ", val);\r
- let entry = {\r
- line: i,\r
- orgUserId: o[0],\r
- role: o[1]\r
- // leave status undefined, this could be valid.\r
- };\r
- if (o[0].toLowerCase() === 'orgUserId') {\r
- // not valid for upload, so set status\r
- entry.status = 'Header';\r
- }\r
- else if (o[0].trim() == '' || o[1].trim() == '') {\r
- // defend against line with only a single comma etc.\r
- entry.status = 'Failed to find 2 non-empty values'; \r
- }\r
- result.push(entry);\r
- } // len 2\r
- } // for\r
- return result;\r
- };\r
- });\r
- \r
- \r
- \r
-})();\r
+/*-
+ * ================================================================================
+ * ECOMP Portal
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ================================================================================
+ */
+/**
+ * bulk user upload controller
+ */
+'use strict';
+(function () {
+ class BulkUserModalCtrl {
+ constructor($scope, $log, $filter, $q, usersService, applicationsService, confirmBoxService, functionalMenuService, ngDialog) {
+
+ // Set to true for copious console output
+ var debug = false;
+ // Roles fetched from app service
+ var appRolesResult = [];
+ // Users fetched from user service
+ var userCheckResult = [];
+ // Requests for user-role assignment built by validator
+ var appUserRolesRequest = [];
+
+ let init = () => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::init');
+ // Angular insists on this.
+ $scope.fileModel = {};
+ // Model for drop-down
+ $scope.adminApps = [];
+ // Enable modal controls
+ this.step1 = true;
+ this.fileSelected = false;
+
+ // Flag that indicates background work is proceeding
+ $scope.isProcessing = true;
+
+ // Load user's admin applications
+ applicationsService.getAdminApps().promise().then(apps => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::init: getAdminApps returned' + JSON.stringify(apps));
+ if (!apps || typeof(apps) != 'object') {
+ $log.error('BulkUserModalCtrl::init: getAdminApps returned unexpected data');
+ }
+ else {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::init: admin apps length is ', apps.length);
+
+ // Sort app names and populate the drop-down model
+ let sortedApps = apps.sort(getSortOrder('name', true));
+ for (let i = 0; i < sortedApps.length; ++i) {
+ $scope.adminApps.push({
+ index: i,
+ id: sortedApps[i].id,
+ value: sortedApps[i].name,
+ title: sortedApps[i].name
+ });
+ }
+ // Pick the first one in the list
+ $scope.selectedApplication = $scope.adminApps[0];
+ }
+ $scope.isProcessing = false;
+ }).catch(err => {
+ $log.error('BulkUserModalCtrl::init: getAdminApps threw', err);
+ $scope.isProcessing = false;
+ });
+
+ }; // init
+
+ // Answers a function that compares properties with the specified name.
+ let getSortOrder = (prop, foldCase) => {
+ return function(a, b) {
+ let aProp = foldCase ? a[prop].toLowerCase() : a[prop];
+ let bProp = foldCase ? b[prop].toLowerCase() : b[prop];
+ if (aProp > bProp)
+ return 1;
+ else if (aProp < bProp)
+ return -1;
+ else
+ return 0;
+ }
+ }
+
+ //This is a fix for dropdown selection, due to b2b dropdown only update value field
+ $scope.$watch('selectedApplication.value', (newVal, oldVal) => {
+ for(var i=0;i<$scope.adminApps.length;i++){
+ if($scope.adminApps[i].value==newVal){
+ $scope.selectedApplication=angular.copy($scope.adminApps[i]);;
+ }
+ }
+ });
+
+ // Invoked when user picks an app on the drop-down.
+ $scope.appSelected = () => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::appSelected: selectedApplication.id is ' + $scope.selectedApplication.id);
+ this.appSelected = true;
+ }
+
+ // Caches the file name supplied by the event handler.
+ $scope.fileChangeHandler = (event, files) => {
+ this.fileSelected = true;
+ this.fileToRead = files[0];
+ if (debug)
+ $log.debug("BulkUserModalCtrl::fileChangeHandler: file is ", this.fileToRead);
+ }; // file change handler
+
+ /**
+ * Reads the contents of the file, calls portal endpoints
+ * to validate roles, userIds and existing role assignments;
+ * ultimately builds array of requests to be sent.
+ * Creates scope variable with input file contents for
+ * communication with functions.
+ *
+ * This function performs a synchronous step-by-step process
+ * using asynchronous promises. The code could all be inline
+ * here but the nesting becomes unwieldy.
+ */
+ $scope.readValidateFile = () => {
+ $scope.isProcessing = true;
+ $scope.progressMsg = 'Reading upload file..';
+ var reader = new FileReader();
+ reader.onload = function(event) {
+ $scope.uploadFile = $filter('csvToObj')(reader.result);
+ if (debug)
+ $log.debug('BulkUserModalCtrl::readValidateFile onload: data length is ' + $scope.uploadFile.length);
+ // sort input by orgUserId
+ $scope.uploadFile.sort(getSortOrder('orgUserId', true));
+
+ let appid = $scope.selectedApplication.id;
+ $scope.progressMsg = 'Fetching application roles..';
+ functionalMenuService.getManagedRolesMenu(appid).then(function (rolesObj) {
+ if (debug)
+ $log.debug("BulkUserModalCtrl::readValidateFile: managedRolesMenu returned " + JSON.stringify(rolesObj));
+ appRolesResult = rolesObj;
+ $scope.progressMsg = 'Validating application roles..';
+ $scope.verifyRoles();
+
+ let userPromises = $scope.buildUserChecks();
+ if (debug)
+ $log.debug('BulkUserModalCtrl::readValidateFile: userPromises length is ' + userPromises.length);
+ $scope.progressMsg = 'Validating Org Users..';
+ $q.all(userPromises).then(function() {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::readValidateFile: userCheckResult length is ' + userCheckResult.length);
+ $scope.evalUserCheckResults();
+
+ let appPromises = $scope.buildAppRoleChecks();
+ if (debug)
+ $log.debug('BulkUserModalCtrl::readValidateFile: appPromises length is ' + appPromises.length);
+ $scope.progressMsg = 'Querying application for user roles..';
+ $q.all(appPromises).then( function() {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::readValidateFile: appUserRolesRequest length is ' + appUserRolesRequest.length);
+ $scope.evalAppRoleCheckResults();
+
+ // Re sort by line for the confirmation dialog
+ $scope.uploadFile.sort(getSortOrder('line', false));
+ // We're done, confirm box may show the table
+ if (debug)
+ $log.debug('BulkUserModalCtrl::readValidateFile inner-then ends');
+ $scope.progressMsg = 'Done.';
+ $scope.isProcessing = false;
+ },
+ function(error) {
+ $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user-app roles');
+ $scope.isProcessing = false;
+ }
+ ); // then of app promises
+ },
+ function(error) {
+ $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user info');
+ $scope.isProcessing = false;
+ }
+ ); // then of user promises
+ },
+ function(error) {
+ $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app role info');
+ $scope.isProcessing = false;
+ }
+ ); // then of role promise
+
+ } // onload
+
+ // Invoke the reader on the selected file
+ reader.readAsText(this.fileToRead);
+ };
+
+ /**
+ * Evaluates the result set returned by the app role service.
+ * Sets an uploadFile array element status if a role is not defined.
+ * Reads and writes scope variable uploadFile.
+ * Reads closure variable appRolesResult.
+ */
+ $scope.verifyRoles = () => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::verifyRoles: appRoles is ' + JSON.stringify(appRolesResult));
+ // check roles in upload file against defined app roles
+ $scope.uploadFile.forEach( function (uploadRow) {
+ // skip rows that already have a defined status: headers etc.
+ if (uploadRow.status) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::verifyRoles: skip row ' + uploadRow.line);
+ return;
+ }
+ uploadRow.role = uploadRow.role.trim();
+ var foundRole=false;
+ for (var i=0; i < appRolesResult.length; i++) {
+ if (uploadRow.role.toUpperCase() === appRolesResult[i].rolename.trim().toUpperCase()) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::verifyRoles: match on role ' + uploadRow.role);
+ foundRole=true;
+ break;
+ }
+ };
+ if (!foundRole) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::verifyRoles: NO match on role ' + uploadRow.role);
+ uploadRow.status = 'Invalid role';
+ };
+ }); // foreach
+ }; // verifyRoles
+
+ /**
+ * Builds and returns an array of promises to invoke the
+ * searchUsers service for each unique Org User UID in the input.
+ * Reads and writes scope variable uploadFile, which must be sorted by Org User UID.
+ * The promise function writes to closure variable userCheckResult
+ */
+ $scope.buildUserChecks = () => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildUserChecks: uploadFile length is ' + $scope.uploadFile.length);
+ userCheckResult = [];
+ let promises = [];
+ let prevRow = null;
+ $scope.uploadFile.forEach(function (uploadRow) {
+ if (uploadRow.status) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildUserChecks: skip row ' + uploadRow.line);
+ return;
+ };
+ // detect repeated UIDs
+ if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildUserChecks: create request for orgUserId ' + uploadRow.orgUserId);
+ let userPromise = usersService.searchUsers(uploadRow.orgUserId).promise().then( (usersList) => {
+ if (typeof usersList[0] !== "undefined") {
+ userCheckResult.push({
+ orgUserId: usersList[0].orgUserId,
+ firstName: usersList[0].firstName,
+ lastName: usersList[0].lastName,
+ jobTitle: usersList[0].jobTitle
+ });
+ }
+ else {
+ // User not found.
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildUserChecks: searchUsers returned null');
+ }
+ }, function(error){
+ $log.error('BulkUserModalCtrl::buildUserChecks: searchUsers failed ' + JSON.stringify(error));
+ });
+ promises.push(userPromise);
+ }
+ else {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildUserChecks: skip repeated orgUserId ' + uploadRow.orgUserId);
+ }
+ prevRow = uploadRow;
+ }); // foreach
+ return promises;
+ }; // buildUserChecks
+
+ /**
+ * Evaluates the result set returned by the user service to set
+ * the uploadFile array element status if the user was not found.
+ * Reads and writes scope variable uploadFile.
+ * Reads closure variable userCheckResult.
+ */
+ $scope.evalUserCheckResults = () => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalUserCheckResult: uploadFile length is ' + $scope.uploadFile.length);
+ $scope.uploadFile.forEach(function (uploadRow) {
+ if (uploadRow.status) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalUserCheckResults: skip row ' + uploadRow.line);
+ return;
+ };
+ let foundorgUserId = false;
+ userCheckResult.forEach(function(userItem) {
+ if (uploadRow.orgUserId.toLowerCase() === userItem.orgUserId.toLowerCase()) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalUserCheckResults: found orgUserId ' + uploadRow.orgUserId);
+ foundorgUserId=true;
+ };
+ });
+ if (!foundorgUserId) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalUserCheckResults: NO match on orgUserId ' + uploadRow.orgUserId);
+ uploadRow.status = 'Invalid orgUserId';
+ }
+ }); // foreach
+ }; // evalUserCheckResults
+
+ /**
+ * Builds and returns an array of promises to invoke the getUserAppRoles
+ * service for each unique Org User in the input file.
+ * Each promise creates an update to be sent to the remote application
+ * with all role names.
+ * Reads scope variable uploadFile, which must be sorted by Org User.
+ * The promise function writes to closure variable appUserRolesRequest
+ */
+ $scope.buildAppRoleChecks = () => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildAppRoleChecks: uploadFile length is ' + $scope.uploadFile.length);
+ appUserRolesRequest = [];
+ let appId = $scope.selectedApplication.id;
+ let promises = [];
+ let prevRow = null;
+ $scope.uploadFile.forEach( function (uploadRow) {
+ if (uploadRow.status) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildAppRoleChecks: skip row ' + uploadRow.line);
+ return;
+ }
+ // Because the input is sorted, generate only one request for each Org User
+ if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildAppRoleChecks: create request for orgUserId ' + uploadRow.orgUserId);
+ let appPromise = usersService.getUserAppRoles(appId, uploadRow.orgUserId).promise().then( (userAppRolesResult) => {
+ // Reply for unknown user has all defined roles with isApplied=false on each.
+ if (typeof userAppRolesResult[0] !== "undefined") {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildAppRoleChecks: adding result '
+ + JSON.stringify(userAppRolesResult));
+ appUserRolesRequest.push({
+ orgUserId: uploadRow.orgUserId,
+ userAppRoles: userAppRolesResult
+ });
+ } else {
+ $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles returned ' + JSON.stringify(userAppRolesResult));
+ };
+ }, function(error){
+ $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles failed ', error);
+ });
+ promises.push(appPromise);
+ } else {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::buildAppRoleChecks: duplicate orgUserId, skip: '+ uploadRow.orgUserId);
+ }
+ prevRow = uploadRow;
+ }); // foreach
+ return promises;
+ }; // buildAppRoleChecks
+
+ /**
+ * Evaluates the result set returned by the app service and adjusts
+ * the list of updates to be sent to the remote application by setting
+ * isApplied=true for each role name found in the upload file.
+ * Reads and writes scope variable uploadFile.
+ * Reads closure variable appUserRolesRequest.
+ */
+ $scope.evalAppRoleCheckResults = () => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: uploadFile length is ' + $scope.uploadFile.length);
+ $scope.uploadFile.forEach(function (uploadRow) {
+ if (uploadRow.status) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: skip row ' + uploadRow.line);
+ return;
+ }
+ // Search for the match in the app-user-roles array
+ appUserRolesRequest.forEach( function (appUserRoleObj) {
+ if (uploadRow.orgUserId.toLowerCase() === appUserRoleObj.orgUserId.toLowerCase()) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: match on orgUserId ' + uploadRow.orgUserId);
+ let roles = appUserRoleObj.userAppRoles;
+ roles.forEach(function (appRoleItem) {
+ //if (debug)
+ // $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: checking uploadRow.role='
+ // + uploadRow.role + ', appRoleItem.roleName= ' + appRoleItem.roleName);
+ if (uploadRow.role === appRoleItem.roleName) {
+ if (appRoleItem.isApplied) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: existing role '
+ + appRoleItem.roleName);
+ uploadRow.status = 'Role exists';
+ }
+ else {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: new role '
+ + appRoleItem.roleName);
+ // After much back-and-forth I decided a clear indicator
+ // is better than blank in the table status column.
+ uploadRow.status = 'OK';
+ appRoleItem.isApplied = true;
+ }
+ // This count is not especially interesting.
+ // numberUserRolesSucceeded++;
+ }
+ }); // for each role
+ }
+ }); // for each result
+ }); // for each row
+ }; // evalAppRoleCheckResults
+
+ /**
+ * Sends requests to Portal requesting user role assignment.
+ * That endpoint handles creation of the user at the remote app if necessary.
+ * Reads closure variable appUserRolesRequest.
+ * Invoked by the Next button on the confirmation dialog.
+ */
+ $scope.updateDB = () => {
+ $scope.isProcessing = true;
+ $scope.progressMsg = 'Sending requests to application..';
+ if (debug)
+ $log.debug('BulkUserModalCtrl::updateDB: request length is ' + appUserRolesRequest.length);
+ var numberUsersSucceeded = 0;
+ let promises = [];
+ appUserRolesRequest.forEach(function(appUserRoleObj) {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::updateDB: appUserRoleObj is ' + JSON.stringify(appUserRoleObj));
+ let updateRequest = {
+ orgUserId: appUserRoleObj.orgUserId,
+ appId: $scope.selectedApplication.id,
+ appRoles: appUserRoleObj.userAppRoles
+ };
+ if (debug)
+ $log.debug('BulkUserModalCtrl::updateDB: updateRequest is ' + JSON.stringify(updateRequest));
+ let updatePromise = usersService.updateUserAppRoles(updateRequest).promise().then(res => {
+ if (debug)
+ $log.debug('BulkUserModalCtrl::updateDB: updated successfully: ' + JSON.stringify(res));
+ numberUsersSucceeded++;
+ }).catch(err => {
+ // What to do if one of many fails??
+ $log.error('BulkUserModalCtrl::updateDB failed: ', err);
+ confirmBoxService.showInformation(
+ 'Failed to update the user application roles. ' +
+ 'Error: ' + err.status).then(isConfirmed => { });
+ }).finally( () => {
+ // $log.debug('BulkUserModalCtrl::updateDB: finally()');
+ });
+ promises.push(updatePromise);
+ }); // for each
+
+ // Run all the promises
+ $q.all(promises).then(function(){
+ $scope.isProcessing = false;
+ confirmBoxService.showInformation('Processed ' + numberUsersSucceeded + ' users.').then(isConfirmed => {
+ // Close the upload-confirm dialog
+ ngDialog.close();
+ });
+ });
+ }; // updateDb
+
+ // Sets the variable that hides/reveals the user controls
+ $scope.step2 = () => {
+ this.fileSelected = false;
+ $scope.selectedFile = null;
+ $scope.fileModel = null;
+ this.step1 = false;
+ }
+
+ // Navigate between dialog screens using step number: 1,2,...
+ $scope.navigateBack = () => {
+ this.step1 = true;
+ this.fileSelected = false;
+ };
+
+ // Opens a dialog to show the data to be uploaded.
+ // Invoked by the upload button on the bulk user dialog.
+ $scope.confirmUpload = () => {
+ // Start the process
+ $scope.readValidateFile();
+ // Dialog shows progress
+ ngDialog.open({
+ templateUrl: 'app/views/users/new-user-dialogs/bulk-user.confirm.html',
+ scope: $scope
+ });
+ };
+
+ // Invoked by the Cancel button on the confirmation dialog.
+ $scope.cancelUpload = () => {
+ ngDialog.close();
+ };
+
+ init();
+ } // constructor
+ } // class
+ BulkUserModalCtrl.$inject = ['$scope', '$log', '$filter', '$q', 'usersService', 'applicationsService', 'confirmBoxService', 'functionalMenuService', 'ngDialog'];
+ angular.module('ecompApp').controller('BulkUserModalCtrl', BulkUserModalCtrl);
+
+ angular.module('ecompApp').directive('fileChange', ['$parse', function($parse){
+ return {
+ require: 'ngModel',
+ restrict: 'A',
+ link : function($scope, element, attrs, ngModel) {
+ var attrHandler = $parse(attrs['fileChange']);
+ var handler=function(e) {
+ $scope.$apply(function() {
+ attrHandler($scope, { $event:e, files:e.target.files } );
+ $scope.selectedFile = e.target.files[0].name;
+ });
+ };
+ element[0].addEventListener('change',handler,false);
+ }
+ }
+ }]);
+
+ angular.module('ecompApp').filter('csvToObj',function() {
+ return function(input) {
+ var result = [];
+ var len, i, line, o;
+ var lines = input.split('\n');
+ // Need 1-based index below
+ for (len = lines.length, i = 1; i <= len; ++i) {
+ // Use 0-based index for array
+ line = lines[i - 1].trim();
+ if (line.length == 0) {
+ // console.log("Skipping blank line");
+ result.push({
+ line: i,
+ orgUserId: '',
+ role: '',
+ status: 'Blank line'
+ });
+ continue;
+ }
+ o = line.split(',');
+ if (o.length !== 2) {
+ // other lengths not valid for upload
+ result.push({
+ line: i,
+ orgUserId: line,
+ role: '',
+ status: 'Failed to find 2 comma-separated values'
+ });
+ }
+ else {
+ // console.log("Valid line: ", val);
+ let entry = {
+ line: i,
+ orgUserId: o[0],
+ role: o[1]
+ // leave status undefined, this could be valid.
+ };
+ if (o[0].toLowerCase() === 'orgUserId') {
+ // not valid for upload, so set status
+ entry.status = 'Header';
+ }
+ else if (o[0].trim() == '' || o[1].trim() == '') {
+ // defend against line with only a single comma etc.
+ entry.status = 'Failed to find 2 non-empty values';
+ }
+ result.push(entry);
+ } // len 2
+ } // for
+ return result;
+ };
+ });
+
+
+
+})();