Skip to content

Commit

Permalink
feat(#9116): update user place field in admin to allow setting multip…
Browse files Browse the repository at this point in the history
…le places (#9128)
  • Loading branch information
Benmuiruri committed Jun 20, 2024
1 parent 020c3c3 commit c7fbcb1
Show file tree
Hide file tree
Showing 23 changed files with 533 additions and 80 deletions.
119 changes: 95 additions & 24 deletions admin/src/js/controllers/edit-user.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const moment = require('moment');
const passwordTester = require('simple-password-tester');
const phoneNumber = require('@medic/phone-number');
const cht = require('@medic/cht-datasource');
const chtDatasource = cht.getDatasource(cht.getRemoteDataContext());
const PASSWORD_MINIMUM_LENGTH = 8;
const PASSWORD_MINIMUM_SCORE = 50;
const USERNAME_ALLOWED_CHARS = /^[a-z0-9_-]+$/;
Expand Down Expand Up @@ -66,6 +68,7 @@ angular
// If $scope.model === {}, we're creating a new user.
return Settings()
.then(settings => {
$scope.permissions = settings.permissions;
$scope.roles = settings.roles;
$scope.allowTokenLogin = allowTokenLogin(settings);
if (!$scope.model) {
Expand All @@ -89,7 +92,7 @@ angular
phone: $scope.model.phone,
// FacilitySelect is what binds to the select, place is there to
// compare to later to see if it's changed once we've run computeFields();
facilitySelect: $scope.model.facility_id,
facilitySelect: $scope.model.facility_id || [],
place: $scope.model.facility_id,
roles: getRoles($scope.model.roles),
// ^ Same with contactSelect vs. contact
Expand All @@ -100,6 +103,23 @@ angular
});
};

const fetchDocsByIds = (ids) => {
return DB()
.allDocs({ keys: ids, include_docs: true });
};

const usersPlaces = (ids) => {
return fetchDocsByIds(ids)
.then((docs) => processDocs(docs))
.then((filteredDocs) => filteredDocs.map((doc) => doc._id));
};

const processDocs = function (result) {
return result.rows
.filter((row) => row.doc && !row.value.deleted)
.map((row) => row.doc);
};

this.setupPromise = determineEditUserModel()
.then(model => {
$scope.editUserModel = model;
Expand All @@ -115,15 +135,16 @@ angular
const personTypes = contactTypes.filter(type => type.person).map(type => type.id);
Select2Search($('#edit-user-profile [name=contactSelect]'), personTypes);
const placeTypes = contactTypes.filter(type => !type.person).map(type => type.id);
Select2Search($('#edit-user-profile [name=facilitySelect]'), placeTypes);
return usersPlaces($scope.editUserModel.facilitySelect).then(facilityIds => {
Select2Search($('#edit-user-profile [name=facilitySelect]'), placeTypes, { initialValue: facilityIds });
});
});

const validateRequired = (fieldName, fieldDisplayName) => {
if (!$scope.editUserModel[fieldName]) {
Translate.fieldIsRequired(fieldDisplayName)
.then(function(value) {
$scope.errors[fieldName] = value;
});
Translate.fieldIsRequired(fieldDisplayName).then(function (value) {
$scope.errors[fieldName] = value;
});
return false;
}
return true;
Expand Down Expand Up @@ -232,6 +253,24 @@ angular
return true;
};

const validatePlacesPermission = () => {
if (!$scope.editUserModel.place || $scope.editUserModel.place.length <= 1) {
return true;
}

const userHasPermission = chtDatasource.v1.hasPermissions(
['can_have_multiple_places'], $scope.editUserModel.roles, $scope.permissions
);

if (!userHasPermission) {
$translate('permission.description.can_have_multiple_places.not_allowed').then(value => {
$scope.errors.multiFacility = value;
});
}
return userHasPermission;
};


const isOnlineUser = (roles) => {
if (!$scope.roles) {
return true;
Expand All @@ -254,24 +293,55 @@ angular
return hasPlace && hasContact;
};

const validateFacilityHierarchy = () => {
const placeIds = $scope.editUserModel.place;

if (!placeIds || placeIds.length === 1) {
return $q.resolve(true);
}

return fetchDocsByIds(placeIds)
.then(result => {
const places = result.rows.map(row => row.doc);
const isSameHierarchy = ContactTypes.isSameContactType(places);

if (!isSameHierarchy) {
$translate('permission.description.can_have_multiple_places.incompatible_place').then(value => {
$scope.errors.multiFacility = value;
});
}
return isSameHierarchy;
})
.catch(err => {
$log.error('Error validating facility hierarchy', err);
return false;
});
};

const validateContactIsInPlace = () => {
const placeId = $scope.editUserModel.place;
const placeIds = $scope.editUserModel.place;
const contactId = $scope.editUserModel.contact;
if (!placeId || !contactId) {
if (!placeIds || !contactId) {
return $q.resolve(true);
}
return DB()
.get(contactId)
.then(function(contact) {
let parent = contact.parent;
let valid = false;
while (parent) {
if (parent._id === placeId) {
valid = true;
break;
}
parent = parent.parent;
}

const getParent = (contactId) => {
return DB().get(contactId).then(contact => contact.parent);
};

const checkParent = (parent, placeIds) => {
if (!parent) {
return false;
}
if (placeIds.includes(parent._id)) {
return true;
}
return checkParent(parent.parent, placeIds);
};

return getParent(contactId)
.then(function (parent) {
const valid = checkParent(parent, placeIds);
if (!valid) {
$translate('configuration.user.place.contact').then(value => {
$scope.errors.contact = value;
Expand Down Expand Up @@ -371,9 +441,8 @@ angular
};

const computeFields = () => {
$scope.editUserModel.place = $(
'#edit-user-profile [name=facilitySelect]'
).val();
const placeValue = $('#edit-user-profile [name=facilitySelect]').val();
$scope.editUserModel.place = Array.isArray(placeValue) && placeValue.length === 0 ? null : placeValue;
$scope.editUserModel.contact = $(
'#edit-user-profile [name=contactSelect]'
).val();
Expand Down Expand Up @@ -449,7 +518,8 @@ angular
validateRole() &&
validateContactAndFacility() &&
validatePasswordForEditUser() &&
validateEmailAddress();
validateEmailAddress() &&
validatePlacesPermission();

if (!synchronousValidations) {
$scope.setError();
Expand All @@ -458,6 +528,7 @@ angular

const asynchronousValidations = $q
.all([
validateFacilityHierarchy(),
validateContactIsInPlace(),
validateTokenLogin(),
])
Expand Down
5 changes: 5 additions & 0 deletions admin/src/js/services/contact-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ angular.module('inboxServices').service('ContactTypes', function(
*/
getPlaceTypes: () => Settings().then(config => contactTypesUtils.getPlaceTypes(config)),

/**
* @returns {boolean} returns whether the provided places have the same contact_type
*/
isSameContactType: (places) => contactTypesUtils.isSameContactType(places),

/**
* Returns a Promise to resolve all the configured person contact types
*/
Expand Down
5 changes: 3 additions & 2 deletions admin/src/js/services/create-user.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
(function () {

'use strict';
const URL = '/api/v2/users';

angular.module('services').factory('CreateUser',
function (
Expand All @@ -20,6 +19,7 @@
* @param {Object} updates Updates you wish to make
*/
const createSingleUser = (updates) => {
const URL = '/api/v3/users';
if (!updates.username) {
return $q.reject('You must provide a username to create a user');
}
Expand All @@ -43,6 +43,7 @@
* @param {Object} data content of the csv file
*/
const createMultipleUsers = (data) => {
const URL = '/api/v2/users';
$log.debug('CreateMultipleUsers', URL, data);

return $http({
Expand All @@ -61,6 +62,6 @@
createMultipleUsers
};
});

}()
);
Loading

0 comments on commit c7fbcb1

Please sign in to comment.