From b8750edfaeaf64a1c04e69457715fb1e37efe989 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:13:14 +0700 Subject: [PATCH 01/13] ugly code request to get place's children --- .../search/src/generate-search-requests.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/shared-libs/search/src/generate-search-requests.js b/shared-libs/search/src/generate-search-requests.js index 894c70e002b..5a1469d95dc 100644 --- a/shared-libs/search/src/generate-search-requests.js +++ b/shared-libs/search/src/generate-search-requests.js @@ -121,6 +121,14 @@ const subjectRequest = function(filters) { return getRequestWithMappedKeys('medic-client/reports_by_subject', subjectIds); }; +const getContactParentRequest = function(filters) { + if (!filters.parent) { + return; + } + + return getRequestWithMappedKeys('medic-client/contacts_by_parent', filters.parents, getKeysArray); +}; + const contactTypeRequest = function(filters, sortByLastVisitedDate) { if (!filters.types) { return; @@ -196,12 +204,26 @@ const requestBuilders = { contacts: function(filters, extensions) { const shouldSortByLastVisitedDate = module.exports.shouldSortByLastVisitedDate(extensions); + + if (filters) { + filters.parents = [ '17f1df8a-b096-4d1e-823d-2723bf7d24ee' ]; + } + + const typeRequest = contactTypeRequest(filters, shouldSortByLastVisitedDate); const hasTypeRequest = typeRequest && typeRequest.params.keys.length; const freetextRequests = freetextRequest(filters, 'medic-client/contacts_by_freetext'); const hasFreetextRequests = freetextRequests && freetextRequests.length; + + + + const parentContactRequest = getContactParentRequest(filters); + + + + if (hasTypeRequest && hasFreetextRequests) { const makeCombinedParams = function(freetextRequest, typeKey) { @@ -235,7 +257,7 @@ const requestBuilders = { return freetextRequests.map(_.partial(makeCombinedRequest, typeRequest, _)); } - let requests = [ freetextRequests, typeRequest ]; + let requests = [ freetextRequests, typeRequest, parentContactRequest ]; requests = _.compact(_.flatten(requests)); if (!requests.length) { @@ -247,6 +269,7 @@ const requestBuilders = { // result and we'll need it later for sorting requests.push(sortByLastVisitedDate()); } + console.warn('search requests -> ', requests); return requests; } }; From 6255b30d97fbafe3eeca6da89be88468f6e7c772 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:30:47 +0700 Subject: [PATCH 02/13] ugly code request to get place's children --- .../src/ts/services/select2-search.service.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/webapp/src/ts/services/select2-search.service.ts b/webapp/src/ts/services/select2-search.service.ts index 80277f2d0a6..9163d5a9243 100644 --- a/webapp/src/ts/services/select2-search.service.ts +++ b/webapp/src/ts/services/select2-search.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { sortBy as _sortBy } from 'lodash-es'; +import { Store } from '@ngrx/store'; import * as phoneNumber from '@medic/phone-number'; import { FormatProvider } from '@mm-providers/format.provider'; @@ -9,13 +11,17 @@ import { SessionService } from '@mm-services/session.service'; import { SettingsService } from '@mm-services/settings.service'; import { ContactMutedService } from '@mm-services/contact-muted.service'; import { TranslateService } from '@mm-services/translate.service'; +import { Selectors } from '@mm-selectors/index'; @Injectable({ providedIn: 'root' }) export class Select2SearchService { + private currentTab; constructor( + private store: Store, + private route: ActivatedRoute, private formatProvider: FormatProvider, private translateService: TranslateService, private lineageModelGeneratorService: LineageModelGeneratorService, @@ -23,7 +29,9 @@ export class Select2SearchService { private sessionService: SessionService, private settingsService: SettingsService, private contactMutedService: ContactMutedService - ) { } + ) { + this.subscribeToStore(); + } private defaultTemplateResult(row) { if (!row.doc) { @@ -56,7 +64,8 @@ export class Select2SearchService { const skip = ((params.data.page || 1) - 1) * options.pageSize; const filters = { types: { selected: types }, - search: params.data.q + search: params.data.q, + ...this.getFilterContactByParent() }; const searchOptions = { limit: options.pageSize, @@ -195,6 +204,22 @@ export class Select2SearchService { } } + private subscribeToStore() { + this.store + .select(Selectors.getCurrentTab) + .subscribe(currentTab => this.currentTab = currentTab); + } + + private getFilterContactByParent() { + const contactId = this.route?.snapshot?.params?.id; + + if (this.currentTab !== 'contacts' || !contactId) { + return; + } + + return { parents: [ contactId ] }; + } + async init(selectEl, _types, _options:any = {}) { const settings = await this.settingsService.get(); const types = Array.isArray(_types) ? _types : [ _types ]; From 57560b5af3bc0f8a6b20d028c658d2dd19c6fd83 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:33:41 +0700 Subject: [PATCH 03/13] pretty code now --- .../search/src/generate-search-requests.js | 106 +++++++++--------- .../src/js/enketo/widgets/db-object-widget.js | 3 +- .../src/ts/services/select2-search.service.ts | 29 ++--- 3 files changed, 63 insertions(+), 75 deletions(-) diff --git a/shared-libs/search/src/generate-search-requests.js b/shared-libs/search/src/generate-search-requests.js index 5a1469d95dc..1c9e00734a1 100644 --- a/shared-libs/search/src/generate-search-requests.js +++ b/shared-libs/search/src/generate-search-requests.js @@ -121,12 +121,18 @@ const subjectRequest = function(filters) { return getRequestWithMappedKeys('medic-client/reports_by_subject', subjectIds); }; -const getContactParentRequest = function(filters) { +const getContactsByParentRequest = function(filters) { if (!filters.parent) { return; } - return getRequestWithMappedKeys('medic-client/contacts_by_parent', filters.parents, getKeysArray); + const types = filters?.types?.selected; + return { + view: 'medic-client/contacts_by_parent', + params: { + keys: types ? types.map(type => ([ filters.parent, type ])) : [ filters.parent ], + }, + }; }; const contactTypeRequest = function(filters, sortByLastVisitedDate) { @@ -183,6 +189,34 @@ const sortByLastVisitedDate = function() { }; }; +const makeCombinedParams = function(freetextRequest, typeKey) { + const type = typeKey[0]; + const params = {}; + if (freetextRequest.key) { + params.key = [ type, freetextRequest.params.key[0] ]; + } else { + params.startkey = [ type, freetextRequest.params.startkey[0] ]; + params.endkey = [ type, freetextRequest.params.endkey[0] ]; + } + return params; +}; + +const getContactsByTypeAndFreetextRequest = function(typeRequests, freetextRequest) { + const result = { + view: 'medic-client/contacts_by_type_freetext', + union: typeRequests.params.keys.length > 1 + }; + + if (result.union) { + result.paramSets = + typeRequests.params.keys.map(_.partial(makeCombinedParams, freetextRequest, _)); + return result; + } + + result.params = makeCombinedParams(freetextRequest, typeRequests.params.keys[0]); + return result; +}; + const requestBuilders = { reports: function(filters) { let requests = [ @@ -204,72 +238,32 @@ const requestBuilders = { contacts: function(filters, extensions) { const shouldSortByLastVisitedDate = module.exports.shouldSortByLastVisitedDate(extensions); - - if (filters) { - filters.parents = [ '17f1df8a-b096-4d1e-823d-2723bf7d24ee' ]; - } - - - const typeRequest = contactTypeRequest(filters, shouldSortByLastVisitedDate); - const hasTypeRequest = typeRequest && typeRequest.params.keys.length; - const freetextRequests = freetextRequest(filters, 'medic-client/contacts_by_freetext'); - const hasFreetextRequests = freetextRequests && freetextRequests.length; - - - - - const parentContactRequest = getContactParentRequest(filters); - - - - - if (hasTypeRequest && hasFreetextRequests) { - - const makeCombinedParams = function(freetextRequest, typeKey) { - const type = typeKey[0]; - const params = {}; - if (freetextRequest.key) { - params.key = [ type, freetextRequest.params.key[0] ]; - } else { - params.startkey = [ type, freetextRequest.params.startkey[0] ]; - params.endkey = [ type, freetextRequest.params.endkey[0] ]; - } - return params; - }; - - const makeCombinedRequest = function(typeRequests, freetextRequest) { - const result = { - view: 'medic-client/contacts_by_type_freetext', - union: typeRequests.params.keys.length > 1 - }; - - if (result.union) { - result.paramSets = - typeRequests.params.keys.map(_.partial(makeCombinedParams, freetextRequest, _)); - return result; - } - - result.params = makeCombinedParams(freetextRequest, typeRequests.params.keys[0]); - return result; - }; + const contactsByParentRequest = getContactsByParentRequest(filters); + const typeRequest = contactTypeRequest(filters, shouldSortByLastVisitedDate); + const hasTypeRequest = typeRequest?.params.keys.length; - return freetextRequests.map(_.partial(makeCombinedRequest, typeRequest, _)); + if (contactsByParentRequest && hasTypeRequest && !freetextRequests?.length) { + // The request's keys already have the type included. + return [ contactsByParentRequest ]; } - let requests = [ freetextRequests, typeRequest, parentContactRequest ]; - requests = _.compact(_.flatten(requests)); + if (hasTypeRequest && freetextRequests?.length) { + const combinedRequests = freetextRequests.map(_.partial(getContactsByTypeAndFreetextRequest, typeRequest, _)); + contactsByParentRequest && combinedRequests.unshift(contactsByParentRequest); + return combinedRequests; + } + const requests = _.compact(_.flatten([ freetextRequests, typeRequest ])); if (!requests.length) { requests.push(defaultContactRequest()); } if (shouldSortByLastVisitedDate) { - // Always push this last, search:getIntersection uses the last request - // result and we'll need it later for sorting + // Always push this last, search:getIntersection uses the last request's result, we'll need it later for sorting. requests.push(sortByLastVisitedDate()); } - console.warn('search requests -> ', requests); + return requests; } }; diff --git a/webapp/src/js/enketo/widgets/db-object-widget.js b/webapp/src/js/enketo/widgets/db-object-widget.js index ad566b1e4a9..07a70acd1a7 100644 --- a/webapp/src/js/enketo/widgets/db-object-widget.js +++ b/webapp/src/js/enketo/widgets/db-object-widget.js @@ -55,7 +55,8 @@ const construct = ( element ) => { } const contactTypes = getContactTypes($question, $textInput); const allowNew = $question.hasClass('or-appearance-allow-new'); - Select2Search.init($selectInput, contactTypes, { allowNew }).then(function() { + const filterByParent = $question.hasClass('or-appearance-with-same-parent'); + Select2Search.init($selectInput, contactTypes, { allowNew, filterByParent }).then(function() { // select2 doesn't understand readonly $selectInput.prop('disabled', $textInput.prop('readonly')); }); diff --git a/webapp/src/ts/services/select2-search.service.ts b/webapp/src/ts/services/select2-search.service.ts index 9163d5a9243..3d2f2958a98 100644 --- a/webapp/src/ts/services/select2-search.service.ts +++ b/webapp/src/ts/services/select2-search.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { sortBy as _sortBy } from 'lodash-es'; -import { Store } from '@ngrx/store'; import * as phoneNumber from '@medic/phone-number'; import { FormatProvider } from '@mm-providers/format.provider'; @@ -11,16 +10,13 @@ import { SessionService } from '@mm-services/session.service'; import { SettingsService } from '@mm-services/settings.service'; import { ContactMutedService } from '@mm-services/contact-muted.service'; import { TranslateService } from '@mm-services/translate.service'; -import { Selectors } from '@mm-selectors/index'; @Injectable({ providedIn: 'root' }) export class Select2SearchService { - private currentTab; constructor( - private store: Store, private route: ActivatedRoute, private formatProvider: FormatProvider, private translateService: TranslateService, @@ -29,9 +25,7 @@ export class Select2SearchService { private sessionService: SessionService, private settingsService: SettingsService, private contactMutedService: ContactMutedService - ) { - this.subscribeToStore(); - } + ) { } private defaultTemplateResult(row) { if (!row.doc) { @@ -65,7 +59,7 @@ export class Select2SearchService { const filters = { types: { selected: types }, search: params.data.q, - ...this.getFilterContactByParent() + parent: options.filterByParent && this.getContactId(), }; const searchOptions = { limit: options.pageSize, @@ -204,20 +198,18 @@ export class Select2SearchService { } } - private subscribeToStore() { - this.store - .select(Selectors.getCurrentTab) - .subscribe(currentTab => this.currentTab = currentTab); - } - - private getFilterContactByParent() { - const contactId = this.route?.snapshot?.params?.id; + private getContactId() { + let activeRoute = this.route.firstChild; + while (activeRoute?.firstChild) { + activeRoute = activeRoute.firstChild; + } - if (this.currentTab !== 'contacts' || !contactId) { + const contactId = activeRoute?.snapshot?.params?.id; + if (!contactId) { return; } - return { parents: [ contactId ] }; + return contactId; } async init(selectEl, _types, _options:any = {}) { @@ -229,6 +221,7 @@ export class Select2SearchService { initialValue: _options.initialValue || selectEl.val(), sendMessageExtras: _options.sendMessageExtras || this.defaultSendMessageExtras, allowNew: _options.allowNew || false, + filterByParent: _options.filterByParent || false, pageSize: _options.pageSize || 20, tags: _options.tags || false, templateResult: _options.templateResult || this.defaultTemplateResult.bind(this) From 3367b8092e2b88ef8506ca9f8557f91115461f3c Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Thu, 14 Dec 2023 20:39:43 +0700 Subject: [PATCH 04/13] coverage --- .../search/src/generate-search-requests.js | 2 +- .../search/test/generate-search-requests.js | 57 +++++++++++++++ .../src/ts/services/select2-search.service.ts | 8 ++- .../services/select2-search.service.spec.ts | 69 +++++++++++++++++-- 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/shared-libs/search/src/generate-search-requests.js b/shared-libs/search/src/generate-search-requests.js index 1c9e00734a1..b67517f413f 100644 --- a/shared-libs/search/src/generate-search-requests.js +++ b/shared-libs/search/src/generate-search-requests.js @@ -254,7 +254,7 @@ const requestBuilders = { return combinedRequests; } - const requests = _.compact(_.flatten([ freetextRequests, typeRequest ])); + const requests = _.compact(_.flatten([ freetextRequests, typeRequest, contactsByParentRequest ])); if (!requests.length) { requests.push(defaultContactRequest()); } diff --git a/shared-libs/search/test/generate-search-requests.js b/shared-libs/search/test/generate-search-requests.js index 792c96744f7..38a61fea8e1 100644 --- a/shared-libs/search/test/generate-search-requests.js +++ b/shared-libs/search/test/generate-search-requests.js @@ -168,6 +168,63 @@ describe('GenerateSearchRequests service', function() { }); }); + it('creates request to filter contacts by parent when contact ID and types are provided', function() { + const filters = { + types: { + selected: [ 'person' ], + }, + parent: 'S-123', + }; + + const result = service('contacts', filters); + + chai.expect(result.length).to.equal(1); + chai.expect(result[0]).to.deep.equal({ + view: 'medic-client/contacts_by_parent', + params: { + keys: [ [ 'S-123', 'person' ] ], + }, + }); + }); + + it('creates request to filter contacts by parent and freetext', function() { + const filters = { + types: { selected: [ 'person' ] }, + search: 'someth', + parent: 'S-123', + }; + + const result = service('contacts', filters); + + chai.expect(result.length).to.equal(2); + chai.expect(result[0]).to.deep.equal({ + view: 'medic-client/contacts_by_parent', + params: { + keys: [ [ 'S-123', 'person' ] ], + }, + }); + chai.expect(result[1]).to.deep.equal({ + view: 'medic-client/contacts_by_type_freetext', + union: false, + params: { + endkey: [ 'person', 'someth\ufff0' ], + startkey: [ 'person', 'someth' ], + }, + }); + }); + + it('creates request to filter contacts by parent when types are not provided', function() { + const filters = { parent: 'S-123' }; + + const result = service('contacts', filters); + + chai.expect(result.length).to.equal(1); + chai.expect(result[0]).to.deep.equal({ + view: 'medic-client/contacts_by_parent', + params: { keys: [ 'S-123' ] }, + }); + }); + it('creates unfiltered contacts request for types filter when all options are selected', function() { const filters = { types: { diff --git a/webapp/src/ts/services/select2-search.service.ts b/webapp/src/ts/services/select2-search.service.ts index 3d2f2958a98..2302a567ec6 100644 --- a/webapp/src/ts/services/select2-search.service.ts +++ b/webapp/src/ts/services/select2-search.service.ts @@ -56,10 +56,16 @@ export class Select2SearchService { private query(params, successCb, failureCb, options, types) { const currentQuery = params.data.q; const skip = ((params.data.page || 1) - 1) * options.pageSize; + + let parent; + if (options.filterByParent) { + parent = this.getContactId(); + } + const filters = { types: { selected: types }, search: params.data.q, - parent: options.filterByParent && this.getContactId(), + parent, }; const searchOptions = { limit: options.pageSize, diff --git a/webapp/tests/karma/ts/services/select2-search.service.spec.ts b/webapp/tests/karma/ts/services/select2-search.service.spec.ts index ee487c273ef..85fa6292721 100644 --- a/webapp/tests/karma/ts/services/select2-search.service.spec.ts +++ b/webapp/tests/karma/ts/services/select2-search.service.spec.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { TestBed, fakeAsync, flush } from '@angular/core/testing'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { ContactMutedService } from '@mm-services/contact-muted.service'; @@ -17,7 +18,8 @@ describe('Select2SearchService', () => { let lineageModelGeneratorService; let sessionService; let settingsService; - + let searchService; + let activatedRoute; let selectEl; let val; let select2Val; @@ -35,7 +37,8 @@ describe('Select2SearchService', () => { contactMutedService = { getMuted: sinon.stub().returns(false) }; - + searchService = { search: sinon.stub().resolves() }; + activatedRoute = { firstChild: {} }; val = ''; select2Val = [{}]; selectEl = { @@ -59,9 +62,10 @@ describe('Select2SearchService', () => { TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), ], providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, { provide: ContactMutedService, useValue: contactMutedService }, { provide: LineageModelGeneratorService, useValue: lineageModelGeneratorService }, - { provide: SearchService, useValue: { } }, + { provide: SearchService, useValue: searchService }, { provide: SessionService, useValue: sessionService }, { provide: SettingsService, useValue: settingsService }, ] @@ -124,5 +128,62 @@ describe('Select2SearchService', () => { expect(selectEl.trigger.callCount).to.equal(1); expect(selectEl.trigger.args[0]).to.deep.equal([ 'change' ]); // the change is notified to the component }); + + it('should set the filter by parent contact and search', fakeAsync(async () => { + activatedRoute.firstChild = { firstChild: { firstChild: { snapshot: { params: { id: 'A-123' } } } } }; + + await service.init(selectEl, [ 'person' ], { initialValue: '', filterByParent: true }); + + const selectConfig = selectEl.select2.args[0][0]; + selectConfig.ajax.transport({ data: { q: 'Eric' } }, () => {}, () => {}); + flush(); + + expect(searchService.search.calledOnce).to.be.true; + expect(searchService.search.args[0][0]).to.equal('contacts'); + expect(searchService.search.args[0][1]).to.deep.equal({ + types: { selected: [ 'person' ] }, + search: 'Eric', + parent: 'A-123' + }); + expect(searchService.search.args[0][2]).to.deep.equal({ limit: 20, skip: 0, hydrateContactNames: true }); + })); + + it('should not set the filter by parent contact when no contact ID', fakeAsync(async () => { + activatedRoute.firstChild = { firstChild: { snapshot: { params: { id: null } } } }; + + await service.init(selectEl, [ 'person' ], { initialValue: '', filterByParent: true }); + + const selectConfig = selectEl.select2.args[0][0]; + selectConfig.ajax.transport({ data: { q: 'Eric' } }, () => {}, () => {}); + flush(); + + expect(searchService.search.calledOnce).to.be.true; + expect(searchService.search.args[0][0]).to.equal('contacts'); + expect(searchService.search.args[0][1]).to.deep.equal({ + types: { selected: [ 'person' ] }, + search: 'Eric', + parent: undefined + }); + expect(searchService.search.args[0][2]).to.deep.equal({ limit: 20, skip: 0, hydrateContactNames: true }); + })); + + it('should not set the filter by parent contact when filterByParent turn off', fakeAsync(async () => { + activatedRoute.firstChild = { firstChild: { firstChild: { snapshot: { params: { id: 'A-123' } } } } }; + + await service.init(selectEl, [ 'person' ], { initialValue: '', filterByParent: false }); + + const selectConfig = selectEl.select2.args[0][0]; + selectConfig.ajax.transport({ data: { q: 'Eric' } }, () => {}, () => {}); + flush(); + + expect(searchService.search.calledOnce).to.be.true; + expect(searchService.search.args[0][0]).to.equal('contacts'); + expect(searchService.search.args[0][1]).to.deep.equal({ + types: { selected: [ 'person' ] }, + search: 'Eric', + parent: undefined + }); + expect(searchService.search.args[0][2]).to.deep.equal({ limit: 20, skip: 0, hydrateContactNames: true }); + })); }); }); From fb4a1a5f46a16ca28410458b9e7f7fce22e86c98 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:48:15 +0700 Subject: [PATCH 05/13] adding type and some clean up --- .../ts/modules/contacts/contacts.component.ts | 6 ++-- .../ts/modules/reports/reports.component.ts | 4 +-- .../contact-view-model-generator.service.ts | 4 +-- webapp/src/ts/services/search.service.ts | 9 +++++- .../src/ts/services/select2-search.service.ts | 30 +++++++------------ .../karma/ts/services/search.service.spec.ts | 8 ++--- .../services/select2-search.service.spec.ts | 3 +- 7 files changed, 31 insertions(+), 33 deletions(-) diff --git a/webapp/src/ts/modules/contacts/contacts.component.ts b/webapp/src/ts/modules/contacts/contacts.component.ts index 85dc4218bbc..66e5a347170 100644 --- a/webapp/src/ts/modules/contacts/contacts.component.ts +++ b/webapp/src/ts/modules/contacts/contacts.component.ts @@ -15,7 +15,7 @@ import { AuthService } from '@mm-services/auth.service'; import { SettingsService } from '@mm-services/settings.service'; import { UHCSettingsService } from '@mm-services/uhc-settings.service'; import { Selectors } from '@mm-selectors/index'; -import { SearchService } from '@mm-services/search.service'; +import { Filter, SearchService } from '@mm-services/search.service'; import { ContactTypesService } from '@mm-services/contact-types.service'; import { RelativeDateService } from '@mm-services/relative-date.service'; import { ScrollLoaderProvider } from '@mm-providers/scroll-loader.provider'; @@ -44,8 +44,8 @@ export class ContactsComponent implements OnInit, AfterViewInit, OnDestroy { error; appending: boolean; hasContacts = true; - filters:any = {}; - defaultFilters:any = {}; + filters: Filter = {}; + defaultFilters: Filter = {}; moreItems; usersHomePlace; contactTypes; diff --git a/webapp/src/ts/modules/reports/reports.component.ts b/webapp/src/ts/modules/reports/reports.component.ts index 68e804f9daf..4f51b3d63db 100644 --- a/webapp/src/ts/modules/reports/reports.component.ts +++ b/webapp/src/ts/modules/reports/reports.component.ts @@ -9,7 +9,7 @@ import { GlobalActions } from '@mm-actions/global'; import { ReportsActions } from '@mm-actions/reports'; import { ServicesActions } from '@mm-actions/services'; import { ChangesService } from '@mm-services/changes.service'; -import { SearchService } from '@mm-services/search.service'; +import { Filter, SearchService } from '@mm-services/search.service'; import { Selectors } from '@mm-selectors/index'; import { AddReadStatusService } from '@mm-services/add-read-status.service'; import { ExportService } from '@mm-services/export.service'; @@ -52,7 +52,7 @@ export class ReportsComponent implements OnInit, AfterViewInit, OnDestroy { loading = true; appending = false; moreItems: boolean; - filters:any = {}; + filters: Filter = {}; hasReports: boolean; selectMode = false; selectModeAvailable = false; diff --git a/webapp/src/ts/services/contact-view-model-generator.service.ts b/webapp/src/ts/services/contact-view-model-generator.service.ts index f68eea09d4e..6021486569a 100644 --- a/webapp/src/ts/services/contact-view-model-generator.service.ts +++ b/webapp/src/ts/services/contact-view-model-generator.service.ts @@ -307,9 +307,9 @@ export class ContactViewModelGeneratorService { contactDocs.forEach((doc) => { subjectIds.push(registrationUtils.getSubjectIds(doc)); }); - const searchOptions = { subjectIds: _flattenDeep(subjectIds) }; + const filter = { subjectIds: _flattenDeep(subjectIds) }; return this.searchService - .search('reports', searchOptions, { include_docs: true }) + .search('reports', filter, { include_docs: true }) .then((reports) => { reports.forEach((report) => { report.valid = !report.errors || !report.errors.length; diff --git a/webapp/src/ts/services/search.service.ts b/webapp/src/ts/services/search.service.ts index 93a86afa20b..900f3eed14c 100644 --- a/webapp/src/ts/services/search.service.ts +++ b/webapp/src/ts/services/search.service.ts @@ -122,7 +122,7 @@ export class SearchService { }); } - search(type, filters, options:any = {}, extensions:any = {}, docIds: any[] | undefined = undefined) { + search(type, filters: Filter, options:any = {}, extensions:any = {}, docIds: any[] | undefined = undefined) { return this.ngZone.runOutsideAngular(() => this._search(type, filters, options, extensions, docIds)); } @@ -201,3 +201,10 @@ export class SearchService { }); } } + +export interface Filter { + types?: { selected: string }; + search?: string; + parent?: string; + subjectIds?: string[]; +} diff --git a/webapp/src/ts/services/select2-search.service.ts b/webapp/src/ts/services/select2-search.service.ts index 2302a567ec6..ad541a9af19 100644 --- a/webapp/src/ts/services/select2-search.service.ts +++ b/webapp/src/ts/services/select2-search.service.ts @@ -5,7 +5,7 @@ import * as phoneNumber from '@medic/phone-number'; import { FormatProvider } from '@mm-providers/format.provider'; import { LineageModelGeneratorService } from '@mm-services/lineage-model-generator.service'; -import { SearchService } from '@mm-services/search.service'; +import { Filter, SearchService } from '@mm-services/search.service'; import { SessionService } from '@mm-services/session.service'; import { SettingsService } from '@mm-services/settings.service'; import { ContactMutedService } from '@mm-services/contact-muted.service'; @@ -55,24 +55,21 @@ export class Select2SearchService { private query(params, successCb, failureCb, options, types) { const currentQuery = params.data.q; - const skip = ((params.data.page || 1) - 1) * options.pageSize; - let parent; - if (options.filterByParent) { - parent = this.getContactId(); - } - - const filters = { - types: { selected: types }, - search: params.data.q, - parent, - }; const searchOptions = { limit: options.pageSize, - skip, + skip: ((params.data.page || 1) - 1) * options.pageSize, hydrateContactNames: true, }; + const filters: Filter = { + types: { selected: types }, + search: params.data.q, + }; + if (options.filterByParent) { + filters.parent = this.getContactId(); + } + this.searchService .search('contacts', filters, searchOptions) .then((documents) => { @@ -210,12 +207,7 @@ export class Select2SearchService { activeRoute = activeRoute.firstChild; } - const contactId = activeRoute?.snapshot?.params?.id; - if (!contactId) { - return; - } - - return contactId; + return activeRoute?.snapshot?.params?.id; } async init(selectEl, _types, _options:any = {}) { diff --git a/webapp/tests/karma/ts/services/search.service.spec.ts b/webapp/tests/karma/ts/services/search.service.spec.ts index 6690a8939cd..884ed26e865 100644 --- a/webapp/tests/karma/ts/services/search.service.spec.ts +++ b/webapp/tests/karma/ts/services/search.service.spec.ts @@ -87,7 +87,7 @@ describe('Search service', () => { .onSecondCall().resolves([ { id: 'b' } ]); let firstReturned = false; - const filters = { foo: 'bar' }; + const filters = { search: 'bar' }; service .search('reports', filters) .then((actual) => { @@ -96,7 +96,7 @@ describe('Search service', () => { }) .catch(err => assert.fail(err)); - filters.foo = 'test'; + filters.search = 'test'; service .search('reports', filters) .then((actual) => { @@ -112,7 +112,7 @@ describe('Search service', () => { .onSecondCall().resolves([ { id: 'b' } ]); let firstReturned = false; service - .search('reports', { freetext: 'first' }) + .search('reports', { search: 'first' }) .then((actual) => { expect(actual).to.deep.equal([ { id: 'a' } ]); firstReturned = true; @@ -120,7 +120,7 @@ describe('Search service', () => { .catch(err => assert.fail(err)); service - .search('reports', { freetext: 'second' }) + .search('reports', { search: 'second' }) .then((actual) => { expect(actual).to.deep.equal([ { id: 'b' } ]); expect(firstReturned).to.equal(true); diff --git a/webapp/tests/karma/ts/services/select2-search.service.spec.ts b/webapp/tests/karma/ts/services/select2-search.service.spec.ts index 85fa6292721..6bafe4d193e 100644 --- a/webapp/tests/karma/ts/services/select2-search.service.spec.ts +++ b/webapp/tests/karma/ts/services/select2-search.service.spec.ts @@ -149,7 +149,7 @@ describe('Select2SearchService', () => { })); it('should not set the filter by parent contact when no contact ID', fakeAsync(async () => { - activatedRoute.firstChild = { firstChild: { snapshot: { params: { id: null } } } }; + activatedRoute.firstChild = { firstChild: { snapshot: { params: undefined } } }; await service.init(selectEl, [ 'person' ], { initialValue: '', filterByParent: true }); @@ -181,7 +181,6 @@ describe('Select2SearchService', () => { expect(searchService.search.args[0][1]).to.deep.equal({ types: { selected: [ 'person' ] }, search: 'Eric', - parent: undefined }); expect(searchService.search.args[0][2]).to.deep.equal({ limit: 20, skip: 0, hydrateContactNames: true }); })); From fb5215bfbe5941cce52d1a215a0285ef9a6b15ca Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Fri, 15 Dec 2023 13:42:35 +0700 Subject: [PATCH 06/13] sonar --- webapp/src/ts/services/select2-search.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webapp/src/ts/services/select2-search.service.ts b/webapp/src/ts/services/select2-search.service.ts index ad541a9af19..ae712910b0a 100644 --- a/webapp/src/ts/services/select2-search.service.ts +++ b/webapp/src/ts/services/select2-search.service.ts @@ -53,12 +53,16 @@ export class Select2SearchService { .map(doc => ({ id: doc._id, doc: doc })); } + private calculateSkip(page: number, pageSize: number): number { + return ((page || 1) - 1) * pageSize; + } + private query(params, successCb, failureCb, options, types) { const currentQuery = params.data.q; const searchOptions = { limit: options.pageSize, - skip: ((params.data.page || 1) - 1) * options.pageSize, + skip: this.calculateSkip(params.data.page, options.pageSize), hydrateContactNames: true, }; From 8ae2c1c345a682d94123af7cea6991b3c0b9d4b4 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:00:25 +0700 Subject: [PATCH 07/13] e2e setup --- .../enketo/db-object-widget.wdio-spec.js | 58 +++++++++++++++++++ .../default/enketo/forms/db-object-widget.xml | 32 ++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/e2e/default/enketo/db-object-widget.wdio-spec.js create mode 100644 tests/e2e/default/enketo/forms/db-object-widget.xml diff --git a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js new file mode 100644 index 00000000000..d1e8d2cef3c --- /dev/null +++ b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js @@ -0,0 +1,58 @@ +const moment = require('moment'); +const fs = require('fs'); + +const utils = require('@utils'); +const commonPage = require('@page-objects/default/common/common.wdio.page'); +const loginPage = require('@page-objects/default/login/login.wdio.page'); +const userFactory = require('@factories/cht/users/users'); +const placeFactory = require('@factories/cht/contacts/place'); +const personFactory = require('@factories/cht/contacts/person'); +const contactPage = require('@page-objects/default/contacts/contacts.wdio.page'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); + +describe('DB Object Widget', () => { + const formId = 'db-object-widget'; + const form = fs.readFileSync(`${__dirname}/forms/${formId}.xml`, 'utf8'); + const formDocument = { + _id: `form:${formId}`, + internalId: formId, + title: `Form ${formId}`, + type: 'form', + context: { person: true, place: true }, + _attachments: { + xml: { + content_type: 'application/octet-stream', + data: Buffer.from(form).toString('base64') + } + } + }; + + const places = placeFactory.generateHierarchy(); + const districtHospital = places.get('district_hospital'); + const area1 = places.get('health_center'); + const area2 = placeFactory.place().build({ + _id: 'area2', + name: 'area 2', + type: 'health_center', + parent: { _id: districtHospital._id } + }); + + const offlineUser = userFactory.build({ place: districtHospital._id, roles: [ 'chw' ] }); + const personArea1 = personFactory.build({ parent: {_id: area1._id, parent: area1.parent} }); + const personArea2 = personFactory.build({name: 'Patricio', parent: {_id: area2._id, parent: area2.parent} }); + + before(async () => { + await utils.saveDocs([ ...places.values(), area2, personArea1, personArea2, formDocument ]); + await utils.createUsers([ offlineUser ]); + await loginPage.login(offlineUser); + }); + + it('should display only the contacts from the parent contact', async () => { + await commonPage.goToPeople(area1._id); + await commonPage.openFastActionReport(formId); + await browser.debug(); + + expect('hola').to.equal('Health facility'); + }); + +}); diff --git a/tests/e2e/default/enketo/forms/db-object-widget.xml b/tests/e2e/default/enketo/forms/db-object-widget.xml new file mode 100644 index 00000000000..8b8a23212a2 --- /dev/null +++ b/tests/e2e/default/enketo/forms/db-object-widget.xml @@ -0,0 +1,32 @@ + + + + DB Object Form + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4af3b055f35b3242d34a448633ec263ad9f41404 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Mon, 18 Dec 2023 14:24:09 +0700 Subject: [PATCH 08/13] e2e no flaky --- .../enketo/db-object-widget.wdio-spec.js | 33 ++++++++++++++----- .../default/enketo/generic-form.wdio.page.js | 23 +++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js index d1e8d2cef3c..42060bead1e 100644 --- a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js +++ b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js @@ -1,14 +1,13 @@ -const moment = require('moment'); const fs = require('fs'); const utils = require('@utils'); -const commonPage = require('@page-objects/default/common/common.wdio.page'); -const loginPage = require('@page-objects/default/login/login.wdio.page'); const userFactory = require('@factories/cht/users/users'); const placeFactory = require('@factories/cht/contacts/place'); const personFactory = require('@factories/cht/contacts/person'); -const contactPage = require('@page-objects/default/contacts/contacts.wdio.page'); +const commonPage = require('@page-objects/default/common/common.wdio.page'); +const loginPage = require('@page-objects/default/login/login.wdio.page'); const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const reportsPage = require('@page-objects/default/reports/reports.wdio.page'); describe('DB Object Widget', () => { const formId = 'db-object-widget'; @@ -38,8 +37,8 @@ describe('DB Object Widget', () => { }); const offlineUser = userFactory.build({ place: districtHospital._id, roles: [ 'chw' ] }); - const personArea1 = personFactory.build({ parent: {_id: area1._id, parent: area1.parent} }); - const personArea2 = personFactory.build({name: 'Patricio', parent: {_id: area2._id, parent: area2.parent} }); + const personArea1 = personFactory.build({ parent: { _id: area1._id, parent: area1.parent } }); + const personArea2 = personFactory.build({ name: 'Patricio', parent: { _id: area2._id, parent: area2.parent } }); before(async () => { await utils.saveDocs([ ...places.values(), area2, personArea1, personArea2, formDocument ]); @@ -50,9 +49,27 @@ describe('DB Object Widget', () => { it('should display only the contacts from the parent contact', async () => { await commonPage.goToPeople(area1._id); await commonPage.openFastActionReport(formId); - await browser.debug(); - expect('hola').to.equal('Health facility'); + const sameParent = await genericForm.getDBObjectWidgetValues('/db_object_form/people/person_test_same_parent'); + expect(sameParent.length).to.equal(1); + expect(sameParent[0].name).to.equal('Mary Smith'); + await sameParent[0].click(); + + const allContacts = await genericForm.getDBObjectWidgetValues('/db_object_form/people/person_test_all'); + expect(allContacts.length).to.equal(3); + expect(allContacts[0].name).to.equal('Mary Smith'); + expect(allContacts[1].name).to.equal('OfflineUser'); + expect(allContacts[2].name).to.equal('Patricio'); + await allContacts[1].click(); + + await genericForm.submitForm(); + await commonPage.waitForPageLoaded(); + await commonPage.goToReports(); + + const firstReport = await reportsPage.getListReportInfo(await reportsPage.firstReport()); + + expect(firstReport.heading).to.equal('OfflineUser'); + expect(firstReport.form).to.equal('Form db-object-widget'); }); }); diff --git a/tests/page-objects/default/enketo/generic-form.wdio.page.js b/tests/page-objects/default/enketo/generic-form.wdio.page.js index 1b61e776d37..9905c242f44 100644 --- a/tests/page-objects/default/enketo/generic-form.wdio.page.js +++ b/tests/page-objects/default/enketo/generic-form.wdio.page.js @@ -107,6 +107,28 @@ const selectYesNoOption = async (selector, value = 'yes') => { return value === 'yes'; }; +const getDBObjectWidgetValues = async (field) => { + const widget = $(`[data-contains-ref-target="${field}"] .selection`); + await (await widget).waitForClickable(); + await (await widget).click(); + + const dropdown = $('.select2-dropdown--below'); + await (await dropdown).waitForDisplayed(); + const firstElement = $('.select2-results__options > li'); + await (await firstElement).waitForClickable(); + + const list = await $$('.select2-results__options > li'); + const contacts = []; + for (const item of list) { + contacts.push({ + name: await (item.$('.name').getText()), + click: () => item.click(), + }); + } + + return contacts; +}; + module.exports = { getFormTitle, getErrorMessage, @@ -125,4 +147,5 @@ module.exports = { currentFormView, formTitle, selectYesNoOption, + getDBObjectWidgetValues, }; From 2bfcd45856a3f1c72bb2bf7e6b84ca370ba2abbe Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:35:09 +0700 Subject: [PATCH 09/13] e2e no flaky take 2 --- .../enketo/db-object-widget.wdio-spec.js | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js index 42060bead1e..36f82321a4f 100644 --- a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js +++ b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js @@ -51,25 +51,32 @@ describe('DB Object Widget', () => { await commonPage.openFastActionReport(formId); const sameParent = await genericForm.getDBObjectWidgetValues('/db_object_form/people/person_test_same_parent'); - expect(sameParent.length).to.equal(1); - expect(sameParent[0].name).to.equal('Mary Smith'); await sameParent[0].click(); + expect(sameParent.length).to.equal(1); + expect(sameParent[0].name).to.equal(personArea1.name); const allContacts = await genericForm.getDBObjectWidgetValues('/db_object_form/people/person_test_all'); + await allContacts[2].click(); expect(allContacts.length).to.equal(3); - expect(allContacts[0].name).to.equal('Mary Smith'); - expect(allContacts[1].name).to.equal('OfflineUser'); - expect(allContacts[2].name).to.equal('Patricio'); - await allContacts[1].click(); + expect(allContacts[0].name).to.equal(personArea1.name); + expect(allContacts[1].name).to.equal(offlineUser.contact.name); + expect(allContacts[2].name).to.equal(personArea2.name); await genericForm.submitForm(); await commonPage.waitForPageLoaded(); await commonPage.goToReports(); const firstReport = await reportsPage.getListReportInfo(await reportsPage.firstReport()); - - expect(firstReport.heading).to.equal('OfflineUser'); + expect(firstReport.heading).to.equal(offlineUser.contact.name); expect(firstReport.form).to.equal('Form db-object-widget'); + + await reportsPage.openReport(firstReport.dataId); + expect(await reportsPage.getReportDetailFieldValueByLabel( + 'report.db-object-widget.people.person_test_same_parent' + )).to.equal(personArea1._id); + expect(await reportsPage.getReportDetailFieldValueByLabel( + 'report.db-object-widget.people.person_test_all' + )).to.equal(personArea2._id); }); }); From 688a035d927673af458a84df3209f08849c9d72f Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:16:47 +0700 Subject: [PATCH 10/13] feedback --- .../search/src/generate-search-requests.js | 4 +++- .../src/ts/services/select2-search.service.ts | 4 ++-- .../services/select2-search.service.spec.ts | 21 ++++++++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/shared-libs/search/src/generate-search-requests.js b/shared-libs/search/src/generate-search-requests.js index b67517f413f..e95da4f46eb 100644 --- a/shared-libs/search/src/generate-search-requests.js +++ b/shared-libs/search/src/generate-search-requests.js @@ -250,7 +250,9 @@ const requestBuilders = { if (hasTypeRequest && freetextRequests?.length) { const combinedRequests = freetextRequests.map(_.partial(getContactsByTypeAndFreetextRequest, typeRequest, _)); - contactsByParentRequest && combinedRequests.unshift(contactsByParentRequest); + if (contactsByParentRequest) { + combinedRequests.unshift(contactsByParentRequest); + } return combinedRequests; } diff --git a/webapp/src/ts/services/select2-search.service.ts b/webapp/src/ts/services/select2-search.service.ts index ae712910b0a..221d43f0fab 100644 --- a/webapp/src/ts/services/select2-search.service.ts +++ b/webapp/src/ts/services/select2-search.service.ts @@ -210,8 +210,8 @@ export class Select2SearchService { while (activeRoute?.firstChild) { activeRoute = activeRoute.firstChild; } - - return activeRoute?.snapshot?.params?.id; + const params = activeRoute?.snapshot?.params; + return params?.parent_id || params?.id; } async init(selectEl, _types, _options:any = {}) { diff --git a/webapp/tests/karma/ts/services/select2-search.service.spec.ts b/webapp/tests/karma/ts/services/select2-search.service.spec.ts index 6bafe4d193e..6930ff5eb67 100644 --- a/webapp/tests/karma/ts/services/select2-search.service.spec.ts +++ b/webapp/tests/karma/ts/services/select2-search.service.spec.ts @@ -129,7 +129,7 @@ describe('Select2SearchService', () => { expect(selectEl.trigger.args[0]).to.deep.equal([ 'change' ]); // the change is notified to the component }); - it('should set the filter by parent contact and search', fakeAsync(async () => { + it('should set the filter by parent contact when app form is opened from contacts tab', fakeAsync(async () => { activatedRoute.firstChild = { firstChild: { firstChild: { snapshot: { params: { id: 'A-123' } } } } }; await service.init(selectEl, [ 'person' ], { initialValue: '', filterByParent: true }); @@ -148,6 +148,25 @@ describe('Select2SearchService', () => { expect(searchService.search.args[0][2]).to.deep.equal({ limit: 20, skip: 0, hydrateContactNames: true }); })); + it('should set the filter by parent contact when contact form is opened from contacts tab', fakeAsync(async () => { + activatedRoute.firstChild = { firstChild: { firstChild: { snapshot: { params: { parent_id: 'A-456' } } } } }; + + await service.init(selectEl, [ 'person' ], { initialValue: '', filterByParent: true }); + + const selectConfig = selectEl.select2.args[0][0]; + selectConfig.ajax.transport({ data: { q: 'Eric' } }, () => {}, () => {}); + flush(); + + expect(searchService.search.calledOnce).to.be.true; + expect(searchService.search.args[0][0]).to.equal('contacts'); + expect(searchService.search.args[0][1]).to.deep.equal({ + types: { selected: [ 'person' ] }, + search: 'Eric', + parent: 'A-456' + }); + expect(searchService.search.args[0][2]).to.deep.equal({ limit: 20, skip: 0, hydrateContactNames: true }); + })); + it('should not set the filter by parent contact when no contact ID', fakeAsync(async () => { activatedRoute.firstChild = { firstChild: { snapshot: { params: undefined } } }; From 95b2057fad8d1f5f20046a67892891847b6b9710 Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:49:17 +0700 Subject: [PATCH 11/13] feedback --- .../enketo/db-object-widget.wdio-spec.js | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js index 36f82321a4f..58bb4a34ce1 100644 --- a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js +++ b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js @@ -9,36 +9,38 @@ const loginPage = require('@page-objects/default/login/login.wdio.page'); const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); const reportsPage = require('@page-objects/default/reports/reports.wdio.page'); -describe('DB Object Widget', () => { - const formId = 'db-object-widget'; - const form = fs.readFileSync(`${__dirname}/forms/${formId}.xml`, 'utf8'); - const formDocument = { - _id: `form:${formId}`, - internalId: formId, - title: `Form ${formId}`, - type: 'form', - context: { person: true, place: true }, - _attachments: { - xml: { - content_type: 'application/octet-stream', - data: Buffer.from(form).toString('base64') - } +// Test Data +const formId = 'db-object-widget'; +const form = fs.readFileSync(`${__dirname}/forms/${formId}.xml`, 'utf8'); +const formDocument = { + _id: `form:${formId}`, + internalId: formId, + title: `Form ${formId}`, + type: 'form', + context: { person: true, place: true }, + _attachments: { + xml: { + content_type: 'application/octet-stream', + data: Buffer.from(form).toString('base64') } - }; + } +}; - const places = placeFactory.generateHierarchy(); - const districtHospital = places.get('district_hospital'); - const area1 = places.get('health_center'); - const area2 = placeFactory.place().build({ - _id: 'area2', - name: 'area 2', - type: 'health_center', - parent: { _id: districtHospital._id } - }); +const places = placeFactory.generateHierarchy(); +const districtHospital = places.get('district_hospital'); +const area1 = places.get('health_center'); +const area2 = placeFactory.place().build({ + _id: 'area2', + name: 'area 2', + type: 'health_center', + parent: { _id: districtHospital._id } +}); - const offlineUser = userFactory.build({ place: districtHospital._id, roles: [ 'chw' ] }); - const personArea1 = personFactory.build({ parent: { _id: area1._id, parent: area1.parent } }); - const personArea2 = personFactory.build({ name: 'Patricio', parent: { _id: area2._id, parent: area2.parent } }); +const offlineUser = userFactory.build({ place: districtHospital._id, roles: [ 'chw' ] }); +const personArea1 = personFactory.build({ parent: { _id: area1._id, parent: area1.parent } }); +const personArea2 = personFactory.build({ name: 'Patricio', parent: { _id: area2._id, parent: area2.parent } }); + +describe('DB Object Widget', () => { before(async () => { await utils.saveDocs([ ...places.values(), area2, personArea1, personArea2, formDocument ]); From 5a039e4085fef3363b0d495730e5cd192049e97e Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Mon, 25 Dec 2023 15:26:29 +0700 Subject: [PATCH 12/13] renaming to "descendant-of-current-contact" --- tests/e2e/default/enketo/forms/db-object-widget.xml | 2 +- webapp/src/js/enketo/widgets/db-object-widget.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/default/enketo/forms/db-object-widget.xml b/tests/e2e/default/enketo/forms/db-object-widget.xml index 8b8a23212a2..6f945b1cc65 100644 --- a/tests/e2e/default/enketo/forms/db-object-widget.xml +++ b/tests/e2e/default/enketo/forms/db-object-widget.xml @@ -21,7 +21,7 @@ - + diff --git a/webapp/src/js/enketo/widgets/db-object-widget.js b/webapp/src/js/enketo/widgets/db-object-widget.js index 07a70acd1a7..81ca00ffc18 100644 --- a/webapp/src/js/enketo/widgets/db-object-widget.js +++ b/webapp/src/js/enketo/widgets/db-object-widget.js @@ -55,7 +55,7 @@ const construct = ( element ) => { } const contactTypes = getContactTypes($question, $textInput); const allowNew = $question.hasClass('or-appearance-allow-new'); - const filterByParent = $question.hasClass('or-appearance-with-same-parent'); + const filterByParent = $question.hasClass('or-appearance-descendant-of-current-contact'); Select2Search.init($selectInput, contactTypes, { allowNew, filterByParent }).then(function() { // select2 doesn't understand readonly $selectInput.prop('disabled', $textInput.prop('readonly')); From 6e1082476a33add59dedbe2db4d7cd67db5d857a Mon Sep 17 00:00:00 2001 From: latin-panda <66472237+latin-panda@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:55:51 +0700 Subject: [PATCH 13/13] Moving back test data def --- .../enketo/db-object-widget.wdio-spec.js | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js index 58bb4a34ce1..36f82321a4f 100644 --- a/tests/e2e/default/enketo/db-object-widget.wdio-spec.js +++ b/tests/e2e/default/enketo/db-object-widget.wdio-spec.js @@ -9,38 +9,36 @@ const loginPage = require('@page-objects/default/login/login.wdio.page'); const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); const reportsPage = require('@page-objects/default/reports/reports.wdio.page'); -// Test Data -const formId = 'db-object-widget'; -const form = fs.readFileSync(`${__dirname}/forms/${formId}.xml`, 'utf8'); -const formDocument = { - _id: `form:${formId}`, - internalId: formId, - title: `Form ${formId}`, - type: 'form', - context: { person: true, place: true }, - _attachments: { - xml: { - content_type: 'application/octet-stream', - data: Buffer.from(form).toString('base64') +describe('DB Object Widget', () => { + const formId = 'db-object-widget'; + const form = fs.readFileSync(`${__dirname}/forms/${formId}.xml`, 'utf8'); + const formDocument = { + _id: `form:${formId}`, + internalId: formId, + title: `Form ${formId}`, + type: 'form', + context: { person: true, place: true }, + _attachments: { + xml: { + content_type: 'application/octet-stream', + data: Buffer.from(form).toString('base64') + } } - } -}; - -const places = placeFactory.generateHierarchy(); -const districtHospital = places.get('district_hospital'); -const area1 = places.get('health_center'); -const area2 = placeFactory.place().build({ - _id: 'area2', - name: 'area 2', - type: 'health_center', - parent: { _id: districtHospital._id } -}); + }; -const offlineUser = userFactory.build({ place: districtHospital._id, roles: [ 'chw' ] }); -const personArea1 = personFactory.build({ parent: { _id: area1._id, parent: area1.parent } }); -const personArea2 = personFactory.build({ name: 'Patricio', parent: { _id: area2._id, parent: area2.parent } }); + const places = placeFactory.generateHierarchy(); + const districtHospital = places.get('district_hospital'); + const area1 = places.get('health_center'); + const area2 = placeFactory.place().build({ + _id: 'area2', + name: 'area 2', + type: 'health_center', + parent: { _id: districtHospital._id } + }); -describe('DB Object Widget', () => { + const offlineUser = userFactory.build({ place: districtHospital._id, roles: [ 'chw' ] }); + const personArea1 = personFactory.build({ parent: { _id: area1._id, parent: area1.parent } }); + const personArea2 = personFactory.build({ name: 'Patricio', parent: { _id: area2._id, parent: area2.parent } }); before(async () => { await utils.saveDocs([ ...places.values(), area2, personArea1, personArea2, formDocument ]);