Skip to content

Commit

Permalink
feat(#6543): disable aggregate targets for users with multiple facili…
Browse files Browse the repository at this point in the history
…ty_ids (#9099)
  • Loading branch information
latin-panda authored Jun 6, 2024
1 parent 2fdddd0 commit ce76404
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 38 deletions.
50 changes: 42 additions & 8 deletions tests/e2e/default/targets/target-aggregates.wdio-spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const moment = require('moment');
const _ = require('lodash');
const fs = require('fs');
const uuid = require('uuid').v4;

const utils = require('@utils');
const commonPage = require('@page-objects/default/common/common.wdio.page');
const analyticsPage = require('@page-objects/default/analytics/analytics.wdio.page');
const targetAggregatesPage = require('@page-objects/default/targets/target-aggregates.wdio.page');
const contactsPage = require('@page-objects/default/contacts/contacts.wdio.page.js');
const loginPage = require('@page-objects/default/login/login.wdio.page');
const moment = require('moment');
const _ = require('lodash');
const fs = require('fs');
const placeFactory = require('@factories/cht/contacts/place');
const userFactory = require('@factories/cht/users/users');
const personFactory = require('@factories/cht/contacts/person');
Expand Down Expand Up @@ -70,7 +72,7 @@ const validateCardFields = async (values) => {
}
};

describe.skip('Target aggregates', () => {
describe('Target aggregates', () => {

describe('as a db admin', () => {
before(async () => await loginPage.cookieLogin());
Expand Down Expand Up @@ -139,14 +141,31 @@ describe.skip('Target aggregates', () => {
// next month targets, in case the reporting period switches mid-test
moment().date(10).add(1, 'month').format('YYYY-MM'),
];
const contactWithManyPlaces = personFactory.build({
parent: { _id: parentPlace._id, parent: { _id: parentPlace._id } },
});
const userWithManyPlaces = {
_id: 'org.couchdb.user:offline_many_facilities',
language: 'en',
known: true,
type: 'user-settings',
roles: [ 'chw' ],
facility_id: [ parentPlace._id, otherParentPlace._id ],
contact_id: contactWithManyPlaces._id,
name: 'offline_many_facilities'
};
const userWithManyPlacesPass = uuid();

before(async () => {
const allDocs = [...docs, parentPlace, otherParentPlace];
const allDocs = [ ...docs, parentPlace, otherParentPlace, contactWithManyPlaces, userWithManyPlaces ];
await utils.saveDocs(allDocs);
await utils.createUsers([user]);
await utils.request({
path: `/_users/${userWithManyPlaces._id}`,
method: 'PUT',
body: { ...userWithManyPlaces, password: userWithManyPlacesPass, type: 'user' },
});
await browser.url('/medic/login');
await loginPage.login({ username: user.username, password: user.password });
await commonPage.waitForPageLoaded();
});

const DOCS_TO_KEEP = [
Expand All @@ -159,9 +178,20 @@ describe.skip('Target aggregates', () => {
[/^form:/],
];

afterEach(async () => await utils.revertDb(DOCS_TO_KEEP, true));
afterEach(async () => {
await commonPage.logout();
await utils.revertDb(DOCS_TO_KEEP, true);
});

it('should disable content when user has many facilities associated', async () => {
await loginPage.login({ password: userWithManyPlacesPass, username: userWithManyPlaces.name });
await commonPage.waitForPageLoaded();
await targetAggregatesPage.checkContentDisabled();
});

it('should display no data when no targets are uploaded', async () => {
await loginPage.login({ username: user.username, password: user.password });
await commonPage.waitForPageLoaded();
const targetsConfig = [
{ id: 'not_aggregate', type: 'count', title: generateTitle('my task') },
{ id: 'count_no_goal', type: 'count', title: generateTitle('count no goal'), aggregate: true },
Expand Down Expand Up @@ -203,6 +233,8 @@ describe.skip('Target aggregates', () => {
});

it('should display correct data', async () => {
await loginPage.login({ username: user.username, password: user.password });
await commonPage.waitForPageLoaded();
const targetsConfig = [
{ id: 'count_no_goal', type: 'count', title: generateTitle('count no goal'), aggregate: true },
{ id: 'count_with_goal', type: 'count', title: generateTitle('count with goal'), goal: 20, aggregate: true },
Expand Down Expand Up @@ -316,6 +348,8 @@ describe.skip('Target aggregates', () => {
});

it('should route to contact-detail on list item click and display contact summary target card', async () => {
await loginPage.login({ username: user.username, password: user.password });
await commonPage.waitForPageLoaded();
const targetsConfig = [
{ id: 'a_target', type: 'count', title: generateTitle('what a target!'), aggregate: true },
{ id: 'b_target', type: 'percent', title: generateTitle('the most target'), aggregate: true },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const commonPage = require('@page-objects/default/common/common.wdio.page');
const AGGREGATE_LIST = '#target-aggregates-list';
const loadingStatus = () => $(`${AGGREGATE_LIST} .loading-status`);
const aggregateList = () => $$(`${AGGREGATE_LIST} ul li`);
Expand Down Expand Up @@ -31,6 +32,12 @@ const goToTargetAggregates = async (enabled) => {
await (await $(CONTENT_DISABLED)).waitForDisplayed();
};

const checkContentDisabled = async () => {
await commonPage.goToUrl('/#/analytics/target-aggregates');
await commonPage.waitForPageLoaded();
await (await $(CONTENT_DISABLED)).waitForDisplayed();
};

const getTargetItem = async (target) => {
const item = lineItem(target.id);
await (await item.$('h4')).waitForDisplayed();
Expand Down Expand Up @@ -128,5 +135,6 @@ module.exports = {
getAggregateDetailListElementByIndex,
getAggregateDetailElementInfo,
clickOnTargetAggregateListItem,
checkContentDisabled,
};

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AfterContentChecked, AfterContentInit, Component, Input, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';

@Component({
Expand All @@ -12,8 +12,7 @@ export class AnalyticsFilterComponent implements AfterContentInit, AfterContentC
subscriptions: Subscription = new Subscription();

constructor(
private router: Router,
private route: ActivatedRoute
private route: ActivatedRoute,
) { }

ngAfterContentInit() {
Expand Down
21 changes: 11 additions & 10 deletions webapp/src/ts/services/analytics-modules.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Injectable } from '@angular/core';

import { SettingsService } from '@mm-services/settings.service';
import { AuthService } from '@mm-services/auth.service';
import { TargetAggregatesService } from '@mm-services/target-aggregates.service';

@Injectable({
providedIn: 'root'
})
export class AnalyticsModulesService {
constructor(
private authService:AuthService,
private settingsService:SettingsService,
private settingsService: SettingsService,
private targetAggregatesService: TargetAggregatesService,
) { }

private getTargetsModule(settings) {
Expand All @@ -20,30 +21,30 @@ export class AnalyticsModulesService {
};
}

private getTargetAggregatesModule (settings, canAggregateTargets) {
private getTargetAggregatesModule (settings, isAggregateEnabled) {
return {
id: 'target-aggregates',
label: 'analytics.target.aggregates',
route: ['/', 'analytics', 'target-aggregates'],
available: () => !!(settings.tasks && settings.tasks.targets && canAggregateTargets)
available: () => !!(settings?.tasks?.targets && isAggregateEnabled)
};
}

private getModules(settings, canAggregateTargets) {
private getModules(settings, isAggregateEnabled) {
return [
this.getTargetsModule(settings),
this.getTargetAggregatesModule(settings, canAggregateTargets),
this.getTargetAggregatesModule(settings, isAggregateEnabled),
].filter(module => module.available());
}

get() {
return Promise
.all([
this.settingsService.get(),
this.authService.has('can_aggregate_targets')
this.targetAggregatesService.isEnabled(),
])
.then(([settings, canAggregateTargets]) => {
const modules = this.getModules(settings, canAggregateTargets);
.then(([settings, isAggregateEnabled]) => {
const modules = this.getModules(settings, isAggregateEnabled);
console.debug('AnalyticsModules. Enabled modules: ', modules.map(module => module.label));
return modules;
});
Expand Down
26 changes: 21 additions & 5 deletions webapp/src/ts/services/target-aggregates.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,20 @@ export class TargetAggregatesService {
});
}

private async getHomePlace() {
private async getUserFacilityIds(): Promise<string[] | undefined> {
const { facility_id }:any = await this.userSettingsService.get();
if (!facility_id) {
if (!facility_id?.length) {
return;
}
return Array.isArray(facility_id) ? facility_id : [ facility_id ];
}

const places = await this.getDataRecordsService.get([ facility_id ]);
private async getHomePlace() {
const facilityIds = await this.getUserFacilityIds();
if (!facilityIds?.length) {
return;
}
const places = await this.getDataRecordsService.get(facilityIds);
return places?.length ? places[0] : undefined;
}

Expand Down Expand Up @@ -269,8 +276,17 @@ export class TargetAggregatesService {
});
}

isEnabled() {
return this.authService.has('can_aggregate_targets');
async isEnabled() {
const canSeeAggregates = await this.authService.has('can_aggregate_targets');
if (!canSeeAggregates) {
return false;
}

const facilityIds = await this.getUserFacilityIds();

// When no facilities assigned, this returns true to display an error indicating to assign facilities to the user
// When more than one facility, this returns false to display an error about the module being disabled.
return !facilityIds || facilityIds.length <= 1;
}

getAggregates() {
Expand Down
16 changes: 8 additions & 8 deletions webapp/tests/karma/ts/services/analytics-modules.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ import sinon from 'sinon';
import { expect, assert } from 'chai';

import { AnalyticsModulesService } from '@mm-services/analytics-modules.service';
import { AuthService } from '@mm-services/auth.service';
import { SettingsService } from '@mm-services/settings.service';
import { TargetAggregatesService } from '@mm-services/target-aggregates.service';

describe('AnalyticsModulesService', () => {
let service: AnalyticsModulesService;
let authService;
let targetAggregatesService;
let settingsService;

beforeEach(() => {
authService = { has: sinon.stub() };
targetAggregatesService = { isEnabled: sinon.stub() };
settingsService = { get: sinon.stub() };

TestBed.configureTestingModule({
providers: [
{ provide: AuthService, useValue: authService },
{ provide: TargetAggregatesService, useValue: targetAggregatesService },
{ provide: SettingsService, useValue: settingsService },
]
],
});

service = TestBed.inject(AnalyticsModulesService);
Expand All @@ -31,7 +31,7 @@ describe('AnalyticsModulesService', () => {

it('should throw an error when settings fails', () => {
settingsService.get.rejects({ some: 'err' });
authService.has.resolves(true);
targetAggregatesService.isEnabled.resolves(true);

return service
.get()
Expand All @@ -41,7 +41,7 @@ describe('AnalyticsModulesService', () => {

it('should enable targets when configured', () => {
settingsService.get.resolves({ tasks: { targets: { enabled: true } } });
authService.has.resolves(false);
targetAggregatesService.isEnabled.resolves(false);

return service
.get()
Expand All @@ -57,7 +57,7 @@ describe('AnalyticsModulesService', () => {

it('should enable target aggregates when configured', () => {
settingsService.get.resolves({ tasks: { targets: { enabled: true } } });
authService.has.resolves(true);
targetAggregatesService.isEnabled.resolves(true);

return service
.get()
Expand Down
34 changes: 30 additions & 4 deletions webapp/tests/karma/ts/services/target-aggregates.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,48 @@ describe('TargetAggregatesService', () => {
});

describe('isEnabled', () => {
it('should return false when user does not have permission', async () => {
authService.has.resolves(false);
it('should return true when user has permission and user has one facility assigned as array', async () => {
authService.has.resolves(true);
userSettingsService.get.resolves({ facility_id: [ 'facility-1' ] });

const result = await service.isEnabled();

expect(result).to.equal(false);
expect(result).to.equal(true);
expect(authService.has.callCount).to.equal(1);
expect(authService.has.args[0]).to.deep.equal(['can_aggregate_targets']);
expect(userSettingsService.get.calledOnce).to.be.true;
});

it('should return true when user has permission', async () => {
it('should return true when user has permission and user has one facility assigned as string', async () => {
authService.has.resolves(true);
userSettingsService.get.resolves({ facility_id: 'facility-1' });

const result = await service.isEnabled();

expect(result).to.equal(true);
expect(authService.has.callCount).to.equal(1);
expect(authService.has.args[0]).to.deep.equal(['can_aggregate_targets']);
expect(userSettingsService.get.calledOnce).to.be.true;
});

it('should return false when user does not have permission', async () => {
authService.has.resolves(false);
userSettingsService.get.resolves({ facility_id: [ 'facility-1' ] });

const result = await service.isEnabled();

expect(result).to.equal(false);
expect(userSettingsService.get.notCalled).to.be.true;
});

it('should return false when user has more than one facility assigned', async () => {
authService.has.resolves(true);
userSettingsService.get.resolves({ facility_id: [ 'facility-1', 'facility-2' ] });

const result = await service.isEnabled();

expect(result).to.equal(false);
expect(userSettingsService.get.calledOnce).to.be.true;
});
});

Expand Down

0 comments on commit ce76404

Please sign in to comment.