[PORTAL-20,PORTAL-23,PORTAL-32] Repair defects
[portal.git] / ecomp-portal-FE-common / client / app / views / users / new-user-dialogs / bulk-user.controller.js
1 /*-
2  * ================================================================================
3  * ECOMP Portal
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ================================================================================
19  */
20 /**
21  * bulk user upload controller
22  */
23 'use strict';
24 (function () {
25     class BulkUserModalCtrl {
26         constructor($scope, $log, $filter, $q, usersService, applicationsService, confirmBoxService, functionalMenuService, ngDialog) {
27                 
28                 // Set to true for copious console output
29                 var debug = false;
30                 // Roles fetched from app service
31                 var appRolesResult = [];
32                 // Users fetched from user service
33                 var     userCheckResult = [];
34                 // Requests for user-role assignment built by validator
35                 var appUserRolesRequest = [];
36                 
37                 let init = () => {
38                         if (debug)
39                                 $log.debug('BulkUserModalCtrl::init');
40                         // Angular insists on this.
41                         $scope.fileModel = {};
42                         // Model for drop-down
43                         $scope.adminApps = [];
44                         // Enable modal controls
45                         this.step1 = true;
46                         this.fileSelected = false;
47
48                         // Flag that indicates background work is proceeding
49                         $scope.isProcessing = true;
50
51                         // Load user's admin applications
52                         applicationsService.getAdminApps().promise().then(apps => {
53                                 if (debug)
54                                         $log.debug('BulkUserModalCtrl::init: getAdminApps returned' + JSON.stringify(apps));
55                     if (!apps || typeof(apps) != 'object') {
56                         $log.error('BulkUserModalCtrl::init: getAdminApps returned unexpected data');
57                     }
58                     else {
59                         if (debug)
60                                 $log.debug('BulkUserModalCtrl::init:  admin apps length is ', apps.length);
61                         
62                         // Sort app names and populate the drop-down model
63                         let sortedApps = apps.sort(getSortOrder('name', true));
64                         for (let i = 0; i < sortedApps.length; ++i) {
65                             $scope.adminApps.push({
66                                 index: i,
67                                 id: sortedApps[i].id,
68                                 value: sortedApps[i].name,
69                                 title: sortedApps[i].name
70                             });
71                         }
72                         // Pick the first one in the list
73                         $scope.selectedApplication = $scope.adminApps[0];
74                     }
75                                 $scope.isProcessing = false;
76                 }).catch(err => {
77                     $log.error('BulkUserModalCtrl::init: getAdminApps threw', err);
78                         $scope.isProcessing = false;
79                 });
80                         
81                 }; // init
82                 
83                 // Answers a function that compares properties with the specified name.
84                 let getSortOrder = (prop, foldCase) => {
85                 return function(a, b) {
86                         let aProp = foldCase ? a[prop].toLowerCase() : a[prop];
87                         let bProp = foldCase ? b[prop].toLowerCase() : b[prop];
88                     if (aProp > bProp)
89                         return 1;
90                     else if (aProp < bProp) 
91                         return -1;
92                     else
93                         return 0;
94                 }
95             }
96                 
97                 //This is a fix for dropdown selection, due to b2b dropdown only update value field
98                 $scope.$watch('selectedApplication.value', (newVal, oldVal) => {
99                         for(var i=0;i<$scope.adminApps.length;i++){                     
100                                 if($scope.adminApps[i].value==newVal){
101                                         $scope.selectedApplication=angular.copy($scope.adminApps[i]);;
102                                 }
103                         }
104                 });
105
106                 // Invoked when user picks an app on the drop-down.
107                 $scope.appSelected = () => {
108                         if (debug)
109                                 $log.debug('BulkUserModalCtrl::appSelected: selectedApplication.id is ' + $scope.selectedApplication.id);
110                         this.appSelected = true;
111                 }
112                 
113                 // Caches the file name supplied by the event handler.
114                 $scope.fileChangeHandler = (event, files) => {
115                         this.fileSelected = true;
116                         this.fileToRead = files[0];
117                         if (debug)
118                                 $log.debug("BulkUserModalCtrl::fileChangeHandler: file is ", this.fileToRead);
119                 }; // file change handler
120                 
121                 /**
122                  * Reads the contents of the file, calls portal endpoints
123                  * to validate roles, userIds and existing role assignments;
124                  * ultimately builds array of requests to be sent.
125                  * Creates scope variable with input file contents for
126                  * communication with functions.
127                  * 
128                  * This function performs a synchronous step-by-step process
129                  * using asynchronous promises. The code could all be inline
130                  * here but the nesting becomes unwieldy.
131                  */
132                 $scope.readValidateFile = () => {
133                         $scope.isProcessing = true;
134                         $scope.progressMsg = 'Reading upload file..';
135                         var reader = new FileReader();
136                         reader.onload = function(event) {
137                                 $scope.uploadFile = $filter('csvToObj')(reader.result);
138                                 if (debug)
139                                         $log.debug('BulkUserModalCtrl::readValidateFile onload: data length is ' + $scope.uploadFile.length);
140                                 // sort input by orgUserId
141                                 $scope.uploadFile.sort(getSortOrder('orgUserId', true));
142                                 
143                                 let appid = $scope.selectedApplication.id;
144                                 $scope.progressMsg = 'Fetching application roles..';
145                     functionalMenuService.getManagedRolesMenu(appid).then(function (rolesObj) {
146                         if (debug)
147                                 $log.debug("BulkUserModalCtrl::readValidateFile: managedRolesMenu returned " + JSON.stringify(rolesObj));
148                                         appRolesResult = rolesObj;
149                                         $scope.progressMsg = 'Validating application roles..';
150                         $scope.verifyRoles();
151                                 
152                         let userPromises = $scope.buildUserChecks();
153                         if (debug)
154                                 $log.debug('BulkUserModalCtrl::readValidateFile: userPromises length is ' + userPromises.length);
155                         $scope.progressMsg = 'Validating Org Users..';
156                         $q.all(userPromises).then(function() {
157                                 if (debug)
158                                         $log.debug('BulkUserModalCtrl::readValidateFile: userCheckResult length is ' + userCheckResult.length);
159                                 $scope.evalUserCheckResults();
160                                         
161                                 let appPromises = $scope.buildAppRoleChecks();
162                                 if (debug)
163                                         $log.debug('BulkUserModalCtrl::readValidateFile: appPromises length is ' + appPromises.length);
164                                 $scope.progressMsg = 'Querying application for user roles..';
165                                 $q.all(appPromises).then( function() {
166                                         if (debug)
167                                                 $log.debug('BulkUserModalCtrl::readValidateFile: appUserRolesRequest length is ' + appUserRolesRequest.length);
168                                         $scope.evalAppRoleCheckResults();
169                                         
170                                                         // Re sort by line for the confirmation dialog
171                                                         $scope.uploadFile.sort(getSortOrder('line', false));
172                                                         // We're done, confirm box may show the table
173                                                         if (debug)
174                                                                 $log.debug('BulkUserModalCtrl::readValidateFile inner-then ends');
175                                                         $scope.progressMsg = 'Done.';
176                                                         $scope.isProcessing = false;
177                                 },
178                                 function(error) {
179                                         $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user-app roles');
180                                                         $scope.isProcessing = false;
181                                 }
182                                 ); // then of app promises
183                         },
184                         function(error) {
185                                 $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving user info');
186                                 $scope.isProcessing = false;
187                         }
188                         ); // then of user promises
189                     },
190                     function(error) {
191                         $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app role info');
192                         $scope.isProcessing = false;
193                     }
194                     ); // then of role promise
195            
196                         } // onload
197                         
198                         // Invoke the reader on the selected file
199                         reader.readAsText(this.fileToRead);
200                 }; 
201                 
202                 /**
203                  * Evaluates the result set returned by the app role service.
204                  * Sets an uploadFile array element status if a role is not defined.
205                  * Reads and writes scope variable uploadFile.
206                  * Reads closure variable appRolesResult.
207                  */
208                 $scope.verifyRoles = () => {
209                         if (debug)
210                                 $log.debug('BulkUserModalCtrl::verifyRoles: appRoles is ' + JSON.stringify(appRolesResult));
211                         // check roles in upload file against defined app roles
212                         $scope.uploadFile.forEach( function (uploadRow) {
213                                 // skip rows that already have a defined status: headers etc.
214                                 if (uploadRow.status) {
215                                         if (debug)
216                                                 $log.debug('BulkUserModalCtrl::verifyRoles: skip row ' + uploadRow.line);
217                                         return;
218                                 }
219                                 uploadRow.role = uploadRow.role.trim();
220                                 var foundRole=false;
221                                 for (var i=0; i < appRolesResult.length; i++) {
222                                         if (uploadRow.role.toUpperCase() === appRolesResult[i].rolename.trim().toUpperCase()) {
223                                                 if (debug)
224                                                         $log.debug('BulkUserModalCtrl::verifyRoles: match on role ' + uploadRow.role);
225                                                 foundRole=true;
226                                                 break;
227                                         }
228                                 };
229                                 if (!foundRole) {
230                                         if (debug)
231                                                 $log.debug('BulkUserModalCtrl::verifyRoles: NO match on role ' + uploadRow.role);
232                                         uploadRow.status = 'Invalid role';
233                                 };
234                         }); // foreach
235                 }; // verifyRoles
236                 
237                 /**
238                  * Builds and returns an array of promises to invoke the 
239                  * searchUsers service for each unique Org User UID in the input.
240                  * Reads and writes scope variable uploadFile, which must be sorted by Org User UID.
241                  * The promise function writes to closure variable userCheckResult
242                  */
243                 $scope.buildUserChecks = () => {
244                         if (debug)
245                                 $log.debug('BulkUserModalCtrl::buildUserChecks: uploadFile length is ' + $scope.uploadFile.length);
246                         userCheckResult = [];
247                         let promises = [];
248                         let prevRow = null;
249                         $scope.uploadFile.forEach(function (uploadRow) {
250                                 if (uploadRow.status) {
251                                         if (debug)
252                                                 $log.debug('BulkUserModalCtrl::buildUserChecks: skip row ' + uploadRow.line);
253                                         return;
254                                 };
255                                 // detect repeated UIDs
256                                 if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {
257                                         if (debug)
258                                                 $log.debug('BulkUserModalCtrl::buildUserChecks: create request for orgUserId ' + uploadRow.orgUserId);
259                                         let userPromise = usersService.searchUsers(uploadRow.orgUserId).promise().then( (usersList) => {
260                                                 if (typeof usersList[0] !== "undefined") {
261                                                         userCheckResult.push({ 
262                                                                 orgUserId:    usersList[0].orgUserId,
263                                                                 firstName: usersList[0].firstName,
264                                                                 lastName:  usersList[0].lastName,
265                                                                 jobTitle:  usersList[0].jobTitle
266                                                         });
267                                                 }
268                                                 else {
269                                                         // User not found.
270                                                         if (debug)
271                                                                 $log.debug('BulkUserModalCtrl::buildUserChecks: searchUsers returned null');
272                                                 }
273                                         }, function(error){
274                                                 $log.error('BulkUserModalCtrl::buildUserChecks: searchUsers failed ' + JSON.stringify(error));
275                                         }); 
276                                         promises.push(userPromise);
277                                 }
278                                 else {
279                                         if (debug)
280                                                 $log.debug('BulkUserModalCtrl::buildUserChecks: skip repeated orgUserId ' + uploadRow.orgUserId);                                       
281                                 }
282                                 prevRow = uploadRow;
283                         }); // foreach
284                         return promises;
285                 }; // buildUserChecks
286                 
287                 /**
288                  * Evaluates the result set returned by the user service to set
289                  * the uploadFile array element status if the user was not found.
290                  * Reads and writes scope variable uploadFile.
291                  * Reads closure variable userCheckResult.
292                  */
293                 $scope.evalUserCheckResults = () => {
294                         if (debug)
295                                 $log.debug('BulkUserModalCtrl::evalUserCheckResult: uploadFile length is ' + $scope.uploadFile.length);
296                         $scope.uploadFile.forEach(function (uploadRow) {
297                                 if (uploadRow.status) {
298                                         if (debug)
299                                                 $log.debug('BulkUserModalCtrl::evalUserCheckResults: skip row ' + uploadRow.line);
300                                         return;
301                                 };
302                                 let foundorgUserId = false;
303                                 userCheckResult.forEach(function(userItem) {
304                                         if (uploadRow.orgUserId.toLowerCase() === userItem.orgUserId.toLowerCase()) {
305                                                 if (debug)
306                                                         $log.debug('BulkUserModalCtrl::evalUserCheckResults: found orgUserId ' + uploadRow.orgUserId);
307                                                 foundorgUserId=true;
308                                         };
309                                 });
310                                 if (!foundorgUserId) {
311                                         if (debug)
312                                                 $log.debug('BulkUserModalCtrl::evalUserCheckResults: NO match on orgUserId ' + uploadRow.orgUserId);
313                                         uploadRow.status = 'Invalid orgUserId';
314                                 }
315                         }); // foreach
316                 }; // evalUserCheckResults
317
318             /**
319                  * Builds and returns an array of promises to invoke the getUserAppRoles
320                  * service for each unique Org User in the input file.
321                  * Each promise creates an update to be sent to the remote application
322                  * with all role names.
323                  * Reads scope variable uploadFile, which must be sorted by Org User.
324                  * The promise function writes to closure variable appUserRolesRequest
325                  */
326                 $scope.buildAppRoleChecks = () => {
327                         if (debug)
328                                 $log.debug('BulkUserModalCtrl::buildAppRoleChecks: uploadFile length is ' + $scope.uploadFile.length); 
329                         appUserRolesRequest = [];
330                         let appId = $scope.selectedApplication.id;
331                         let promises = [];
332                         let prevRow = null;
333                         $scope.uploadFile.forEach( function (uploadRow) {
334                                 if (uploadRow.status) {
335                                         if (debug)
336                                                 $log.debug('BulkUserModalCtrl::buildAppRoleChecks: skip row ' + uploadRow.line);
337                                         return;
338                                 }
339                                 // Because the input is sorted, generate only one request for each Org User
340                                 if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) {
341                                  if (debug)
342                                          $log.debug('BulkUserModalCtrl::buildAppRoleChecks: create request for orgUserId ' + uploadRow.orgUserId);
343                                  let appPromise = usersService.getUserAppRoles(appId, uploadRow.orgUserId,true).promise().then( (userAppRolesResult) => {
344                                          // Reply for unknown user has all defined roles with isApplied=false on each.  
345                                          if (typeof userAppRolesResult[0] !== "undefined") {
346                                                  if (debug)
347                                                          $log.debug('BulkUserModalCtrl::buildAppRoleChecks: adding result ' 
348                                                                          + JSON.stringify(userAppRolesResult));
349                                                  appUserRolesRequest.push({
350                                                          orgUserId: uploadRow.orgUserId,
351                                                          userAppRoles: userAppRolesResult                                                        
352                                                  });
353                                          } else {
354                                                  $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles returned ' + JSON.stringify(userAppRolesResult));
355                                          };
356                                  }, function(error){
357                                          $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles failed ', error);
358                                  });
359                                  promises.push(appPromise);
360                                 } else {
361                                  if (debug)
362                                          $log.debug('BulkUserModalCtrl::buildAppRoleChecks: duplicate orgUserId, skip: '+ uploadRow.orgUserId);
363                          }
364                          prevRow = uploadRow;
365                  }); // foreach
366                         return promises;
367                 }; // buildAppRoleChecks
368                 
369                 /**
370                  * Evaluates the result set returned by the app service and adjusts 
371                  * the list of updates to be sent to the remote application by setting
372                  * isApplied=true for each role name found in the upload file.
373                  * Reads and writes scope variable uploadFile.
374                  * Reads closure variable appUserRolesRequest.
375                  */
376                 $scope.evalAppRoleCheckResults = () => {
377                         if (debug)
378                                 $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: uploadFile length is ' + $scope.uploadFile.length);
379                         $scope.uploadFile.forEach(function (uploadRow) {
380                                 if (uploadRow.status) {
381                                         if (debug)
382                                                 $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: skip row ' + uploadRow.line);
383                                         return;
384                                 }
385                                 // Search for the match in the app-user-roles array
386                                 appUserRolesRequest.forEach( function (appUserRoleObj) {
387                                         if (uploadRow.orgUserId.toLowerCase() === appUserRoleObj.orgUserId.toLowerCase()) {
388                                                 if (debug)
389                                                         $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: match on orgUserId ' + uploadRow.orgUserId);
390                                                 let roles = appUserRoleObj.userAppRoles;
391                                                 roles.forEach(function (appRoleItem) {
392                                                         //if (debug)
393                                                         //      $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: checking uploadRow.role='
394                                                         //                      + uploadRow.role + ', appRoleItem.roleName= ' + appRoleItem.roleName);
395                                                         if (uploadRow.role === appRoleItem.roleName) {
396                                                                 if (appRoleItem.isApplied) {
397                                                                         if (debug)
398                                                                                 $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: existing role ' 
399                                                                                         + appRoleItem.roleName);
400                                                                         uploadRow.status = 'Role exists';
401                                                                 }
402                                                                 else {
403                                                                         if (debug)
404                                                                                 $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: new role ' 
405                                                                                         + appRoleItem.roleName);
406                                                                         // After much back-and-forth I decided a clear indicator
407                                                                         // is better than blank in the table status column.
408                                                                         uploadRow.status = 'OK';
409                                                                         appRoleItem.isApplied = true;
410                                                                 }
411                                                                 // This count is not especially interesting.
412                                                                 // numberUserRolesSucceeded++;
413                                                         }
414                                                 }); // for each role
415                                         }
416                                 }); // for each result          
417                         }); // for each row
418                 }; // evalAppRoleCheckResults
419              
420                 /**
421                  * Sends requests to Portal requesting user role assignment.
422                  * That endpoint handles creation of the user at the remote app if necessary.
423                  * Reads closure variable appUserRolesRequest.
424                  * Invoked by the Next button on the confirmation dialog.
425                  */
426                 $scope.updateDB = () => {
427                         $scope.isProcessing = true;
428                         $scope.progressMsg = 'Sending requests to application..';
429                         if (debug)
430                                 $log.debug('BulkUserModalCtrl::updateDB: request length is ' + appUserRolesRequest.length);
431                         var numberUsersSucceeded = 0;
432                         let promises = [];
433                         appUserRolesRequest.forEach(function(appUserRoleObj) {
434                                 if (debug) 
435                                         $log.debug('BulkUserModalCtrl::updateDB: appUserRoleObj is ' + JSON.stringify(appUserRoleObj));
436                      let updateRequest = {
437                                  orgUserId: appUserRoleObj.orgUserId, 
438                                  appId: $scope.selectedApplication.id, 
439                                  appRoles: appUserRoleObj.userAppRoles
440                      };
441                      if (debug)
442                          $log.debug('BulkUserModalCtrl::updateDB: updateRequest is ' + JSON.stringify(updateRequest));
443                      let updatePromise = usersService.updateUserAppRoles(updateRequest).promise().then(res => {
444                          if (debug)
445                                  $log.debug('BulkUserModalCtrl::updateDB: updated successfully: ' + JSON.stringify(res));
446                          numberUsersSucceeded++;
447                      }).catch(err => {
448                          // What to do if one of many fails??
449                          $log.error('BulkUserModalCtrl::updateDB failed: ', err);
450                          confirmBoxService.showInformation(
451                                          'Failed to update the user application roles. ' +
452                                          'Error: ' + err.status).then(isConfirmed => { });
453                      }).finally( () => {
454                          // $log.debug('BulkUserModalCtrl::updateDB: finally()');
455                      });
456                      promises.push(updatePromise);
457                  }); // for each
458                         
459                  // Run all the promises
460                  $q.all(promises).then(function(){
461                          $scope.isProcessing = false;
462                          confirmBoxService.showInformation('Processed ' + numberUsersSucceeded + ' users.').then(isConfirmed => {
463                                  // Close the upload-confirm dialog
464                                  ngDialog.close();
465                          });
466                  });
467              }; // updateDb
468              
469                 // Sets the variable that hides/reveals the user controls
470                 $scope.step2 = () => {
471                         this.fileSelected = false;
472                         $scope.selectedFile = null;
473                         $scope.fileModel = null;
474                         this.step1 = false;                     
475                 }
476                 
477              // Navigate between dialog screens using step number: 1,2,...
478              $scope.navigateBack = () => {
479                  this.step1 = true;
480                  this.fileSelected = false;
481              };
482              
483              // Opens a dialog to show the data to be uploaded.
484              // Invoked by the upload button on the bulk user dialog.
485              $scope.confirmUpload = () => {
486                 // Start the process
487                 $scope.readValidateFile();
488                 // Dialog shows progress
489                 ngDialog.open({
490                         templateUrl: 'app/views/users/new-user-dialogs/bulk-user.confirm.html',
491                         scope: $scope
492                 });
493              };
494
495              // Invoked by the Cancel button on the confirmation dialog.
496              $scope.cancelUpload = () => {
497                  ngDialog.close();
498              };
499              
500              init();
501         } // constructor
502     } // class
503     BulkUserModalCtrl.$inject = ['$scope', '$log', '$filter', '$q', 'usersService', 'applicationsService', 'confirmBoxService', 'functionalMenuService', 'ngDialog'];    
504     angular.module('ecompApp').controller('BulkUserModalCtrl', BulkUserModalCtrl);
505
506     angular.module('ecompApp').directive('fileChange', ['$parse', function($parse){
507         return {
508                 require: 'ngModel',
509             restrict: 'A',
510             link : function($scope, element, attrs, ngModel) {
511                 var attrHandler = $parse(attrs['fileChange']);
512                 var handler=function(e) {
513                         $scope.$apply(function() {
514                                 attrHandler($scope, { $event:e, files:e.target.files } );
515                                 $scope.selectedFile = e.target.files[0].name;
516                         });
517                 };
518                 element[0].addEventListener('change',handler,false);
519            }
520         }
521     }]);
522
523     angular.module('ecompApp').filter('csvToObj',function() {
524         return function(input) {
525             var result = [];
526             var len, i, line, o;
527                 var lines = input.split('\n');
528             // Need 1-based index below
529             for (len = lines.length, i = 1; i <= len; ++i) {
530                 // Use 0-based index for array
531                 line = lines[i - 1].trim();
532                         if (line.length == 0) {
533                                 // console.log("Skipping blank line");
534                                 result.push({
535                                         line: i,
536                                         orgUserId: '',
537                                         role: '',
538                                         status: 'Blank line'
539                                 });
540                                 continue;
541                         }
542                         o = line.split(',');
543                         if (o.length !== 2) {
544                                 // other lengths not valid for upload
545                                 result.push({
546                                         line: i,
547                                         orgUserId: line,   
548                                         role: '',
549                                         status: 'Failed to find 2 comma-separated values'
550                                 });
551                         }
552                         else {
553                                 // console.log("Valid line: ", val);
554                                 let entry = {
555                                                 line: i,
556                                                 orgUserId: o[0],
557                                                 role: o[1]
558                                                 // leave status undefined, this could be valid.
559                                 };
560                                 if (o[0].toLowerCase() === 'orgUserId') {
561                                         // not valid for upload, so set status
562                                         entry.status = 'Header';
563                                 }
564                                 else if (o[0].trim() == '' || o[1].trim() == '') {
565                                         // defend against line with only a single comma etc.
566                                         entry.status = 'Failed to find 2 non-empty values';                                     
567                                 }
568                                 result.push(entry);
569                         } // len 2
570             } // for
571             return result;
572         };
573     });
574     
575   
576         
577 })();