Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#6543): only show assigned facilities without children for users with multiple facilities #9094

Merged
merged 15 commits into from
Jun 7, 2024
15 changes: 14 additions & 1 deletion webapp/src/ts/effects/contacts.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,22 @@ export class ContactsEffects {
return this.contactIdToLoad !== id ? Promise.reject({code: 'SELECTED_CONTACT_CHANGED'}) : Promise.resolve();
}

private shouldGetDescendants(contactId, userFacilityId: string[] = []) {
if (!userFacilityId?.length) {
return true;
}

if (userFacilityId.length > 1) {
return true;
}

return userFacilityId[0] !== contactId;
}

private loadChildren(contactId, userFacilityId, trackName) {
const trackPerformance = this.performanceService.track();
const getChildPlaces = userFacilityId !== contactId;
const getChildPlaces = this.shouldGetDescendants(contactId, userFacilityId);

return this.contactViewModelGeneratorService
.loadChildren(this.selectedContact, {getChildPlaces})
.then(children => {
Expand Down
6 changes: 3 additions & 3 deletions webapp/src/ts/modules/contacts/contacts-content.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ export class ContactsContentComponent implements OnInit, OnDestroy {
private getUserFacility() {
this.store.select(Selectors.getUserFacilityId)
.pipe(first(id => id !== null))
.subscribe((userFacilityId) => {
const shouldDisplayHomePlace = userFacilityId &&
.subscribe((userFacilityIds) => {
const shouldDisplayHomePlace = userFacilityIds &&
!this.filters?.search &&
!this.route.snapshot.params.id &&
!this.responsiveService.isMobile();

if (shouldDisplayHomePlace) {
this.contactsActions.selectContact(userFacilityId);
this.contactsActions.selectContact(userFacilityIds[0]);
Benmuiruri marked this conversation as resolved.
Show resolved Hide resolved
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/ts/modules/contacts/contacts.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<mm-navigation></mm-navigation>
<mm-search-bar
*ngIf="useSearchNewDesign"
[showSort]="true"
[showSort]="isAllowedToSort"
[sortDirection]="sortDirection"
[lastVisitedDateExtras]="lastVisitedDateExtras"
(sort)="sort($event)"
Expand Down
96 changes: 63 additions & 33 deletions webapp/src/ts/modules/contacts/contacts.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
visitCountSettings;
defaultSortDirection = 'alpha';
sortDirection = this.defaultSortDirection;
isAllowedToSort = true;
additionalListItem = false;
useSearchNewDesign = true;
enketoEdited?: boolean;
Expand Down Expand Up @@ -100,6 +101,9 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {

this.visitCountSettings = this.UHCSettings.getVisitCountSettings(settings);
this.usersHomePlace = homePlaceSummary;
if (this.usersHomePlace && this.usersHomePlace.length > 1) {
this.isAllowedToSort = false;
}
this.lastVisitedDateExtras = viewLastVisitedDate;
this.contactTypes = contactTypes;

Expand Down Expand Up @@ -136,8 +140,8 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
this.contactsActions.removeContactFromList({ _id: change.id });
this.hasContacts = !!this.contactsList.length;
}
if (this.usersHomePlace && change.id === this.usersHomePlace._id) {
this.usersHomePlace = await this.getUserHomePlaceSummary(change.id);
if (this.usersHomePlace?.some(homePlace => homePlace._id === change.id)) {
this.usersHomePlace = await this.getUserHomePlaceSummary();
}
const withIds =
this.isSortedByLastVisited() &&
Expand Down Expand Up @@ -218,31 +222,42 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
);
}

private getUserHomePlaceSummary(homePlaceId?: string) {
private getUserFacilityId(userSettings) {
return Array.isArray(userSettings.facility_id) ? userSettings.facility_id : [userSettings.facility_id];
}

private getUserHomePlaceSummary() {
return this.userSettingsService
.get()
.then((userSettings:any) => {
const facilityId: string = homePlaceId ?? userSettings.facility_id;
if (!facilityId) {
.then((userSettings: any) => {
const facilityId = this.getUserFacilityId(userSettings);

if (!facilityId.length || facilityId.some(id => id === undefined)) {
return;
}


this.globalActions.setUserFacilityId(facilityId);
Benmuiruri marked this conversation as resolved.
Show resolved Hide resolved
return this.getDataRecordsService
.get([ facilityId ])
.then(places => places?.length ? places[0] : undefined);
.get(facilityId)
.then(places => {
const validPlaces = places?.filter(place => place !== undefined);
return validPlaces.length ? validPlaces : undefined;
});
})
.then((summary) => {
if (summary) {
summary.home = true;
.then((homeplaces) => {
if (homeplaces) {
homeplaces.forEach(homeplace => {
homeplace.home = true;
});
}
return summary;
return homeplaces;
});
}

private canViewLastVisitedDate() {
if (this.sessionService.isAdmin()) {
// disable UHC for DB admins
// disable UHC for DB admins
return Promise.resolve(false);
}
return this.authService.has('can_view_last_visited_date');
Expand All @@ -265,13 +280,17 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
return this.contactTypesService.getTypeById(this.contactTypes, typeId);
}

private isPrimaryContact(contact) {
return this.usersHomePlace && this.usersHomePlace.length === 1 && contact.home;
}

private populateContactDetails(contact, type) {
contact.route = 'contacts';
contact.icon = type?.icon;
contact.heading = contact.name || '';
contact.valid = true;
contact.summary = null;
contact.primary = contact.home;
contact.primary = this.isPrimaryContact(contact);
contact.dod = contact.date_of_death;
}

Expand Down Expand Up @@ -333,7 +352,7 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {

if (this.usersHomePlace) {
// backwards compatibility with pre-flexible hierarchy users
const homeType = this.contactTypesService.getTypeId(this.usersHomePlace);
const homeType = this.contactTypesService.getTypeId(this.usersHomePlace[0]);
return this.contactTypesService
.getChildren(homeType)
.then(filterChildPlaces);
Expand Down Expand Up @@ -362,6 +381,13 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
return this.sortDirection === 'last_visited_date';
}

private updateAdditionalListItem(homeIndex) {
this.additionalListItem =
!this.filters.search &&
(this.additionalListItem || !this.appending) &&
homeIndex === -1;
}

private query(opts?) {
const trackPerformance = this.performanceService.track();
const options = Object.assign({ limit: this.PAGE_SIZE }, opts);
Expand Down Expand Up @@ -415,23 +441,27 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
.then(updatedContacts => {
// If you have a home place make sure its at the top
if (this.usersHomePlace) {
const homeIndex = _findIndex(updatedContacts, (contact:any) => {
return contact._id === this.usersHomePlace._id;
});
this.additionalListItem =
!this.filters.search &&
(this.additionalListItem || !this.appending) &&
homeIndex === -1;

if (!this.appending) {
if (homeIndex !== -1) {
// move it to the top
updatedContacts.splice(homeIndex, 1);
updatedContacts.unshift(this.usersHomePlace);
} else if (!this.filters.search) {
updatedContacts.unshift(this.usersHomePlace);
this.usersHomePlace.forEach(homePlace => {
const homeIndex = _findIndex(updatedContacts, (contact: any) => contact._id === homePlace._id);

this.updateAdditionalListItem(homeIndex);

if (!this.appending) {
if (homeIndex !== -1) {
// move it to the top
const [homeContact] = updatedContacts.splice(homeIndex, 1);
updatedContacts.unshift(homeContact);
} else if (!this.filters.search) {
updatedContacts.unshift(homePlace);
}
}
}
});
}

// only show homeplaces facilities for multi-facility users
if (this.usersHomePlace?.length > 1 && !this.filters.search) {
const homePlaceIds = this.usersHomePlace.map(place => place._id);
updatedContacts = updatedContacts.filter(place => homePlaceIds.includes(place._id));
}

updatedContacts = this.formatContacts(updatedContacts);
Expand Down Expand Up @@ -484,7 +514,7 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
this.globalActions.setLeftActionBar({
exportFn: () => this.exportContacts(),
hasResults: this.hasContacts,
userFacilityId: this.usersHomePlace?._id,
userFacilityId: this.usersHomePlace?.[0]?._id,
childPlaces: this.allowedChildPlaces,
});
}
Expand All @@ -497,7 +527,7 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy {
}

this.fastActionList = await this.fastActionButtonService.getContactLeftSideActions({
parentFacilityId: this.usersHomePlace?._id,
parentFacilityId: this.usersHomePlace?.[0]?._id,
childContactTypes: this.allowedChildPlaces,
});
}
Expand Down
28 changes: 26 additions & 2 deletions webapp/tests/karma/ts/effects/contacts.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ describe('Contacts effects', () => {
]]);
});

it('should not load child places for user facility', async () => {
store.overrideSelector(Selectors.getUserFacilityId, 'facility');
it('should not load child places for user with one facility', async () => {
store.overrideSelector(Selectors.getUserFacilityId, ['facility']);
store.refreshState();

contactViewModelGeneratorService.getContact.resolves({ _id: 'facility', doc: { _id: 'facility' } });
Expand All @@ -311,6 +311,30 @@ describe('Contacts effects', () => {
]]);
});

it('should load child places for multi-facility user', async () => {
store.overrideSelector(Selectors.getUserFacilityId, ['facility-1', 'facility-2' ]);
store.refreshState();

contactViewModelGeneratorService.getContact.resolves({ _id: 'facility-1', doc: { _id: 'facility-1' } });
contactViewModelGeneratorService.loadChildren.resolves([
{ type: { id: 'patient' }, contacts: [{ _id: 'person1' }] },
]);

actions$ = of(ContactActionList.selectContact({ id: 'facility-1' }));
await effects.selectContact.toPromise();

expect(contactViewModelGeneratorService.loadChildren.callCount).to.equal(1);
expect(contactViewModelGeneratorService.loadChildren.args[0]).to.deep.equal([
{ _id: 'facility-1', doc: { _id: 'facility-1' } },
{ getChildPlaces: true },
]);
const receiveSelectedContactChildren: any = ContactsActions.prototype.receiveSelectedContactChildren;
expect(receiveSelectedContactChildren.callCount).to.equal(1);
expect(receiveSelectedContactChildren.args[0]).to.deep.equal([[
{ type: { id: 'patient' }, contacts: [{ _id: 'person1' }] },
]]);
});

it('should not receive children if the selected contact changes', async () => {
contactViewModelGeneratorService.getContact.onFirstCall()
.resolves({_id: 'contact1', doc: {_id: 'contact1'}});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ describe('Contacts content component', () => {

it(`should load the user's home place when a param id not set and no search term exists`, fakeAsync(() => {
const selectContact = sinon.stub(ContactsActions.prototype, 'selectContact');
store.overrideSelector(Selectors.getUserFacilityId, 'homeplace');
store.overrideSelector(Selectors.getUserFacilityId, ['homeplace']);
store.overrideSelector(Selectors.getFilters, undefined);
activatedRoute.params = of({});
activatedRoute.snapshot.params = {};
Expand Down
49 changes: 49 additions & 0 deletions webapp/tests/karma/ts/modules/contacts/contacts.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1270,4 +1270,53 @@ describe('Contacts component', () => {
});
});
});

describe('Facility ID', () => {
it('supports user with multi-facility homeplaces', fakeAsync(() => {
sinon.resetHistory();
const multi_facility = [{
_id: 'district-id-1',
name: 'My District 1',
type: 'district_hospital'
},
{
_id: 'district-id-2',
name: 'My District 2',
type: 'district_hospital'
}];

userSettingsService.get.resolves({ facility_id: [multi_facility[0]._id, multi_facility[1]._id] });
getDataRecordsService.get.resolves(multi_facility);

sinon.stub(ContactsActions.prototype, 'updateContactsList');
component.ngOnInit();
flush();
const contacts = component.contactsActions.updateContactsList.args[0][0];

expect(contacts.length).to.equal(2);
expect(contacts[0]._id).to.equal('district-id-2');
expect(contacts[1]._id).to.equal('district-id-1');
expect(contacts[0].home).to.equal(true);
expect(contacts[1].home).to.equal(true);
expect(stopPerformanceTrackStub.args[1][0]).to.deep.equal({
name: 'contact_list:load',
recordApdex: true,
});
}));

it('supports user with one facility homeplace', fakeAsync(() => {
sinon.resetHistory();
sinon.stub(ContactsActions.prototype, 'updateContactsList');
component.ngOnInit();
flush();
const contacts = component.contactsActions.updateContactsList.args[0][0];
expect(contacts.length).to.equal(1);
expect(contacts[0]._id).to.equal('district-id');
expect(contacts[0].home).to.equal(true);
expect(stopPerformanceTrackStub.args[1][0]).to.deep.equal({
name: 'contact_list:load',
recordApdex: true,
});
}));
});
});
Loading