From b54df0ddd0c6a0372327c5aa3668e5a6458fcd64 Mon Sep 17 00:00:00 2001 From: st782s Date: Thu, 4 May 2017 07:48:42 -0400 Subject: [PORTAL-7] Rebase This rebasing includes common libraries and common overlays projects abstraction of components Change-Id: I9a24a338665c7cd058978e8636bc412d9e2fdce8 Signed-off-by: st782s --- .../users/new-user-dialogs/bulk-user.ack.html | 32 ++ .../users/new-user-dialogs/bulk-user.confirm.html | 83 +++ .../users/new-user-dialogs/bulk-user.controller.js | 577 +++++++++++++++++++++ .../users/new-user-dialogs/bulk-user.modal.html | 70 +++ .../users/new-user-dialogs/bulk-user.modal.less | 60 +++ .../users/new-user-dialogs/new-user.controller.js | 216 ++++++++ .../new-user-dialogs/new-user.controller.spec.js | 255 +++++++++ .../users/new-user-dialogs/new-user.modal.html | 84 +++ .../users/new-user-dialogs/new-user.modal.less | 112 ++++ .../client/app/views/users/users.controller.js | 243 +++++++++ .../app/views/users/users.controller.spec.js | 141 +++++ .../client/app/views/users/users.less | 47 ++ .../client/app/views/users/users.tpl.html | 98 ++++ 13 files changed, 2018 insertions(+) create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.ack.html create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.confirm.html create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.controller.js create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.html create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.less create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.js create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.spec.js create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.html create mode 100644 ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.less create mode 100644 ecomp-portal-FE-common/client/app/views/users/users.controller.js create mode 100644 ecomp-portal-FE-common/client/app/views/users/users.controller.spec.js create mode 100644 ecomp-portal-FE-common/client/app/views/users/users.less create mode 100644 ecomp-portal-FE-common/client/app/views/users/users.tpl.html (limited to 'ecomp-portal-FE-common/client/app/views/users') diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.ack.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.ack.html new file mode 100644 index 00000000..9527c750 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.ack.html @@ -0,0 +1,32 @@ + +
+
+
Bulk User Upload Acknowledgement
+
+

The valid entries have been uploaded.

+ +
+
OK
+
+
+
+
diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.confirm.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.confirm.html new file mode 100644 index 00000000..a3c0b534 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.confirm.html @@ -0,0 +1,83 @@ + +
+
Bulk User Upload Confirmation
+
+ + +
+ {{progressMsg}} +
+
+ +
+ +
+
+ Click OK to upload the valid requests. + Invalid requests will be ignored.
+
+ + + + + + + + + + + + + + + + + + +
LineOrg User ID + App + RoleStatus
+
+
+
+
+
+
+
+
+
+ +
+
+ + + +
+
+
diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.controller.js b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.controller.js new file mode 100644 index 00000000..e3046b86 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.controller.js @@ -0,0 +1,577 @@ +/*- + * ================================================================================ + * 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; + }; + }); + + + +})(); diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.html new file mode 100644 index 00000000..3d479cb9 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.html @@ -0,0 +1,70 @@ + +
+
Bulk User Upload
+
+
+
Select Application:
+
+ + + +
+
+ +
+
Select Upload File:
+ + + {{selectedFile}} +
File must have one entry per line with this format: +
orgUserId, role name
+
+
+ + +
+ +
+ +
+ + + + + +
+
+
diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.less b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.less new file mode 100644 index 00000000..b6ee63f8 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/bulk-user.modal.less @@ -0,0 +1,60 @@ +.bulk-user-modal { + height: 430px; + + .title { + .dGray18r; //AT&T Dark Gray + border-bottom: @blue-active 3px solid; + } + + .main { + margin: 16px; + + .upload-instructions { + .dGray14r; + } + + // http://stackoverflow.com/questions/572768/styling-an-input-type-file-button + + .file-label { + border: 1px solid #ffffff; + border-radius: 6px; + margin-top: 4px; + margin-bottom: 4px; + margin-left: 0px; + margin-right: 8px; + color: #ffffff; + background: #067ab4; + display: inline-block; + text-align: center; + font-family: Omnes-ECOMP-W02-Medium,Arial; + font-size: 14px; + height: 29px; + line-height: 29px; + width: 90px; + + input[type="file"] { + // Hide the browser's control + display: none; + } + + } + + .file-label:hover { + background: #009fdb; + } + + .file-label:active { + background: #009fdb; + } + + .file-label:invalid+span { + color: #ffffff; + } + + .file-label:valid+span { + color: #ffffff; + } + + } + +} diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.js b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.js new file mode 100644 index 00000000..882f1e8f --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.js @@ -0,0 +1,216 @@ +/*- + * ================================================================================ + * 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. + * ================================================================================ + */ +/** + * Created by nnaffar on 12/20/15. + */ +'use strict'; +(function () { + class NewUserModalCtrl { + constructor($scope, $log, usersService, applicationsService, confirmBoxService) { + let init = () => { + //$log.info('NewUserModalCtrl::init'); + this.isSaving = false; + this.anyChanges = false; + this.adminApps = []; + this.isGettingAdminApps = false; + if($scope.ngDialogData && $scope.ngDialogData.selectedUser && $scope.ngDialogData.dialogState){ + this.selectedUser = $scope.ngDialogData.selectedUser; + this.dialogState = $scope.ngDialogData.dialogState; + this.isShowBack = false; + if(this.dialogState === 3){ + this.getUserAppsRoles(); + } + }else{ + this.isShowBack = true; + this.selectedUser = null; + this.dialogState = 1; + } + }; + + this.appChanged = (index) => { + let myApp = this.adminApps[index]; + //$log.debug('NewUserModalCtrl::appChanged: index: ', index, '; app id: ', myApp.id, 'app name: ',myApp.name); + myApp.isChanged = true; + this.anyChanges = true; + } + + this.deleteApp = (app) => { + let appMessage = this.selectedUser.firstName + ' ' + this.selectedUser.lastName; + confirmBoxService.deleteItem(appMessage).then(isConfirmed => { + if(isConfirmed){ + this.anyChanges = true; + app.isChanged = true; + app.isDeleted = true; // use this to hide the app in the display + app.appRoles.forEach(function(role){ + role.isApplied = false; + }); + } + }).catch(err => { + $log.error('NewUserModalCtrl::deleteApp error: ',err); + confirmBoxService.showInformation('There was a problem deleting the the applications. ' + + 'Please try again later. Error: ' + err.status).then(isConfirmed => {}); + }); + }; + + this.getUserAppsRoles = () => { + if (!this.selectedUser || !this.selectedUser.orgUserId) { + $log.error('NewUserModalCtrl::getUserAppsRoles error: No user is selected'); + this.dialogState = 1; + return; + } + //$log.debug('NewUserModalCtrl::getUserAppsRoles: about to call getAdminAppsSimpler'); + this.isGettingAdminApps = true; + applicationsService.getAdminAppsSimpler().then((apps) => { + //$log.debug('NewUserModalCtrl::getUserAppsRoles: beginning of then for getAdminAppsSimpler'); + this.isGettingAdminApps = false; + if (!apps || !apps.length) { + $log.error('NewUserModalCtrl::getUserAppsRoles error: no admin apps found'); + return null; + } + //$log.debug('NewUserModalCtrl::getUserAppsRoles: then for getAdminAppsSimpler: step 2'); + //$log.debug('NewUserModalCtrl::getUserAppsRoles: admin apps: ', apps); + this.adminApps = apps; + this.dialogState = 3; + this.userAppRoles = {}; + this.numberAppsProcessed = 0; + this.isLoading = true; + apps.forEach(app => { + //$log.debug('NewUserModalCtrl::getUserAppsRoles: app: id: ', app.id, 'name: ',app.name); + // Keep track of which app has changed, so we know which apps to update using a BE API + app.isChanged = false; + // Each of these specifies a state, which corresponds to a different message and style that gets displayed + app.isLoading = true; + app.isError = false; + app.isDeleted = false; + app.printNoChanges = false; + app.isUpdating = false; + app.isErrorUpdating = false; + app.isDoneUpdating = false; + app.errorMessage = ""; + usersService.getUserAppRoles(app.id, this.selectedUser.orgUserId).promise().then((userAppRolesResult) => { + //$log.debug('NewUserModalCtrl::getUserAppsRoles: got a result for app: ',app.id,': ',app.name,': ',userAppRolesResult); + app.appRoles = userAppRolesResult; + app.isLoading = false; + + }).catch(err => { + $log.error(err); + app.isError = true; + app.isLoading = false; + app.errorMessage = err.headers('FEErrorString'); + //$log.debug('NewUserModalCtrl::getUserAppsRoles: in new-user.controller: response header: '+err.headers('FEErrorString')); + }).finally(()=>{ + this.numberAppsProcessed++; + if (this.numberAppsProcessed === this.adminApps.length) { + this.isLoading = false; + } + }); + }) + return; + }).catch(err => { + $log.error(err); + }) + + } + + /** + * Update the selected user apps with the new roles. + * If no roles remain, set the user to inactive. + */ + this.updateUserAppsRoles = () => { + // $log.debug('NewUserModalCtrl::updateUserAppsRoles: entering updateUserAppsRoles'); + if(!this.selectedUser || !this.selectedUser.orgUserId || !this.adminApps){ + $log.error('NewUserModalCtrl::updateUserAppsRoles: mmissing arguments'); + return; + } + this.isSaving = true; + //$log.debug('NewUserModalCtrl::updateUserAppsRoles: going to update user: ' + this.selectedUser.orgUserId); + this.numberAppsProcessed = 0; + this.numberAppsSucceeded = 0; + this.adminApps.forEach(app => { + if (app.isChanged) { + //$log.debug('NewUserModalCtrl::updateUserAppsRoles: app roles have changed; going to update: id: ', app.id, '; name: ', app.name); + app.isUpdating = true; + var newUserAppRoles = { + orgUserId: this.selectedUser.orgUserId, + appId: app.id, + appRoles: app.appRoles, + appName: app.name + }; + usersService.updateUserAppRoles(newUserAppRoles).promise() + .then(res => { + //$log.debug('NewUserModalCtrl::updateUserAppsRoles: User app roles updated successfully on app: ',app.id); + app.isUpdating = false; + app.isDoneUpdating = true; + this.numberAppsSucceeded++; + }).catch(err => { + $log.error(err); + app.isErrorUpdating = true; + confirmBoxService.showInformation( + 'Failed to update the user application roles: ' + err.status) + .then(isConfirmed => {}); + }).finally(()=>{ + this.numberAppsProcessed++; + if (this.numberAppsProcessed === this.adminApps.length) { + this.isSaving = false; // hide the spinner + } + if (this.numberAppsSucceeded === this.adminApps.length) { + $scope.closeThisDialog(true);//close and resolve dialog promise with true (to update the table) + } + }) + } else { + //$log.debug('NewUserModalCtrl::updateUserAppsRoles: app roles have NOT changed; NOT going to update: id: ', app.id, '; name: ', app.name); + app.noChanges = true; + app.isError = false; //remove the error message; just show the No Changes messages + this.numberAppsProcessed++; + this.numberAppsSucceeded++; + if (this.numberAppsProcessed === this.adminApps.length) { + this.isSaving = false; // hide the spinner + } + if (this.numberAppsSucceeded === this.adminApps.length) { + $scope.closeThisDialog(true);//close and resolve dialog promise with true (to update the table) + } + } + }); + }; + + /** + * Navigate between dialog screens using step number: 1,2,... + */ + this.navigateBack = () => { + if (this.dialogState === 1) { + //back from 1st screen? + } + if (this.dialogState === 3) { + this.dialogState = 1; + } + }; + + init(); + + $scope.$on('$stateChangeStart', e => { + //Disable navigation when modal is opened + //**Nabil - note: this will cause the history back state to be replaced with current state + e.preventDefault(); + }); + } + } + NewUserModalCtrl.$inject = ['$scope', '$log', 'usersService', 'applicationsService', 'confirmBoxService']; + angular.module('ecompApp').controller('NewUserModalCtrl', NewUserModalCtrl); +})(); diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.spec.js b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.spec.js new file mode 100644 index 00000000..8d5ac749 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.controller.spec.js @@ -0,0 +1,255 @@ +/*- + * ================================================================================ + * 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. + * ================================================================================ + */ +/** + * Created by nnaffar on 12/20/15. + */ +'use strict'; + +describe('Controller: NewUserModalCtrl ', () => { + beforeEach(module('testUtils')); + beforeEach(module('ecompApp')); + + let promisesTestUtils; + //destroy $http default cache before starting to prevent the error 'default cache already exists' + //_promisesTestUtils_ comes from testUtils for promises resolve/reject + beforeEach(inject((_CacheFactory_, _promisesTestUtils_)=> { + _CacheFactory_.destroyAll(); + promisesTestUtils = _promisesTestUtils_; + })); + + let newUser, $controller, $q, $rootScope, $log, $scope; + + let applicationsServiceMock, usersServiceMock, confirmBoxServiceMock; + let deferredAdminApps, deferredUsersAccounts, deferredUsersAppRoles, deferredUsersAppRoleUpdate; + + beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> { + $rootScope = _$rootScope_; + $q = _$q_; + $controller = _$controller_; + $log = _$log_; + })); + + beforeEach(()=> { + [deferredAdminApps, deferredUsersAccounts, deferredUsersAppRoles, deferredUsersAppRoleUpdate] = [$q.defer(),$q.defer(), $q.defer(), $q.defer()]; + + /*applicationsServiceMock = { + getAdminApps: () => { + var promise = () => {return deferredAdminApps.promise}; + var cancel = jasmine.createSpy(); + return { + promise: promise, + cancel: cancel + } + } + };*/ + + confirmBoxServiceMock = { + deleteItem: () => { + var promise = () => {return deferredAdminApps.promise}; + var cancel = jasmine.createSpy(); + return { + promise: promise, + cancel: cancel + } + } + }; + + applicationsServiceMock = jasmine.createSpyObj('applicationsServiceMock', ['getAdminAppsSimpler']); + applicationsServiceMock.getAdminAppsSimpler.and.returnValue(deferredAdminApps.promise); + + usersServiceMock = jasmine.createSpyObj('usersServiceMock', ['getAccountUsers','getUserAppRoles','updateUserAppsRoles']); + + //applicationsServiceMock.getAdminApps().promise().and.returnValue(deferredAdminApps.promise); + usersServiceMock.getAccountUsers.and.returnValue(deferredUsersAccounts.promise); + usersServiceMock.getUserAppRoles.and.returnValue(deferredUsersAppRoles.promise); + usersServiceMock.updateUserAppsRoles.and.returnValue(deferredUsersAppRoleUpdate.promise); + + $scope = $rootScope.$new(); + newUser = $controller('NewUserModalCtrl', { + $scope: $scope, + $log: $log, + usersService: usersServiceMock, + applicationsService: applicationsServiceMock, + confirmBoxService: confirmBoxServiceMock + }); + //$scope.users = users; + }); + + /*beforeEach(()=> { + scope = $rootScope.$new(); + newUser = $controller('NewUserModalCtrl', { + $scope: scope, + $log: $log, + usersService: usersService, + applicationsService: applicationsService, + confirmBoxService: confirmBoxService + }); + });*/ + + + it('should open modal window without user when no user is selected', ()=> { + expect(newUser.selectedUser).toBe(null); + }); + + it('should open modal window with selectedUser apps roles when user is selected', ()=> { + let roles = {apps: [{id: 1, appRoles: [{id: 3, isApplied: true}]}]}; + let someUser = {orgUserId: 'asdfjl'}; + + deferredUsersAppRoles.resolve(roles); + deferredAdminApps.resolve(roles.apps); + + $scope.ngDialogData = { + selectedUser: someUser, + dialogState: 2 + }; + + //inject ngDialogData to the scope controller + newUser = $controller('NewUserModalCtrl', { + $scope: $scope, + $log: $log, + usersService: usersServiceMock, + applicationsService: applicationsServiceMock, + confirmBoxService: confirmBoxServiceMock + }); + + newUser.getUserAppsRoles(); + $scope.$apply(); + + expect(newUser.selectedUser).toBe(someUser); + expect(newUser.adminApps).toEqual(roles.apps); + }); + + it('should push to apps order list only apps that has applied roles when initializing', () => { + let roles = {apps: [{appId: 13, appRoles: [{id: 3, isApplied: true}]},{appId: 20, appRoles: [{id: 3, isApplied: false}]}]}; + let someUser = {orgUserId: 'asdfjl'}; + + deferredUsersAppRoles.resolve(roles); + //deferredAdminApps.resolve(roles.apps); + + $scope.ngDialogData = { + selectedUser: someUser, + dialogState: 2 + }; + + //inject ngDialogData to the scope controller + newUser = $controller('NewUserModalCtrl', { + $scope: $scope, + $log: $log, + usersService: usersServiceMock, + applicationsService: applicationsServiceMock, + confirmBoxService: confirmBoxServiceMock + }); + + $scope.$apply(); + + // expect(newUser.appsOrder).toEqual([13]); + }); + + it('should push app to apps order list when applying at least one role to user from app', () => { + let roles = {apps: [{appId: 13, appRoles: [{id: 3, isApplied: true}]},{appId: 20, appRoles: [{id: 3, isApplied: false}]}]}; + let someUser = {orgUserId: 'asdfjl'}; + + // promisesTestUtils.resolvePromise(usersService, 'getUserAppsRoles', roles); + deferredUsersAppRoles.resolve(roles); + + $scope.ngDialogData = { + selectedUser: someUser, + dialogState: 2 + }; + + //inject ngDialogData to the scope controller + newUser = $controller('NewUserModalCtrl', { + $scope: $scope, + $log: $log, + usersService: usersServiceMock, + applicationsService: applicationsServiceMock, + confirmBoxService: confirmBoxServiceMock + }); + + //$scope.$apply(); + //newUser.updateAppsOrder({appId: 39, appRoles: [{id: 13, isApplied: true}]}); + $scope.$apply(); + + // expect(newUser.appsOrder).toEqual([13, 39]); + }); + + + it('should remove app from list when removing all user roles in it', () => { + let roles = {apps: [{appName: 'aaa', appId: 13, appRoles: [{id: 3, isApplied: true}]},{appName: 'vvv', appId: 20, appRoles: [{id: 3, isApplied: true}]}]}; + let someUser = {orgUserId: 'asdfjl'}; + + // promisesTestUtils.resolvePromise(usersService, 'getUserAppsRoles', roles); + promisesTestUtils.resolvePromise(confirmBoxServiceMock, 'deleteItem', true); + + deferredUsersAppRoles.resolve(roles); + + $scope.ngDialogData = { + selectedUser: someUser, + dialogState: 2 + }; + + //inject ngDialogData to the scope controller + newUser = $controller('NewUserModalCtrl', { + $scope: $scope, + $log: $log, + usersService: usersServiceMock, + applicationsService: applicationsServiceMock, + confirmBoxService: confirmBoxServiceMock + }); + + $scope.$apply(); + newUser.deleteApp(roles.apps[0]); + $scope.$apply(); + + // expect(newUser.appsOrder).toEqual([20]); + }); + + it('should close the modal when update changes succeeded', () => { + let roles = {apps: [{appName: 'aaa', appId: 13, appRoles: [{id: 3, isApplied: true}]},{appName: 'vvv', appId: 20, appRoles: [{id: 3, isApplied: true}]}]}; + let someUser = {orgUserId: 'asdfjl'}; + //promisesTestUtils.resolvePromise(usersServiceMock, 'getUserAppsRoles', roles); + //promisesTestUtils.resolvePromise(usersServiceMock, 'updateUserAppsRoles'); + deferredUsersAppRoles.resolve(roles); + deferredUsersAppRoleUpdate.resolve(); + deferredAdminApps.resolve(roles.apps); + + $scope.ngDialogData = { + selectedUser: someUser, + dialogState: 2 + }; + + //inject ngDialogData to the scope controller + newUser = $controller('NewUserModalCtrl', { + $scope: $scope, + $log: $log, + usersService: usersServiceMock, + applicationsService: applicationsServiceMock, + confirmBoxService: confirmBoxServiceMock + }); + $scope.closeThisDialog = function(){}; + spyOn($scope, 'closeThisDialog'); + + newUser.getUserAppsRoles(); + $scope.$apply(); + newUser.updateUserAppsRoles(); + $scope.$apply(); + expect($scope.closeThisDialog).toHaveBeenCalledWith(true); + }); + }); diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.html b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.html new file mode 100644 index 00000000..5f26152b --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.html @@ -0,0 +1,84 @@ + +
+ +
+ + + +
+ + + +
+
+ +
+
+ + +
+
+ Access and roles: +
+ +
+
+
{{app.name | elipsis: 27}}
+
+ +
+
{{app.errorMessage}}
+
Contacting application...
+
No changes
+
Updating application...
+
Finished updating application
+
Could not update application...
+
+
+
+
+ +
+ + + + +
+ +
+ +
+ + + +
diff --git a/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.less b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.less new file mode 100644 index 00000000..68c23e52 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/new-user-dialogs/new-user.modal.less @@ -0,0 +1,112 @@ +.new-user-modal { + height: 430px; + + .user-apps-roles{ + .title{ + //.n18r; + .dGray18r; //AT&T Dark Gray + border-bottom: @portalDBlue 3px solid; + } + + input:not([type="button"]) { + height: 13px; + } + + .app-roles-list{ + height: 286px; + overflow-y: auto; + + .app-item{ + border: 1px solid @portalLGray; + border-radius: 2px; + background-color: @funcBkgGray; + + padding: 10px; + margin-top: 8px; + //margin-right: 6px; + //margin-left: 6px; + + .app-item-left{ + padding-top: 0; + line-height: 30px; + height: 30px; + vertical-align: middle; + display:inline-block; + width: 45%; + border-radius: 2px; + border: 1px solid @portalLGray; + margin-right: 10px; + padding-left: 4px; + background: @portalWhite; + white-space: nowrap; + + } + .app-item-right{ + display:inline-block; + width: 45%; + border-radius: 2px; + border: 1px solid @portalLGray; + background: @portalWhite; + vertical-align: middle; + } + + .app-item-right-error{ + .portalRed; + padding: 7px 7px 7px 7px; + display:inline-block; + width: 45%; + border-radius: 2px; + border: 1px solid @portalLGray; + background: @portalWhite; + vertical-align: middle; + } + + .app-item-right-contacting{ + .portalGreen; + padding: 7px 7px 7px 7px; + display:inline-block; + width: 45%; + border-radius: 2px; + border: 1px solid @portalLGray; + background: @portalWhite; + vertical-align: middle; + } + + .app-select-left{ + width: 45%; + margin-right: 10px; + vertical-align: middle; + + + .select-field{ + padding-top: 0; + line-height: 30px; + height: 30px; + vertical-align: middle; + border-radius: 2px; + border: 1px solid @portalLGray; + margin-right: 10px; + padding-left: 4px; + background: @portalWhite; + display:inline-block; + } + } + + + .app-item-delete{ + .ico_trash_default; + display: inline-block; + vertical-align: 2px; + cursor: pointer; + position: relative; + top: 6px; + color: transparent; + margin-left: 8px; + + } + + } + } + + } +} diff --git a/ecomp-portal-FE-common/client/app/views/users/users.controller.js b/ecomp-portal-FE-common/client/app/views/users/users.controller.js new file mode 100644 index 00000000..1aa67601 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/users.controller.js @@ -0,0 +1,243 @@ +/*- + * ================================================================================ + * 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. + * ================================================================================ + */ +'use strict'; +(function () { + class UsersCtrl { + constructor($log, applicationsService, usersService, confirmBoxService, $scope, ngDialog) { + this.$log = $log; + $scope.adminAppsIsNull = false; + $scope.appsIsDown = false; + $scope.noUsersInApp = false; + $scope.multiAppAdmin = false; + + $log.info('UsersCtrl:: initializing...'); + /** + * Handle all active HTTP requests + * activeRequests @type {Array[requests with cancel option]} + */ + let activeRequests = []; + let clearReq = (req) => { + activeRequests.splice(activeRequests.indexOf(req), 1); + }; + + let init = () => { + this.isLoadingTable = false; + this.selectedApp = null; + this.isAppSelectDisabled = false; + this.selectApp = 'Select application'; + this.adminApps = [{index: 0, id: 0, value: this.selectApp, title: this.selectApp}]; + getAdminApps(); + + /*Table general configuration params*/ + this.searchString = ''; + /*Table data*/ + this.usersTableHeaders = ['First Name', 'Last Name', 'User ID', 'Roles']; + this.accountUsers = []; + }; + + let getAdminApps = () => { + $log.debug('UsersCtrl::getAdminApps: - Starting getAdminApps'); + try { + this.isLoadingTable = true; + let adminAppsReq = applicationsService.getAdminApps(); + adminAppsReq.promise().then(apps => { + if (!apps || !apps.length) { + $log.error('UsersCtrl::getAdminApps: - no apps found'); + return null; + } + $log.debug('UsersCtrl::getAdminApps: Apps for this user are: ' + JSON.stringify(apps)); + if (apps.length >= 2) { + $log.info('UsersCtrl::getAdminApps: - more than one app for this admin:', apps.length, ' apps'); + $scope.multiAppAdmin = true; + } else { + this.adminApps = [] ; + } + let sortedApps = apps.sort(getSortOrder("name")); + let realAppIndex = 1; + for(let i=1; i<=sortedApps.length; i++){ + this.adminApps.push({ + index: realAppIndex, + id: sortedApps[i - 1].id, + value: sortedApps[i - 1].name, + title: sortedApps[i - 1].name + }); + realAppIndex = realAppIndex + 1; + } + + $log.debug('UsersCtrl::getAdminApps: Apps for this user are: ' + JSON.stringify(this.adminApps)); + + this.selectedApp = this.adminApps[0]; + clearReq(adminAppsReq); + $scope.adminAppsIsNull = false; + }).catch(e => { + $scope.adminAppsIsNull = true; + $log.error('UsersCtrl::getAdminApps: - getAdminApps() failed = '+ e.message); + clearReq(adminAppsReq); + confirmBoxService.showInformation('There was a problem retrieving the applications. ' + + 'Please try again later.').then(isConfirmed => {}); + + }).finally(() => { + this.isLoadingTable = false; + }); + } catch (e) { + $scope.adminAppsIsNull = true; + $log.error('UsersCtrl::getAdminApps: - getAdminApps() failed!'); + this.isLoadingTable = false; + } + }; + + let getSortOrder = (prop) => { + return function(a, b) { + if (a[prop] > b[prop]) { + return 1; + } else if (a[prop] < b[prop]) { + return -1; + } + return 0; + } + } + + this.updateUsersList = () => { + $scope.appsIsDown = false; + $scope.noUsersInApp = false; + // $log.debug('UsersCtrl::updateUsersList: Starting updateUsersList'); + //reset search string + this.searchString = ''; + //should i disable this too in case of moving between tabs? + this.isAppSelectDisabled = true; + //activate spinner + this.isLoadingTable = true; + + if(this.adminApps!=null && this.selectedApp!=null){ + var tempSelected = null; + for(let i=0; i<=this.adminApps.length; i++){ + if(typeof this.adminApps[i] != 'undefined' && this.selectedApp.value==this.adminApps[i].value){ + tempSelected=_.clone(this.adminApps[i]); + } + } + if(tempSelected!=null){ + this.selectedApp= tempSelected; + } + } + + if (this.selectedApp.title != this.selectApp) { // 'Select Application' + usersService.getAccountUsers(this.selectedApp.id) + .then(accountUsers => { + $log.debug('UsersCtrl::updateUsersList accountUsers: '+ accountUsers); + if (angular.isObject(accountUsers)===false) { + $log.error('UsersCtrl::updateUsersList accountUsers: App is down!'); + $scope.appsIsDown = true; + } + $log.debug('UsersCtrl::updateUsersList length: '+ Object.keys(accountUsers).length); + this.isAppSelectDisabled = false; + this.accountUsers = accountUsers; + if (angular.isObject(accountUsers) && Object.keys(accountUsers).length === 0) { + $log.debug('UsersCtrl::updateUsersList accountUsers: App has no users.'); + $scope.noUsersInApp = true; + } + }).catch(err => { + this.isAppSelectDisabled = false; + $log.error('UsersCtrl::updateUsersList error: ' + err); + confirmBoxService.showInformation('There was a problem updating the users List. ' + + 'Please try again later.').then(isConfirmed => {}); + $scope.appsIsDown = true; + }).finally(() => { + this.isLoadingTable = false; + $scope.noAppSelected = false; + }); + } else { + // this.selectedApp = this.adminApps[0]; + this.isAppSelectDisabled = false; + this.isLoadingTable = false; + $scope.noUsersInApp = false; + $scope.noAppSelected = true; + } + }; + + + this.openAddNewUserModal = (user) => { + let data = null; + if (user) { + data = { + dialogState: 3, + selectedUser: { + orgUserId: user.orgUserId, + firstName: user.firstName, + lastName: user.lastName + } + } + } + ngDialog.open({ + templateUrl: 'app/views/users/new-user-dialogs/new-user.modal.html', + controller: 'NewUserModalCtrl', + controllerAs: 'newUser', + data: data + }).closePromise.then(needUpdate => { + if (needUpdate.value === true) { + $log.debug('UsersCtrl::openAddNewUserModal updating table data...'); + this.updateUsersList(); + } + }); + }; + + this.openBulkUserUploadModal = (adminApps) => { + let data = null; + if (adminApps) { + data = { + dialogState: 3, + selectedApplication: { + appid: adminApps[0].appid, + appName: adminApps[0].appName + } + } + } + ngDialog.open({ + templateUrl: 'app/views/users/new-user-dialogs/bulk-user.modal.html', + controller: 'BulkUserModalCtrl', + controllerAs: 'bulkUser', + data: data + }).closePromise.then(needUpdate => { + this.updateUsersList(); + }); + }; + + + $scope.$watch('users.selectedApp.value', (newVal, oldVal) => { + if (!newVal || _.isEqual(newVal, oldVal)) { + return; + } + $log.debug('UsersCtrl::openAddNewUserModal:$watch selectedApp -> Fire with: ', newVal); + this.accountUsers = []; //reset table and show swirl here + this.updateUsersList(); + }); + + $scope.$on('$destroy', () => { + //cancel all active requests when closing the modal + activeRequests.forEach(req => { + req.cancel(); + }); + }); + + init(); + } + } + UsersCtrl.$inject = ['$log', 'applicationsService', 'usersService', 'confirmBoxService', '$scope', 'ngDialog']; + angular.module('ecompApp').controller('UsersCtrl', UsersCtrl); +})(); diff --git a/ecomp-portal-FE-common/client/app/views/users/users.controller.spec.js b/ecomp-portal-FE-common/client/app/views/users/users.controller.spec.js new file mode 100644 index 00000000..96231163 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/users.controller.spec.js @@ -0,0 +1,141 @@ +/*- + * ================================================================================ + * 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. + * ================================================================================ + */ +// /** +// * Created by nnaffar on 12/17/15. +// */ +// 'use strict'; +// +// describe('Controller: UsersCtrl ', () => { +// beforeEach(module('ecompApp')); +// +// //destroy $http default cache before starting to prevent the error 'default cache already exists' +// beforeEach(inject((_CacheFactory_)=> { +// _CacheFactory_.destroyAll(); +// })); +// +// let users, $controller, $q, $rootScope, $log, $scope; +// +// beforeEach(inject((_$controller_, _$q_, _$rootScope_, _$log_)=> { +// [$controller, $q, $rootScope, $log] = [_$controller_, _$q_, _$rootScope_, _$log_]; +// })); +// +// let applicationsServiceMock, usersServiceMock; +// let deferredAdminApps, deferredUsersAccounts; +// beforeEach(()=> { +// [deferredAdminApps, deferredUsersAccounts] = [$q.defer(), $q.defer()]; +// +// applicationsServiceMock = { +// getAdminApps: () => { +// var promise = () => {return deferredAdminApps.promise}; +// var cancel = jasmine.createSpy(); +// return { +// promise: promise, +// cancel: cancel +// } +// } +// }; +// +// usersServiceMock = jasmine.createSpyObj('usersServiceMock', ['getAccountUsers']); +// +// //applicationsServiceMock.getAdminApps().promise().and.returnValue(deferredAdminApps.promise); +// usersServiceMock.getAccountUsers.and.returnValue(deferredUsersAccounts.promise); +// +// $scope = $rootScope.$new(); +// users = $controller('UsersCtrl', { +// $log: $log, +// applicationsService: applicationsServiceMock, +// usersService: usersServiceMock, +// $scope: $scope +// }); +// $scope.users = users; +// }); +// +// //MOCKS +// let appsListMock = [ +// {value: 'SSP', title: 'SSP', id: 3}, +// {value: 'ASDC', title: 'ASDC', id: 23}, +// {value: 'Formation', title: 'Formation', id: 223} +// ]; +// +// let usersListMock = [ +// { +// "orgUserId": "nn605g", +// "firstName": "Nabil", +// "lastName": "Naffar", +// "roles": [ +// { +// "roleId": 1, +// "roleName": "Standard user" +// }, +// { +// "roleId": 9, +// "roleName": "Super standard user" +// }, +// { +// "roleId": 2, +// "roleName": "Super duper standard user" +// } +// ] +// }]; +// let secondUsersListMock = [ +// { +// "orgUserId": "sadf7", +// "firstName": "John", +// "lastName": "Hall", +// "roles": [ +// { +// "roleId": 1, +// "roleName": "Standard user" +// }, +// { +// "roleId": 2, +// "roleName": "Super duper standard user" +// } +// ] +// }]; +// +// it('should get all user\'s administrated applications when initializing the view', ()=> { +// deferredAdminApps.resolve(appsListMock); +// deferredUsersAccounts.resolve(usersListMock); +// $scope.$apply(); +// expect(users.adminApps).toEqual(appsListMock); +// expect(users.selectedApp).toEqual(appsListMock[0]); +// }); +// +// it('should get first application users by default when initializing the view', () => { +// $scope.$apply(); +// deferredAdminApps.resolve(appsListMock); +// deferredUsersAccounts.resolve(usersListMock); +// $scope.$apply(); +// expect(users.accountUsers).toEqual(usersListMock); +// }); +// +// it('should get application users when changing application', () => { +// $scope.$apply(); +// deferredAdminApps.resolve(appsListMock); +// $scope.$apply(); +// +// users.selectedApp = appsListMock[1]; +// deferredUsersAccounts.resolve(secondUsersListMock); +// $scope.$apply('users');//change app +// +// expect(users.accountUsers).toEqual(secondUsersListMock); +// }); +// }); diff --git a/ecomp-portal-FE-common/client/app/views/users/users.less b/ecomp-portal-FE-common/client/app/views/users/users.less new file mode 100644 index 00000000..7a0e9ebb --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/users.less @@ -0,0 +1,47 @@ +.users-page-main{ + .bg_portalWhite;//white for 1702 + //.bg_portalGray; // gray for 1610 + position: @page-main-position; + top: @page-main-top; + left: @page-main-left; + right: @page-main-right; + bottom: @page-main-bottom; + padding-top: @padding-top; + overflow-y: @page-main-overflow-y; + padding-left: @padding-left-side; +#input-table-search::-webkit-input-placeholder, +{ +font-style: italic; + color: #D7D7D7; +} + .users-table { + width: @table-width; + //margin-left: @table-margin-left; + margin: 0 auto; + + } + + .error-text { + width: 1170px; + margin: auto; + padding: 10px; + left: 20px; + font-weight: bold; + font-size: 16px; + text-align: left; + color: @err; + // background-color: @portalWhite; + background-color: @u; + + .error-help { + color: @o; //@portalDGray; + font-weight: normal; + } + + .error-help-bold { + color: @o; //@portalDGray; + font-weight: bold; + } + + } +} diff --git a/ecomp-portal-FE-common/client/app/views/users/users.tpl.html b/ecomp-portal-FE-common/client/app/views/users/users.tpl.html new file mode 100644 index 00000000..ff3edde0 --- /dev/null +++ b/ecomp-portal-FE-common/client/app/views/users/users.tpl.html @@ -0,0 +1,98 @@ + +
+
+
+
+

Users

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

Use the 'Select application' dropdown to see users.

+
+
+

 

+

+ No users found. Select "Add User" to add a User to the application. +

+
+
+

 

+

+ Failed to communicate with the application. + Please try again later or contact a system administrator. +

+
+
+ +
+ + + + + + + + + + + + + + + + + +
First NameLast NameUser IDRoles
+
+
+
+
+
+

Attention:

+

 

+

It appears that you have not been added as an admin yet to an application.

+

 

+

Click on the Admins link to the left and check and see if you are listed as an admin for an application. + If not, you can add yourself to the appropriate application.

+
+
+
+ +
-- cgit 1.2.3-korg