Merge pull request #2308 from FinnStutzenstein/Issue1213
Handle duplicates during user import (fixes #1213)
This commit is contained in:
commit
31e47e0ac5
@ -684,6 +684,16 @@ img {
|
|||||||
background-color: #317796;
|
background-color: #317796;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-entries {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-entries > li {
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.slimDropDown {
|
.slimDropDown {
|
||||||
padding-left: 4px !important;
|
padding-left: 4px !important;
|
||||||
padding-right: 4px !important;
|
padding-right: 4px !important;
|
||||||
|
@ -98,6 +98,9 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
|||||||
resolve: {
|
resolve: {
|
||||||
groups: function(Group) {
|
groups: function(Group) {
|
||||||
return Group.findAll();
|
return Group.findAll();
|
||||||
|
},
|
||||||
|
users: function(User) {
|
||||||
|
return User.findAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -646,10 +649,12 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
|||||||
|
|
||||||
.controller('UserImportCtrl', [
|
.controller('UserImportCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
|
'$q',
|
||||||
'gettext',
|
'gettext',
|
||||||
|
'gettextCatalog',
|
||||||
'User',
|
'User',
|
||||||
'Group',
|
'Group',
|
||||||
function($scope, gettext, User, Group) {
|
function($scope, $q, gettext, gettextCatalog, User, Group) {
|
||||||
// import from textarea
|
// import from textarea
|
||||||
$scope.importByLine = function () {
|
$scope.importByLine = function () {
|
||||||
$scope.usernames = $scope.userlist[0].split("\n");
|
$scope.usernames = $scope.userlist[0].split("\n");
|
||||||
@ -707,9 +712,17 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
|||||||
$scope.pageChanged = function() {
|
$scope.pageChanged = function() {
|
||||||
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
$scope.limitBegin = ($scope.currentPage - 1) * $scope.itemsPerPage;
|
||||||
};
|
};
|
||||||
|
$scope.duplicateActions = [
|
||||||
|
'keep original',
|
||||||
|
'override new',
|
||||||
|
'create duplicate'
|
||||||
|
];
|
||||||
// detect if csv file is loaded
|
// detect if csv file is loaded
|
||||||
$scope.$watch('csv.result', function () {
|
$scope.$watch('csv.result', function () {
|
||||||
|
// All user objects are already loaded via the resolve statement from ui-router.
|
||||||
|
var users = User.getAll();
|
||||||
$scope.users = [];
|
$scope.users = [];
|
||||||
|
$scope.duplicates = 0;
|
||||||
var quotionRe = /^"(.*)"$/;
|
var quotionRe = /^"(.*)"$/;
|
||||||
angular.forEach($scope.csv.result, function (user) {
|
angular.forEach($scope.csv.result, function (user) {
|
||||||
// title
|
// title
|
||||||
@ -735,6 +748,8 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
|||||||
// number
|
// number
|
||||||
if (user.number) {
|
if (user.number) {
|
||||||
user.number = user.number.replace(quotionRe, '$1');
|
user.number = user.number.replace(quotionRe, '$1');
|
||||||
|
} else {
|
||||||
|
user.number = "";
|
||||||
}
|
}
|
||||||
// groups
|
// groups
|
||||||
if (user.groups) {
|
if (user.groups) {
|
||||||
@ -782,20 +797,102 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
|||||||
} else {
|
} else {
|
||||||
user.is_committee = false;
|
user.is_committee = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for duplicates
|
||||||
|
user.duplicate = false;
|
||||||
|
users.forEach(function(user_) {
|
||||||
|
if (user_.first_name == user.first_name &&
|
||||||
|
user_.last_name == user.last_name &&
|
||||||
|
user_.structure_level == user.structure_level) {
|
||||||
|
if (user.duplicate) {
|
||||||
|
// there are multiple duplicates!
|
||||||
|
user.duplicate_info += '\n' + gettextCatalog.getString('There are more than one duplicates of this user!');
|
||||||
|
} else {
|
||||||
|
user.duplicate = true;
|
||||||
|
user.duplicateAction = $scope.duplicateActions[1];
|
||||||
|
user.duplicate_info = '';
|
||||||
|
if (user_.title)
|
||||||
|
user.duplicate_info += user_.title + ' ';
|
||||||
|
if (user_.first_name)
|
||||||
|
user.duplicate_info += user_.first_name;
|
||||||
|
if (user_.first_name && user_.last_name)
|
||||||
|
user.duplicate_info += ' ';
|
||||||
|
if (user_.last_name)
|
||||||
|
user.duplicate_info += user_.last_name;
|
||||||
|
user.duplicate_info += ' (';
|
||||||
|
if (user_.number)
|
||||||
|
user.duplicate_info += gettextCatalog.getString('number') + ': ' + user_.number + ', ';
|
||||||
|
if (user_.structure_level)
|
||||||
|
user.duplicate_info += gettextCatalog.getString('structure level') + ': ' + user_.structure_level + ', ';
|
||||||
|
user.duplicate_info += gettextCatalog.getString('username') + ': ' + user_.username + ') '+
|
||||||
|
gettextCatalog.getString('already exists.');
|
||||||
|
|
||||||
|
$scope.duplicates++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
$scope.users.push(user);
|
$scope.users.push(user);
|
||||||
});
|
});
|
||||||
|
$scope.calcStats();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
$scope.calcStats = function() {
|
||||||
|
// not imported: if importerror or duplicate->keep original
|
||||||
|
$scope.usersWillNotBeImported = 0;
|
||||||
|
// imported: all others
|
||||||
|
$scope.usersWillBeImported = 0;
|
||||||
|
|
||||||
|
$scope.users.forEach(function(user) {
|
||||||
|
if (user.importerror || (user.duplicate && user.duplicateAction == $scope.duplicateActions[0])) {
|
||||||
|
$scope.usersWillNotBeImported++;
|
||||||
|
} else {
|
||||||
|
$scope.usersWillBeImported++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.setGlobalAction = function (action) {
|
||||||
|
$scope.users.forEach(function (user) {
|
||||||
|
if (user.duplicate)
|
||||||
|
user.duplicateAction = action;
|
||||||
|
});
|
||||||
|
$scope.calcStats();
|
||||||
|
};
|
||||||
|
|
||||||
// import from csv file
|
// import from csv file
|
||||||
$scope.import = function () {
|
$scope.import = function () {
|
||||||
$scope.csvImporting = true;
|
$scope.csvImporting = true;
|
||||||
|
var existingUsers = User.getAll();
|
||||||
angular.forEach($scope.users, function (user) {
|
angular.forEach($scope.users, function (user) {
|
||||||
if (!user.importerror) {
|
if (!user.importerror) {
|
||||||
|
// Do nothing on duplicateAction==duplicateActions[0] (keep original)
|
||||||
|
if (user.duplicate && (user.duplicateAction == $scope.duplicateActions[1])) {
|
||||||
|
// delete existing user
|
||||||
|
var deletePromises = [];
|
||||||
|
existingUsers.forEach(function(user_) {
|
||||||
|
if (user_.first_name == user.first_name &&
|
||||||
|
user_.last_name == user.last_name &&
|
||||||
|
user_.structure_level == user.structure_level) {
|
||||||
|
deletePromises.push(User.destroy(user_.id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$q.all(deletePromises).then(function() {
|
||||||
User.create(user).then(
|
User.create(user).then(
|
||||||
function(success) {
|
function(success) {
|
||||||
user.imported = true;
|
user.imported = true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
} else if (!user.duplicate ||
|
||||||
|
(user.duplicateAction == $scope.duplicateActions[2])) {
|
||||||
|
// create user
|
||||||
|
User.create(user).then(
|
||||||
|
function(success) {
|
||||||
|
user.imported = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$scope.csvimported = true;
|
$scope.csvimported = true;
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
<div ng-show="csv.result">
|
<div ng-show="csv.result">
|
||||||
<h3 translate>Preview</h3>
|
<h3 translate>Preview</h3>
|
||||||
<table ng-if="!csvImporting" class="table table-striped table-bordered table-condensed">
|
<table class="table table-striped table-bordered table-condensed">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
@ -95,14 +95,32 @@
|
|||||||
<th translate>Comment
|
<th translate>Comment
|
||||||
<th translate>Is active
|
<th translate>Is active
|
||||||
<th translate>Is committee</th>
|
<th translate>Is committee</th>
|
||||||
|
<th ng-if="duplicates > 0" translate>
|
||||||
|
<i class="fa fa-exclamation-triangle text-danger"></i>
|
||||||
|
<strong class="text-danger" ng-if="duplicates == 1">1 Duplicate</strong>
|
||||||
|
<strong class="text-danger" ng-if="duplicates > 1">{{ duplicates }} Duplicates</strong>
|
||||||
|
|
||||||
|
<div uib-dropdown>
|
||||||
|
<button type="button" class="btn btn-default btn-danger btn-sm" uib-dropdown-toggle>
|
||||||
|
<translate>Set global action</translate>
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-entries">
|
||||||
|
<li role="menuitem" ng-repeat="action in duplicateActions" class="pointer"
|
||||||
|
ng-click="setGlobalAction(action)">
|
||||||
|
<translate>{{ action }}</translate>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="user in users | limitTo : itemsPerPage : limitBegin">
|
<tr ng-repeat="user in users | limitTo : itemsPerPage : limitBegin">
|
||||||
<td class="minimum"
|
<td class="minimum"
|
||||||
ng-class="{ 'text-danger': user.importerror, 'text-success': user.imported }">
|
ng-class="{ 'text-danger': (user.importerror || user.duplicateAction == duplicateActions[0]), 'text-success': user.imported }">
|
||||||
<span ng-if="user.importerror">
|
<span ng-if="user.importerror || user.duplicateAction == duplicateActions[0]">
|
||||||
<i class="fa fa-exclamation-triangle fa-lg"></i>
|
<i class="fa fa-exclamation-triangle fa-lg"></i>
|
||||||
</span>
|
</span>
|
||||||
<span ng-if="!user.importerror && !user.imported">
|
<span ng-if="!user.importerror && !user.imported && user.duplicateAction != duplicateActions[0]">
|
||||||
<i class="fa fa-check-circle-o fa-lg"></i>
|
<i class="fa fa-check-circle-o fa-lg"></i>
|
||||||
</span>
|
</span>
|
||||||
<span ng-if="user.imported">
|
<span ng-if="user.imported">
|
||||||
@ -130,25 +148,41 @@
|
|||||||
<div ng-repeat="groupname in user.groupnames">
|
<div ng-repeat="groupname in user.groupnames">
|
||||||
{{ groupname | translate }}
|
{{ groupname | translate }}
|
||||||
</div>
|
</div>
|
||||||
|
<td style="max-width: 130px;">
|
||||||
|
<span uib-tooltip="{{ user.comment }}">
|
||||||
|
{{ user.comment | limitTo: 30 }}{{ user.comment.length > 30 ? '...' : '' }}
|
||||||
|
</span>
|
||||||
<td>
|
<td>
|
||||||
{{ user.comment }}
|
<i class="fa pointer" ng-class="user.is_active ? 'fa-check-square-o' : 'fa-square-o'"
|
||||||
|
ng-click="user.is_active = !user.is_active"></i>
|
||||||
<td>
|
<td>
|
||||||
<i ng-if="user.is_active" class="fa fa-check-square-o"></i>
|
<i class="fa pointer" ng-class="user.is_committee ? 'fa-check-square-o' : 'fa-square-o'"
|
||||||
<td>
|
ng-click="user.is_committee = !user.is_committee"></i>
|
||||||
<i ng-if="user.is_committee" class="fa fa-check-square-o"></i>
|
<td ng-if="duplicates > 0">
|
||||||
|
<div ng-if="user.duplicate" uib-tooltip="{{ user.duplicate_info }}" uib-dropdown>
|
||||||
|
<button type="button" class="btn btn-default btn-sm"
|
||||||
|
uib-dropdown-toggle ng-class="user.duplicateAction == duplicateActions[0] ? 'btn-warning' : (user.duplicateAction == duplicateActions[1] ? 'btn-danger' : 'btn-success')">
|
||||||
|
{{ user.duplicateAction }}
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-entries">
|
||||||
|
<li role="menuitem" ng-repeat="action in duplicateActions" class="pointer"
|
||||||
|
ng-click="user.duplicateAction = action; calcStats()">
|
||||||
|
<i class="fa fa-check" ng-if="user.duplicateAction == action"></i>
|
||||||
|
<translate>{{ action }}</translate>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</table>
|
</table>
|
||||||
<uib-pagination total-items="users.length" items-per-page="itemsPerPage" ng-model="currentPage" ng-change="pageChanged()"></uib-pagination>
|
<uib-pagination total-items="users.length" items-per-page="itemsPerPage" ng-model="currentPage" ng-change="pageChanged()"></uib-pagination>
|
||||||
|
|
||||||
<div class="text-danger">
|
<div class="text-danger">
|
||||||
<div ng-repeat="user in usersFailed = (users | filter:{importerror:true})"></div>
|
|
||||||
<i class="fa fa-exclamation-triangle"></i>
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
{{ usersFailed.length }}
|
{{ usersWillNotBeImported }}
|
||||||
<translate>participants will be not imported.</translate>
|
<translate>participants will be not imported.</translate>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div ng-repeat="user in usersPassed = (users | filter:{importerror:false})"></div>
|
|
||||||
<i class="fa fa-check-circle-o fa-lg"></i>
|
<i class="fa fa-check-circle-o fa-lg"></i>
|
||||||
{{ users.length - usersFailed.length }}
|
{{ usersWillBeImported }}
|
||||||
<translate>participants will be imported.</translate>
|
<translate>participants will be imported.</translate>
|
||||||
</div>
|
</div>
|
||||||
<div ng-repeat="user in usersImported = (users | filter:{imported:true})"></div>
|
<div ng-repeat="user in usersImported = (users | filter:{imported:true})"></div>
|
||||||
@ -160,11 +194,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
<button ng-if="!csvImporting" ng-click="clear()" class="btn btn-default" translate>
|
<button ng-click="clear()" class="btn btn-default" translate>
|
||||||
Clear preview
|
Clear preview
|
||||||
</button>
|
</button>
|
||||||
<button ng-if="!csvImporting" ng-click="import()" class="btn btn-primary" translate>
|
<button ng-if="!csvImporting" ng-click="import()" class="btn btn-primary" translate>
|
||||||
Import {{ users.length - usersFailed.length }} participants
|
Import {{ usersWillBeImported }} participants
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
|
Loading…
Reference in New Issue
Block a user