diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2c8c1be7..d9f2945b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,155 @@ +# [100.49.0](https://github.com/dhis2/capture-app/compare/v100.48.0...v100.49.0) (2023-12-18) + + +### Features + +* [DHIS2-12544] Add verbose logging to rules engine ([#3480](https://github.com/dhis2/capture-app/issues/3480)) ([2a6d4a8](https://github.com/dhis2/capture-app/commit/2a6d4a839ae0daed7b2ddbf09a401c29dbb3cfd7)) + +# [100.48.0](https://github.com/dhis2/capture-app/compare/v100.47.3...v100.48.0) (2023-12-15) + + +### Features + +* [DHIS2-15783] Tooltip on long working list names ([#3474](https://github.com/dhis2/capture-app/issues/3474)) ([6263aa8](https://github.com/dhis2/capture-app/commit/6263aa8a2114b028afff3b78c39f699c0946e682)) + +## [100.47.3](https://github.com/dhis2/capture-app/compare/v100.47.2...v100.47.3) (2023-12-14) + + +### Bug Fixes + +* [DHIS2-16084] Filter unidirectional relationship types ([#3477](https://github.com/dhis2/capture-app/issues/3477)) ([3579fc0](https://github.com/dhis2/capture-app/commit/3579fc05d02f6210f381f93414d07b60a0cfea53)) + +## [100.47.2](https://github.com/dhis2/capture-app/compare/v100.47.1...v100.47.2) (2023-12-10) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([1a4e748](https://github.com/dhis2/capture-app/commit/1a4e7482467656e19ce58f34862a306e6ae008cc)) + +## [100.47.1](https://github.com/dhis2/capture-app/compare/v100.47.0...v100.47.1) (2023-12-05) + + +### Bug Fixes + +* [DHIS2-15694] Cancel button in Relationship ([#3471](https://github.com/dhis2/capture-app/issues/3471)) ([c1dfbdb](https://github.com/dhis2/capture-app/commit/c1dfbdb3352388bba7d51b8bc2b9d033d10967c3)) +* [DHIS2-15983] Enrollment date not assigned on form init ([#3475](https://github.com/dhis2/capture-app/issues/3475)) ([a13a766](https://github.com/dhis2/capture-app/commit/a13a766ea463514dfdd090b8d40db6406dac80bb)) + +# [100.47.0](https://github.com/dhis2/capture-app/compare/v100.46.1...v100.47.0) (2023-12-03) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([e20fb8c](https://github.com/dhis2/capture-app/commit/e20fb8c551aefb0546c5973dd87940d56e3018be)) + + +### Features + +* [DHIS2-16123] Add inheritable TEAs to Relationships ([#3464](https://github.com/dhis2/capture-app/issues/3464)) ([620b008](https://github.com/dhis2/capture-app/commit/620b00832da7c4951d6e905b40317dd0acca0aa6)) + +## [100.46.1](https://github.com/dhis2/capture-app/compare/v100.46.0...v100.46.1) (2023-11-30) + + +### Bug Fixes + +* [DHIS2-15686] fix program rule variables for edit profile mode ([#3463](https://github.com/dhis2/capture-app/issues/3463)) ([a24bb27](https://github.com/dhis2/capture-app/commit/a24bb2723f06cd4a3ca87bbbb22977b097377b3c)) + +# [100.46.0](https://github.com/dhis2/capture-app/compare/v100.45.2...v100.46.0) (2023-11-30) + + +### Features + +* [DHIS2-14275] Support custom icons ([#3473](https://github.com/dhis2/capture-app/issues/3473)) ([2404fca](https://github.com/dhis2/capture-app/commit/2404fca8085965e699ae518cfe816f2f3d38dea7)) + +## [100.45.2](https://github.com/dhis2/capture-app/compare/v100.45.1...v100.45.2) (2023-11-29) + + +### Bug Fixes + +* [DHIS2-15693] Rules not triggered on program update ([#3472](https://github.com/dhis2/capture-app/issues/3472)) ([2dbca1e](https://github.com/dhis2/capture-app/commit/2dbca1efe36ed0e166d4aea803505c30c79cb35d)) + +## [100.45.1](https://github.com/dhis2/capture-app/compare/v100.45.0...v100.45.1) (2023-11-26) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([8f28703](https://github.com/dhis2/capture-app/commit/8f28703ae8b56cfee283ec1ce0ec2b13dfd91e30)) + +# [100.45.0](https://github.com/dhis2/capture-app/compare/v100.44.7...v100.45.0) (2023-11-20) + + +### Features + +* [DHIS2-13237] Enrollment coordinates in enrollment widget ([#3141](https://github.com/dhis2/capture-app/issues/3141)) ([2f2e52c](https://github.com/dhis2/capture-app/commit/2f2e52c3103e9cb48e77766701a9a5fc9af6ad48)) + +## [100.44.7](https://github.com/dhis2/capture-app/compare/v100.44.6...v100.44.7) (2023-11-19) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([46fb227](https://github.com/dhis2/capture-app/commit/46fb2270b21c4dec57f4447232608d8dd3cfb9af)) + +## [100.44.6](https://github.com/dhis2/capture-app/compare/v100.44.5...v100.44.6) (2023-11-16) + + +### Bug Fixes + +* [DHIS2-15967] allow enrolling TEIs with mandatory TET attributes ([#3455](https://github.com/dhis2/capture-app/issues/3455)) ([cc87a8a](https://github.com/dhis2/capture-app/commit/cc87a8a993001d5b4cea8493a84a6798f481cc5a)) + +## [100.44.5](https://github.com/dhis2/capture-app/compare/v100.44.4...v100.44.5) (2023-11-10) + + +### Bug Fixes + +* [TECH-1627] use only valid combinations of orgUnit and ouMode ([#3405](https://github.com/dhis2/capture-app/issues/3405)) ([20d05f1](https://github.com/dhis2/capture-app/commit/20d05f1602840e8ac1241ee305b796d58f616802)) + +## [100.44.4](https://github.com/dhis2/capture-app/compare/v100.44.3...v100.44.4) (2023-11-10) + + +### Bug Fixes + +* [DHIS2-15383] align mandatory behaviour for all value types ([#3413](https://github.com/dhis2/capture-app/issues/3413)) ([b0eddc7](https://github.com/dhis2/capture-app/commit/b0eddc76b674ceb20df0eb5bcd0672a3c42d3954)) + +## [100.44.3](https://github.com/dhis2/capture-app/compare/v100.44.2...v100.44.3) (2023-11-10) + + +### Bug Fixes + +* [DHIS2-15814] missing orgunit names ([#3449](https://github.com/dhis2/capture-app/issues/3449)) ([488f8c0](https://github.com/dhis2/capture-app/commit/488f8c090680ad47ed693214478a7ef2919b82e7)) + +## [100.44.2](https://github.com/dhis2/capture-app/compare/v100.44.1...v100.44.2) (2023-11-05) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([0076449](https://github.com/dhis2/capture-app/commit/0076449e545803fa8b5e36b0663d807bb6c7d410)) + +## [100.44.1](https://github.com/dhis2/capture-app/compare/v100.44.0...v100.44.1) (2023-11-02) + + +### Bug Fixes + +* [DHIS2-15945] translations in enrollment widget ([#3433](https://github.com/dhis2/capture-app/issues/3433)) ([c188767](https://github.com/dhis2/capture-app/commit/c1887673e56f2f73cfc594b682d471c10858c0fd)) + +# [100.44.0](https://github.com/dhis2/capture-app/compare/v100.43.0...v100.44.0) (2023-11-01) + + +### Features + +* [DHIS2-15483] assign an user when scheduling an enrollment event ([#3419](https://github.com/dhis2/capture-app/issues/3419)) ([556884f](https://github.com/dhis2/capture-app/commit/556884f7302215c31b7be433bbeb24503ea9b12d)) + +# [100.43.0](https://github.com/dhis2/capture-app/compare/v100.42.0...v100.43.0) (2023-10-31) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([9636ea1](https://github.com/dhis2/capture-app/commit/9636ea1e8f54685b2c5ca65fd7b49cd3f5d5f846)) +* [DHIS2-15879] Unable to schedule event with note ([#3421](https://github.com/dhis2/capture-app/issues/3421)) ([e017ac8](https://github.com/dhis2/capture-app/commit/e017ac8e3df69c4803d2e6f73aa9a585984a008f)) + + +### Features + +* [DHIS-14012] removed download as XML button ([#3434](https://github.com/dhis2/capture-app/issues/3434)) ([7b0e387](https://github.com/dhis2/capture-app/commit/7b0e3871c76478aabfd6696a8e7ac27339254299)) + # [100.42.0](https://github.com/dhis2/capture-app/compare/v100.41.4...v100.42.0) (2023-10-24) diff --git a/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature b/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature index 480c827c0a..cc60abeef8 100644 --- a/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature +++ b/cypress/e2e/EnrollmentPage/StagesAndEventsWidget.feature @@ -6,13 +6,16 @@ Feature: User interacts with Stages and Events Widget Scenario: User can close the Stages and Events Widget Given you open the enrollment page + And the program stages should be displayed When you click the stages and events widget toggle open close button Then the stages and events widget should be closed Scenario: User can close and reopen the Stages and Events Widget Given you open the enrollment page + And the program stages should be displayed + When you click the stages and events widget toggle open close button + Then the stages and events widget should be closed When you click the stages and events widget toggle open close button - And you click the stages and events widget toggle open close button Then the program stages should be displayed Scenario: User can view the list of events @@ -21,15 +24,11 @@ Feature: User interacts with Stages and Events Widget And you see the first 5 events in the table And you see buttons in the footer list - Scenario: User can view more events + Scenario: User can view more events and then view less Given you open the enrollment page which has multiples events and stages When you click show more button in stages&event list Then more events should be displayed And reset button should be displayed - - Scenario: User can reset events - Given you open the enrollment page which has multiples events and stages - When you click show more button in stages&event list And you click reset button Then there should be 5 rows in the table diff --git a/cypress/e2e/MainPage.feature b/cypress/e2e/MainPage.feature index 70a5430db6..980d9fd1e2 100644 --- a/cypress/e2e/MainPage.feature +++ b/cypress/e2e/MainPage.feature @@ -42,3 +42,15 @@ Feature: User interacts with Main page Then you see the opt out component for Child Programme When you opt out to use the new enrollment Dashboard for Child Programme Then you see the opt in component for Child Programme + + @v<41 + Scenario: The icon is rendered as an svg + Given you are in the main page with no selections made + When you select Child Programme + Then the icon is rendered as an svg + + @v>=41 + Scenario: The icon is rendered as a custom icon + Given you are in the main page with no selections made + When you select Child Programme + Then the icon is rendered as a custom icon diff --git a/cypress/e2e/MainPage/index.js b/cypress/e2e/MainPage/index.js index f004659122..6090ac6fb5 100644 --- a/cypress/e2e/MainPage/index.js +++ b/cypress/e2e/MainPage/index.js @@ -20,6 +20,18 @@ And('you can load the view with the name Events assigned to me', () => { }); }); +Then('the icon is rendered as a custom icon', () => { + cy.get('[alt="child_program_positive"]') + .invoke('attr', 'src') + .should('match', /\/icons\/child_program_positive\/icon$/); +}); + +Then('the icon is rendered as an svg', () => { + cy.get('[alt="child_program_positive"]') + .invoke('attr', 'src') + .should('match', /\/icons\/child_program_positive\/icon.svg$/); +}); + Then('the TEI working list is displayed', () => { cy.get('[data-test="tei-working-lists"]').within(() => { cy.contains('Rows per page').should('exist'); diff --git a/cypress/e2e/NewEventThroughAddRelationship.feature b/cypress/e2e/NewEventThroughAddRelationship.feature index 5276bbb960..f0db9f2503 100644 --- a/cypress/e2e/NewEventThroughAddRelationship.feature +++ b/cypress/e2e/NewEventThroughAddRelationship.feature @@ -23,3 +23,8 @@ Feature: User adds events And you submit the event form with the associated relationship to the already existing person Then the event and relationship should be sent to the server successfully + Scenario: User gets navigated correctly when clicking on the back button + Given you open the the new event page in Ngelehun and malaria case context + When you navigate to register a person relationship + And you click the cancel button + Then you should be navigated back to the event form diff --git a/cypress/e2e/NewEventThroughAddRelationship/index.js b/cypress/e2e/NewEventThroughAddRelationship/index.js index d525b595a4..3e17338b14 100644 --- a/cypress/e2e/NewEventThroughAddRelationship/index.js +++ b/cypress/e2e/NewEventThroughAddRelationship/index.js @@ -168,3 +168,12 @@ Then('the event and relationship should be sent to the server successfully', () }); }); }); + +When('you click the cancel button', () => { + cy.get('[data-test="cancel-button"]') + .click(); +}); + +Then('you should be navigated back to the event form', () => { + cy.contains('New Malaria case registration'); +}); diff --git a/cypress/e2e/NewPage.feature b/cypress/e2e/NewPage.feature index 220ca7e971..afbc13720c 100644 --- a/cypress/e2e/NewPage.feature +++ b/cypress/e2e/NewPage.feature @@ -94,6 +94,13 @@ Feature: User creates a new entries from the registration page Then you are navigated to the Antenatal care visit registration page Then program and organisation unit is still selected in top bar + Scenario: Clicking the cancel button should navigate with correct context + Given you are on the default registration page + And you select org unit + And you select Child Programme + When you click the cancel button + Then you are navigated to the working list with programId IpHINAT79UW + ### New event in Antenatal care visit Scenario: New event in Antenatal care visit > Submitting the form with empty visit date throws validation error diff --git a/cypress/e2e/SearchPage/index.js b/cypress/e2e/SearchPage/index.js index b322ef00bc..644ff73111 100644 --- a/cypress/e2e/SearchPage/index.js +++ b/cypress/e2e/SearchPage/index.js @@ -81,7 +81,6 @@ When('you can close the modal', () => { .contains('Back to search') .click(); cy.get('[data-test="dhis2-uicore-modal"]') - .contains('No results found') .should('not.exist'); }); diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js index 676a73b3bb..be1a87296a 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetEnrollment/index.js @@ -15,7 +15,7 @@ Then('the enrollment widget should be closed', () => { Then('the enrollment widget should be opened', () => { cy.get('[data-test="widget-enrollment"]').within(() => { - cy.get('[data-test="widget-contents"]').children().should('exist'); + cy.get('[data-test="widget-enrollment-contents"]').children().should('exist'); }); }); diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage.feature b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage.feature index bcdfb2ff8a..24017fb326 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage.feature +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage.feature @@ -99,3 +99,8 @@ Feature: The user interacts with the widgets on the enrollment add event page When you click switch tab to Schedule Then you should see Schedule tab And you should see suggested date: 08-01 + + Scenario: You can assign a user when scheduling an event + Given you land on the enrollment edit event page by having typed /#/enrollmentEventNew?enrollmentId=zRfAPUpjoG3&orgUnitId=DiszpKrYNg8&programId=M3xtLkYBlKI&stageId=uvMKOn1oWvd&teiId=S3JjTA4QMNe + When you click switch tab to Schedule + Then you can assign a user when scheduling the event diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage/index.js index 1640f4243c..547b242c5a 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentAddEventPage/index.js @@ -1,4 +1,16 @@ +import { Then } from '@badeball/cypress-cucumber-preprocessor'; import '../sharedSteps'; import '../WidgetEnrollment'; import '../WidgetProfile'; import '../WidgetTab'; + +Then('you can assign a user when scheduling the event', () => { + cy.get('[data-test="assignee-section"]').within(() => { + cy.get('[data-test="capture-ui-input"]').click(); + cy.get('[data-test="capture-ui-input"]').type('Tracker demo'); + cy.contains('Tracker demo User').click(); + }); + cy.get('[data-test="assignee-section"]').within(() => { + cy.get('[data-test="dhis2-uicore-chip"]').contains('Tracker demo User').should('exist'); + }); +}); diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule.feature b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule.feature index 277fcb0191..6f021d0886 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule.feature +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule.feature @@ -1,8 +1,10 @@ Feature: The user interacts with the widgets Schedule tab + # Blocked by DHIS2-16229 + @skip Scenario: User cancel after choose a schedule date in schedule tab Given you land on the enrollment add event page by having typed #/enrollmentEventNew?programId=IpHINAT79UW&orgUnitId=DiszpKrYNg8&teiId=EaOyKGOIGRp&enrollmentId=wBU0RAsYjKE&stageId=A03MvHHogjR&tab=SCHEDULE Then you should see Schedule tab Then you choose a schedule date When you click cancel in Schedule tab - Then you should see confirm dialog \ No newline at end of file + Then you should see confirm dialog diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule/index.js index 1b606aa3c0..3896685c41 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEventSchedule/index.js @@ -18,7 +18,7 @@ When('you click cancel in Schedule tab', () => { }); Then('you should see confirm dialog', () => { - cy.get('[role="dialog"]') + cy.get('aside[role="dialog"]') .find('[data-test="dhis2-uicore-modaltitle"]') .contains('Discard unsaved changes?') .should('exist'); diff --git a/cypress/e2e/WorkingLists/EventWorkingLists/EventWorkingListsUser/index.js b/cypress/e2e/WorkingLists/EventWorkingLists/EventWorkingListsUser/index.js index d16c2a84d7..79dd1acad6 100644 --- a/cypress/e2e/WorkingLists/EventWorkingLists/EventWorkingListsUser/index.js +++ b/cypress/e2e/WorkingLists/EventWorkingLists/EventWorkingListsUser/index.js @@ -148,12 +148,12 @@ When('you open the column selector', () => { }); When('you select Household location and save from the column selector', () => { - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('Household location') .find('input') .click(); - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('Save') .click(); }); diff --git a/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser.feature b/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser.feature index b0bdfef1e0..73b30f7668 100644 --- a/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser.feature +++ b/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser.feature @@ -1,5 +1,13 @@ Feature: User interacts with tei working lists + Scenario: The TEI custom working can be shared + Given you open the main page with Ngelehun and Malaria focus investigation context + And you see the custom TEI working lists + And you can load the view with the name Events assigned to me + And you create a copy of the working list + When you change the sharing settings + Then you see the new sharing settings + Scenario: User opens the default working list for a tracker program Given you open the main page with Ngelehun and child programme context Then the default working list should be displayed @@ -81,7 +89,7 @@ When you change rows per page to 10 Then the list should display 10 rows of data And for a tracker program the page navigation should show that you are on the first page -Scenario: Show teis ordered ascendingly by first name +Scenario: Show teis ordered ascendingly by first name Given you open the main page with Ngelehun and child programme context When you click the first name column header Then the sort arrow should indicate ascending order @@ -93,13 +101,7 @@ Given you open the main page with Ngelehun and Malaria focus investigation conte Then you see the custom TEI working lists And you can load the view with the name Events assigned to me -Scenario: The TEI custom working can be shared -Given you open the main page with Ngelehun and Malaria focus investigation context -And you see the custom TEI working lists -And you can load the view with the name Events assigned to me -And you create a copy of the working list -When you change the sharing settings -Then you see the new sharing settings + Scenario: The user creates, updates and deletes a TEI custom working list Given you open the main page with Ngelehun and Malaria case diagnosis context @@ -139,7 +141,7 @@ And you select a data element columns and save from the column selector Then you see data elements specific filters and columns @v>=39 -Scenario: While in a program stage working list, the user can filter by both TEA and data elements +Scenario: While in a program stage working list, the user can filter by both TEA and data elements Given you open the main page with Ngelehun, WHO RMNCH Tracker and First antenatal care visit context When you set the enrollment status filter to active And you apply the current filter @@ -152,7 +154,7 @@ And you apply the current filter Then the list should display 1 row of data @v>=39 -Scenario: While in a program stage working list, the user can sort by both TEA and data elements +Scenario: While in a program stage working list, the user can sort by both TEA and data elements Given you open the main page with Ngelehun, WHO RMNCH Tracker and First antenatal care visit context And you set the first name filter to u And you apply the current filter diff --git a/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser/index.js b/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser/index.js index 1c1574e25d..c6adb4c683 100644 --- a/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser/index.js +++ b/cypress/e2e/WorkingLists/TeiWorkingLists/TeiWorkingListsUser/index.js @@ -246,12 +246,12 @@ When('you open the column selector', () => { }); When('you select the registering unit and save from the column selector', () => { - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('Registering unit') .find('input') .click(); - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('Save') .click(); }); @@ -544,27 +544,32 @@ Then('you see the new sharing settings', () => { .click(); }); -When('you create a copy of the working list', () => { - cy.get('[data-test="list-view-menu-button"]') - .click(); +When('you create a copy of the working list', + () => { + cy.get('[data-test="list-view-menu-button"]') + .click(); - cy.contains('Save current view as') - .click(); + cy.contains('Save current view as') + .click(); - const id = uuid(); - cy.get('[data-test="view-name-content"]') - .type(id); + const id = uuid(); + cy.get('[data-test="view-name-content"]') + .type(id); - cy.intercept('POST', '**/trackedEntityInstanceFilters**').as('newTrackerFilter'); + cy.intercept('POST', '**/trackedEntityInstanceFilters**') + .as('newTrackerFilter'); - cy.get('button') - .contains('Save') - .click(); + cy.get('[data-test="new-template-dialog"]') + .within(() => { + cy.get('[data-test="dhis2-uicore-button"]') + .contains('Save') + .click(); + }); - cy.wait('@newTrackerFilter', { timeout: 30000 }); + cy.wait('@newTrackerFilter', { timeout: 30000 }); - cy.reload(); -}); + cy.reload(); + }); When('you open the program stage filters from the more filters dropdown menu', () => { cy.get('[data-test="tei-working-lists"]') @@ -602,12 +607,12 @@ When('you select the Foci response program stage', () => { }); When('you select a data element columns and save from the column selector', () => { - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('People included') .find('input') .click(); - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('Save') .click(); }); @@ -663,12 +668,12 @@ Then('you see scheduledAt filter', () => { }); When('you select a scheduledAt column and save from the column selector', () => { - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('Appointment date') .find('input') .click(); - cy.get('div[role="dialog"]') + cy.get('aside[role="dialog"]') .contains('Save') .click(); }); diff --git a/i18n/ar.po b/i18n/ar.po index e1e1024ca7..f2f9d46408 100644 --- a/i18n/ar.po +++ b/i18n/ar.po @@ -1,16 +1,16 @@ # # Translators: # KRG HIS , 2020 -# Viktor Varland , 2023 -# Hamza Assada <7amza.it@gmail.com>, 2023 # Philip Larsen Donnelly, 2023 +# Hamza Assada <7amza.it@gmail.com>, 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Philip Larsen Donnelly, 2023\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Arabic (https://app.transifex.com/hisp-uio/teams/100509/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -533,6 +533,9 @@ msgstr "ابدأ الكتابة للبحث" msgid "suggestions could not be retrieved" msgstr "تعذر استرداد الاقتراحات" +msgid "No results found" +msgstr "لم يتم العثور على أي نتائج" + msgid "No items to display" msgstr "لا توجد عناصر للعرض" @@ -951,9 +954,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "إحتمال وجود تكرار" -msgid "No results found" -msgstr "لم يتم العثور على أي نتائج" - msgid "An error occurred loading possible duplicates" msgstr "" @@ -1154,6 +1154,33 @@ msgstr "اشر من أجل الملحق" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "خط العرض" + +msgid "Longitude" +msgstr "خط الطول" + +msgid "Edit" +msgstr "تعديل" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "الإحداثيات" + +msgid "Delete polygon" +msgstr "حذف المضلع" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "عين المساحة" + msgid "Enrollment date" msgstr "تاريخ التسجيل" @@ -1178,6 +1205,12 @@ msgstr "" msgid "Cancelled" msgstr "الملغية" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1300,9 +1333,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "تعديل" - msgid "tracked entity instance" msgstr "نموذج الكيان المتتبع" @@ -1368,7 +1398,7 @@ msgid "To open this relationship, please wait until saving is complete" msgstr "" msgid "Type" -msgstr "" +msgstr "النوع" msgid "Created date" msgstr "" @@ -1550,12 +1580,6 @@ msgstr "إلى وقت" msgid "Page {{currentPage}}" msgstr "صفحة {{currentPage}}" -msgid "Delete polygon" -msgstr "حذف المضلع" - -msgid "Set area" -msgstr "عين المساحة" - msgid "Area on map saved" msgstr "" diff --git a/i18n/cs.po b/i18n/cs.po index 4e2e113dd9..eb1fcb93e4 100644 --- a/i18n/cs.po +++ b/i18n/cs.po @@ -43,7 +43,7 @@ msgstr "" "mějte však na paměti, že se tím zavřou další verze." msgid "View {{programName}} dashboard" -msgstr "" +msgstr "Zobrazit {{programName}} ovládací panel" msgid "View dashboard" msgstr "Zobrazit ovládací panel" @@ -80,7 +80,7 @@ msgstr "chyba" msgid "" "Plugins are not yet available - Please contact your system administrator" -msgstr "" +msgstr "Pluginy zatím nejsou k dispozici – obraťte se na správce systému" msgid "This value is validating" msgstr "Tato hodnota se ověřuje" @@ -161,13 +161,13 @@ msgid "Complete event" msgstr "Dokončit událost" msgid "{{ stageName }} - Basic info" -msgstr "" +msgstr "{{ stageName }} - Základní informace" msgid "{{ stageName }} - Status" -msgstr "" +msgstr "{{ stageName }} - Stav" msgid "Please select {{categoryName}}" -msgstr "" +msgstr "Vyberte prosím {{categoryName}}" msgid "A future date is not allowed" msgstr "Budoucí datum není povoleno" @@ -185,10 +185,10 @@ msgid "Metadata error. see log for details" msgstr "Chyba metadat. viz log pro podrobnosti" msgid "{{ stageName }} - Details" -msgstr "" +msgstr "{{ stageName }} - Podrobnosti" msgid "{{ stageName }} - {{ sectionName }}" -msgstr "" +msgstr "{{ stageName }} - {{ sectionName }}" msgid "Assigned user" msgstr "Přiřazený uživatel" @@ -395,6 +395,8 @@ msgid "" "This event has unsaved changes. Leaving this page without saving will lose " "these changes. Are you sure you want to discard unsaved changes?" msgstr "" +"Tato událost obsahuje neuložené změny. Opuštěním této stránky bez uložení " +"tyto změny ztratíte. Opravdu chcete zahodit neuložené změny?" msgid "No events to display" msgstr "Žádné události k zobrazení" @@ -534,6 +536,9 @@ msgstr "začněte psát pro vyhledávání" msgid "suggestions could not be retrieved" msgstr "návrhy nelze načíst" +msgid "No results found" +msgstr "Nenalezeny žádné výsledky" + msgid "No items to display" msgstr "Žádné položky k zobrazení" @@ -607,13 +612,13 @@ msgid "Write comment" msgstr "Napsat komentář" msgid "was blanked out and hidden by your last action" -msgstr "" +msgstr "byla vymazána a skryta vaší poslední akcí" msgid "Notice" -msgstr "" +msgstr "Oznámení" msgid "Close the notice" -msgstr "" +msgstr "Zavřít oznámení" msgid "Use new Enrollment dashboard for {{programName}}" msgstr "Použít nový ovládací panel zápisu pro {{programName}}" @@ -629,18 +634,29 @@ msgid "" "functionality in Capture is ongoing and will be added in upcoming app " "releases." msgstr "" +"Kliknutím na přihlášení níže začnete používat nový ovládací panel registrace" +" v aplikaci Capture pro tento program Tracker. V současné době existuje " +"určitá funkce z aplikace Tracker Capture, která ještě nebyla přidána, včetně" +" funkcí vztahů a doporučení. Práce na zahrnutí této funkce sledování do " +"Capture pokračují a budou přidány v nadcházejících vydáních aplikace." msgid "" "The core team appreciates any feedback on this new functionality which is " "currently being beta tested, please report any issues and feedback in the " "DHIS2 JIRA project." msgstr "" +"Základní tým oceňuje jakoukoli zpětnou vazbu k této nové funkcionalitě, " +"která je v současné době beta testována, nahlaste prosím jakékoli problémy a" +" zpětnou vazbu v projektu DHIS2 JIRA." msgid "" "Click the button below to opt-in to the new enrollment dashboard " "functionality in the Capture app (beta) for this Tracker program for all " "users." msgstr "" +"Kliknutím na tlačítko níže se přihlásíte k nové funkci ovládacího panelu " +"registrace v aplikaci Capture (beta) pro tento program Tracker pro všechny " +"uživatele." msgid "Yes, opt in" msgstr "Ano, přihlásit se" @@ -662,6 +678,7 @@ msgstr "" msgid "" "An error occurred while fetching enrollments. Please enter a valid url." msgstr "" +"Při načítání registrací došlo k chybě. Zadejte prosím platnou adresu URL." msgid "Enrollment Dashboard" msgstr "Ovládací panel zápisu" @@ -704,19 +721,19 @@ msgstr "" "vyberte všechny kategorie." msgid "Invalid enrollment id {{enrollmentId}}." -msgstr "" +msgstr "Neplatné id zápisu {{enrollmentId}}." msgid "Choose an enrollment to view the dashboard." msgstr "Chcete-li zobrazit ovládací panel, vyberte zápis." msgid "There are no active enrollments." -msgstr "" +msgstr "Nejsou žádné aktivní zápisy." msgid "Add new enrollment for {{teiDisplayName}} in this program." -msgstr "" +msgstr "Přidejte nový zápis pro {{teiDisplayName}} v tomto programu." msgid "No access to program owner." -msgstr "" +msgstr "Žádný přístup k vlastníkovi programu." msgid "{{teiDisplayName}} is not enrolled in this program." msgstr "{{teiDisplayName}} není v tomto programu zapsán." @@ -783,7 +800,7 @@ msgid "Refer" msgstr "Odkazovat" msgid "You can't add any more {{ programStageName }} events" -msgstr "" +msgstr "Nemůžete přidat žádné další události {{ programStageName }}" msgid "Cancel without saving" msgstr "Zrušit bez uložení" @@ -847,7 +864,7 @@ msgid "New Enrollment in program{{escape}} {{programName}}" msgstr "Nový zápis do programu{{escape}} {{programName}}" msgid "Save {{trackedEntityTypeName}}" -msgstr "" +msgstr "Uložit {{trackedEntityTypeName}}" msgid "Save {{trackedEntityName}}" msgstr "Uložit {{trackedEntityName}}" @@ -956,14 +973,11 @@ msgid "Search by attributes" msgstr "Hledání podle atributů" msgid "Could not retrieve metadata. Please try again later." -msgstr "" +msgstr "Nepodařilo se načíst metadata. Prosím zkuste to znovu později." msgid "Possible duplicates found" msgstr "Byly nalezeny možné duplikáty" -msgid "No results found" -msgstr "Nenalezeny žádné výsledky" - msgid "An error occurred loading possible duplicates" msgstr "Při načítání možných duplikátů došlo k chybě" @@ -977,28 +991,28 @@ msgid "Add relationship" msgstr "Přidat vztah" msgid "No results found for " -msgstr "" +msgstr "Pro dotaz nebyly nalezeny žádné výsledky" msgid "Registering unit" msgstr "Registrační jednotka" msgid "Choose a registering unit" -msgstr "" +msgstr "Vyberte registrační jednotku" msgid "Clear selection" -msgstr "" +msgstr "Vymazat výběr" msgid "No programs available." msgstr "Nejsou k dispozici žádné programy." msgid "Search for a program" -msgstr "" +msgstr "Vyhledat program" msgid "Some programs are being filtered by the chosen registering unit" -msgstr "" +msgstr "Některé programy jsou filtrovány vybranou registrační jednotkou" msgid "Show all programs" -msgstr "" +msgstr "Zobrazit všechny programy" msgid "Choose a program" msgstr "Vyberte program" @@ -1074,7 +1088,7 @@ msgid "Cannot search in all programs" msgstr "Nelze vyhledávat ve všech programech" msgid "Missing search criteria" -msgstr "" +msgstr "Chybí kritéria vyhledávání" msgid "Results found" msgstr "Nalezeny výsledky" @@ -1113,20 +1127,22 @@ msgid "This program is protected" msgstr "Tento program je chráněn" msgid "Reason to check for enrollments" -msgstr "" +msgstr "Důvod ke kontrole zápisů" msgid "" "Describe the reason you are checking for enrollments in this protected " "program" -msgstr "" +msgstr "Popište důvod, proč kontrolujete zápisy v tomto chráněném programu" msgid "Check for enrollments" -msgstr "" +msgstr "Zkontrolujte zápisy" msgid "" "You must provide a reason to check for enrollments in this protected " "program. All activity will be logged." msgstr "" +"Pro kontrolu zápisů v tomto chráněném programu musíte uvést důvod. Veškerá " +"aktivita bude zaznamenána." msgid "Save comment" msgstr "Uložit komentář" @@ -1173,6 +1189,34 @@ msgstr "Označit pro další sledování" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +"Stávající data pro automaticky generované události nebudou aktualizována." + +msgid "Latitude" +msgstr "Zeměpisná šířka" + +msgid "Longitude" +msgstr "Zeměpisná délka" + +msgid "Edit" +msgstr "Upravit" + +msgid "Set coordinates" +msgstr "Nastavit souřadnice" + +msgid "Coordinates" +msgstr "Souřadnice" + +msgid "Delete polygon" +msgstr "Smazat polygon" + +msgid "Close without saving" +msgstr "Zavřít bez uložení" + +msgid "Finish drawing before saving" +msgstr "Před uložením dokončete kreslení" + +msgid "Set area" +msgstr "Nastavit oblast" msgid "Enrollment date" msgstr "Datum zápisu" @@ -1198,6 +1242,12 @@ msgstr "Poslední aktualizace {{date}}" msgid "Cancelled" msgstr "Zrušeno" +msgid "Add coordinates" +msgstr "Přidat souřadnice" + +msgid "Add area" +msgstr "Přidat oblast" + msgid "Comments about this enrollment" msgstr "Komentáře k tomuto zápisu" @@ -1326,9 +1376,6 @@ msgstr "Widget profilu nelze načíst. Prosím zkuste to znovu později" msgid "{{TETName}} profile" msgstr "profil {{TETName}}" -msgid "Edit" -msgstr "Upravit" - msgid "tracked entity instance" msgstr "sledovaná instance entity" @@ -1336,7 +1383,7 @@ msgid "New {{ eventName }} event" msgstr "Nová událost {{ eventName }}" msgid "To open this event, please wait until saving is complete" -msgstr "" +msgstr "Chcete-li otevřít tuto událost, počkejte na dokončení ukládání" msgid "Show {{ rest }} more" msgstr "Zobrazit {{rest}} více" @@ -1366,38 +1413,38 @@ msgid "Stages and Events" msgstr "Fáze a události" msgid "New TEI Relationship" -msgstr "" +msgstr "Nový vztah TEI" msgid "Missing implementation step" -msgstr "" +msgstr "Chybí krok implementace" msgid "Go back without saving relationship" -msgstr "" +msgstr "Vraťte se zpět bez uložení vztahu" msgid "New Relationship" -msgstr "" +msgstr "Nový vztah" msgid "Link to an existing {{tetName}}" -msgstr "" +msgstr "Odkaz na existující {{tetName}}" msgid "An error occurred while adding the relationship" -msgstr "" +msgstr "Při přidávání vztahu došlo k chybě" msgid "" "Something went wrong while loading relationships. Please try again later." -msgstr "" +msgstr "Při načítání vztahů se něco pokazilo. Prosím zkuste to znovu později." msgid "{{trackedEntityTypeName}} relationships" -msgstr "" +msgstr "{{trackedEntityTypeName}} vztahy" msgid "To open this relationship, please wait until saving is complete" -msgstr "" +msgstr "Chcete-li otevřít tento vztah, počkejte na dokončení ukládání" msgid "Type" -msgstr "" +msgstr "Typ" msgid "Created date" -msgstr "" +msgstr "Datum vytvoření" msgid "Program stage name" msgstr "Název fáze programu" @@ -1436,19 +1483,19 @@ msgid "Choose a program stage to filter by {{label}}" msgstr "Vyberte fázi programu, kterou chcete filtrovat podle {{label}}" msgid "Active enrollments" -msgstr "" +msgstr "Aktivní zápisy" msgid "Completed enrollments" -msgstr "" +msgstr "Dokončené zápisy" msgid "Cancelled enrollments" -msgstr "" +msgstr "Zrušené zápisy" msgid "Working list could not be updated" msgstr "Pracovní seznam nelze aktualizovat" msgid "an error occurred loading the working lists" -msgstr "" +msgstr "došlo k chybě při načítání pracovních seznamů" msgid "an error occurred loading Tracked entity instance lists" msgstr "došlo k chybě při načítání seznamů instancí trasovaných entit" @@ -1576,14 +1623,8 @@ msgstr "Na čas" msgid "Page {{currentPage}}" msgstr "Stránka {{currentPage}}" -msgid "Delete polygon" -msgstr "Smazat polygon" - -msgid "Set area" -msgstr "Nastavit oblast" - msgid "Area on map saved" -msgstr "" +msgstr "Oblast na mapě byla uložena" msgid "Compatibility mode" msgstr "Režim kompatibility" diff --git a/i18n/en.pot b/i18n/en.pot index dc4398bd87..35a6d54c38 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -530,6 +530,9 @@ msgstr "start typing to search" msgid "suggestions could not be retrieved" msgstr "suggestions could not be retrieved" +msgid "No results found" +msgstr "No results found" + msgid "No items to display" msgstr "No items to display" @@ -962,9 +965,6 @@ msgstr "Could not retrieve metadata. Please try again later." msgid "Possible duplicates found" msgstr "Possible duplicates found" -msgid "No results found" -msgstr "No results found" - msgid "An error occurred loading possible duplicates" msgstr "An error occurred loading possible duplicates" @@ -1176,6 +1176,33 @@ msgstr "Mark for follow-up" msgid "Existing dates for auto-generated events will not be updated." msgstr "Existing dates for auto-generated events will not be updated." +msgid "Latitude" +msgstr "Latitude" + +msgid "Longitude" +msgstr "Longitude" + +msgid "Edit" +msgstr "Edit" + +msgid "Set coordinates" +msgstr "Set coordinates" + +msgid "Coordinates" +msgstr "Coordinates" + +msgid "Delete polygon" +msgstr "Delete polygon" + +msgid "Close without saving" +msgstr "Close without saving" + +msgid "Finish drawing before saving" +msgstr "Finish drawing before saving" + +msgid "Set area" +msgstr "Set area" + msgid "Enrollment date" msgstr "Enrollment date" @@ -1200,6 +1227,12 @@ msgstr "Last updated {{date}}" msgid "Cancelled" msgstr "Cancelled" +msgid "Add coordinates" +msgstr "Add coordinates" + +msgid "Add area" +msgstr "Add area" + msgid "Comments about this enrollment" msgstr "Comments about this enrollment" @@ -1323,9 +1356,6 @@ msgstr "Profile widget could not be loaded. Please try again later" msgid "{{TETName}} profile" msgstr "{{TETName}} profile" -msgid "Edit" -msgstr "Edit" - msgid "tracked entity instance" msgstr "tracked entity instance" @@ -1619,12 +1649,6 @@ msgstr "To time" msgid "Page {{currentPage}}" msgstr "Page {{currentPage}}" -msgid "Delete polygon" -msgstr "Delete polygon" - -msgid "Set area" -msgstr "Set area" - msgid "Area on map saved" msgstr "Area on map saved" diff --git a/i18n/es.po b/i18n/es.po index a009f61377..67b54c1201 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -6,20 +6,20 @@ # Philip Larsen Donnelly, 2022 # Marta Vila , 2022 # Pablo Pajuelo Cabezas , 2022 -# Viktor Varland , 2023 # Alison Andrade , 2023 # Janeth Cruz, 2023 # Prabhjot Singh, 2023 # Christian Atavillos, 2023 # Enzo Nicolas Rossi , 2023 # Gabriela Rodriguez , 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Gabriela Rodriguez , 2023\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Spanish (https://app.transifex.com/hisp-uio/teams/100509/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -557,6 +557,9 @@ msgstr "Empiece a escribir para buscar" msgid "suggestions could not be retrieved" msgstr "No se pudieron recuperar las sugerencias" +msgid "No results found" +msgstr "No results found" + msgid "No items to display" msgstr "No hay elementos para mostrar" @@ -1004,9 +1007,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Posibles duplicados encontrados" -msgid "No results found" -msgstr "No results found" - msgid "An error occurred loading possible duplicates" msgstr "Se produjo un error al cargar posibles duplicados" @@ -1226,6 +1226,33 @@ msgstr "Marcar para seguimiento" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Latitud" + +msgid "Longitude" +msgstr "Longitud" + +msgid "Edit" +msgstr "Editar" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Coordenadas" + +msgid "Delete polygon" +msgstr "Eliminar polígono" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Establecer área" + msgid "Enrollment date" msgstr "Fecha de inscripción" @@ -1252,6 +1279,12 @@ msgstr "Última actualización {{date}}" msgid "Cancelled" msgstr "Cancelar" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "Comentarios sobre esta inscripción" @@ -1388,9 +1421,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "Perfil de {{TETName}} " -msgid "Edit" -msgstr "Editar" - msgid "tracked entity instance" msgstr "instancia de entidad rastreada" @@ -1642,12 +1672,6 @@ msgstr "Hasta la hora" msgid "Page {{currentPage}}" msgstr "Página {{currentPage}}" -msgid "Delete polygon" -msgstr "Eliminar polígono" - -msgid "Set area" -msgstr "Establecer área" - msgid "Area on map saved" msgstr "Área en el mapa guardada" diff --git a/i18n/es_419.po b/i18n/es_419.po index 42247a9fcb..9e781ff26a 100644 --- a/i18n/es_419.po +++ b/i18n/es_419.po @@ -1,14 +1,14 @@ # # Translators: -# Jaime Bosque , 2023 # Enzo Nicolas Rossi , 2023 +# Jaime Bosque , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-04T07:07:59.195Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Enzo Nicolas Rossi , 2023\n" +"Last-Translator: Jaime Bosque , 2023\n" "Language-Team: Spanish (Latin America) (https://app.transifex.com/hisp-uio/teams/100509/es_419/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -160,6 +160,15 @@ msgstr "Coordenadas" msgid "Enrollment" msgstr "Inscripción" +msgid "Complete event" +msgstr "Evento completo" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -182,15 +191,18 @@ msgstr "" "Error de metadatos. Un administrador deberá ver el registro de auditoría " "para más detalles" +msgid "{{ stageName }} - Details" +msgstr "" + +msgid "{{ stageName }} - {{ sectionName }}" +msgstr "" + msgid "Assigned user" msgstr "Usuario asignado" msgid "Search for user" msgstr "Buscar usuario" -msgid "Complete event" -msgstr "Evento completo" - msgid "Basic info" msgstr " " @@ -532,6 +544,9 @@ msgstr "Empiece a escribir para buscar" msgid "suggestions could not be retrieved" msgstr "no se pudieron cargar las sugerencias" +msgid "No results found" +msgstr "" + msgid "No items to display" msgstr "No hay elementos para mostrar" @@ -947,10 +962,19 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" msgstr "" -msgid "No results found" +msgid "Search {{uniqueAttrName}}" +msgstr "" + +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" msgstr "" msgid "An error occurred loading possible duplicates" @@ -1011,9 +1035,6 @@ msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1065,12 +1086,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1160,6 +1175,33 @@ msgstr "" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Latitud" + +msgid "Longitude" +msgstr "Longitud" + +msgid "Edit" +msgstr "" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "Fecha de inscripción" @@ -1184,6 +1226,12 @@ msgstr "" msgid "Cancelled" msgstr "Cancelar" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1306,9 +1354,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "Perfil de {{TETName}} " -msgid "Edit" -msgstr "" - msgid "tracked entity instance" msgstr "" @@ -1345,13 +1390,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" msgstr "" -msgid "Download as XML" +msgid "Link to an existing {{tetName}}" +msgstr "" + +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1522,12 +1601,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/fr.po b/i18n/fr.po index 9e40ff42d3..4c1f686a40 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -1,21 +1,21 @@ # # Translators: # Edem Kossi , 2022 -# Philip Larsen Donnelly, 2022 # Karoline Tufte Lien , 2022 # tx_e2f_fr r25 , 2022 # Bram Piot , 2022 # Yao Selom SAKA (HISP WCA) , 2023 # Yayra Gomado , 2023 -# Viktor Varland , 2023 # Gabriela Rodriguez , 2023 +# Viktor Varland , 2023 +# Philip Larsen Donnelly, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Gabriela Rodriguez , 2023\n" +"Last-Translator: Philip Larsen Donnelly, 2023\n" "Language-Team: French (https://app.transifex.com/hisp-uio/teams/100509/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -557,6 +557,9 @@ msgstr "commencer à saisir pour effectuer une recherche" msgid "suggestions could not be retrieved" msgstr "les suggestions n'ont pas pu être récupérées" +msgid "No results found" +msgstr "Aucun résultat trouvé" + msgid "No items to display" msgstr "Aucun élément à afficher" @@ -995,9 +998,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Doublons éventuellement détectés" -msgid "No results found" -msgstr "Aucun résultat trouvé" - msgid "An error occurred loading possible duplicates" msgstr "Une erreur s'est produite lors du chargement des doublons possibles" @@ -1213,6 +1213,33 @@ msgstr "Marquer pour suivi" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Latitude" + +msgid "Longitude" +msgstr "Longitude" + +msgid "Edit" +msgstr "Modifier" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Coordonnées" + +msgid "Delete polygon" +msgstr "Supprimer polygone" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Définir l'aire" + msgid "Enrollment date" msgstr "Date d'enrôlement" @@ -1238,6 +1265,12 @@ msgstr "Dernière mise à jour {{date}}" msgid "Cancelled" msgstr "Annulé" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "Commentaires sur cette inscription" @@ -1374,9 +1407,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "Profil {{Nom de la TET}}" -msgid "Edit" -msgstr "Modifier" - msgid "tracked entity instance" msgstr "instance d'entité suivie" @@ -1629,12 +1659,6 @@ msgstr "À" msgid "Page {{currentPage}}" msgstr "Page {{page en cours}}" -msgid "Delete polygon" -msgstr "Supprimer polygone" - -msgid "Set area" -msgstr "Définir l'aire" - msgid "Area on map saved" msgstr "" diff --git a/i18n/id.po b/i18n/id.po index b22da534fd..065b73ed4c 100644 --- a/i18n/id.po +++ b/i18n/id.po @@ -3,19 +3,19 @@ # Carwoto Sa'an , 2021 # Yusuf Setiawan , 2021 # Guardian Sanjaya , 2022 -# Untoro Dwi Raharjo , 2023 # Philip Larsen Donnelly, 2023 # Raja Fathurrahim, 2023 # Farida Sibuea , 2023 -# Viktor Varland , 2023 # Aprisa Chrysantina , 2023 +# Viktor Varland , 2023 +# Untoro Dwi Raharjo , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Aprisa Chrysantina , 2023\n" +"Last-Translator: Untoro Dwi Raharjo , 2023\n" "Language-Team: Indonesian (https://app.transifex.com/hisp-uio/teams/100509/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -544,6 +544,9 @@ msgstr "mulai mengetik untuk mencari" msgid "suggestions could not be retrieved" msgstr "saran tidak dapat diambil" +msgid "No results found" +msgstr "Tidak ada hasil ditemukan" + msgid "No items to display" msgstr "Tidak ada item untuk ditampilkan" @@ -972,9 +975,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Kemungkinan duplikat ditemukan" -msgid "No results found" -msgstr "Tidak ada hasil ditemukan" - msgid "An error occurred loading possible duplicates" msgstr "Terjadi kesalahan saat memuat kemungkinan duplikat" @@ -1181,6 +1181,33 @@ msgstr "Tandai untuk follow up" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Garis lintang" + +msgid "Longitude" +msgstr "Garis bujur" + +msgid "Edit" +msgstr "Sunting" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Koordinat" + +msgid "Delete polygon" +msgstr "Hapus poligon" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Setel luas" + msgid "Enrollment date" msgstr "Tanggal pendaftaran" @@ -1205,6 +1232,12 @@ msgstr "Terakhir diperbarui {{date}}" msgid "Cancelled" msgstr "Dibatalkan" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "Komentar tentang pendaftaran ini" @@ -1327,9 +1360,6 @@ msgstr "Widget profil tidak dapat dimuat. Silakan coba lagi nanti" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Sunting" - msgid "tracked entity instance" msgstr "contoh entitas yang dilacak" @@ -1577,12 +1607,6 @@ msgstr "Ke waktu" msgid "Page {{currentPage}}" msgstr "Halaman {{Halaman saat ini}}" -msgid "Delete polygon" -msgstr "Hapus poligon" - -msgid "Set area" -msgstr "Setel luas" - msgid "Area on map saved" msgstr "" diff --git a/i18n/km.po b/i18n/km.po index a36bcf4e1a..cdd9ea090a 100644 --- a/i18n/km.po +++ b/i18n/km.po @@ -1,15 +1,15 @@ # # Translators: # Philip Larsen Donnelly, 2022 -# Viktor Varland , 2022 # channara rin, 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-04T07:07:59.195Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: channara rin, 2023\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Khmer (https://app.transifex.com/hisp-uio/teams/100509/km/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -153,6 +153,15 @@ msgstr "" msgid "Enrollment" msgstr "" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -171,13 +180,16 @@ msgstr "បោះបង់" msgid "Metadata error. see log for details" msgstr "" -msgid "Assigned user" +msgid "{{ stageName }} - Details" msgstr "" -msgid "Search for user" +msgid "{{ stageName }} - {{ sectionName }}" msgstr "" -msgid "Complete event" +msgid "Assigned user" +msgstr "" + +msgid "Search for user" msgstr "" msgid "Basic info" @@ -510,6 +522,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "មិនមានលទ្ធផលបង្ហាញ" + msgid "No items to display" msgstr "" @@ -913,11 +928,20 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" msgstr "" -msgid "No results found" -msgstr "មិនមានលទ្ធផលបង្ហាញ" +msgid "Search {{uniqueAttrName}}" +msgstr "" + +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" +msgstr "" msgid "An error occurred loading possible duplicates" msgstr "" @@ -977,9 +1001,6 @@ msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1031,12 +1052,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1126,6 +1141,33 @@ msgstr "" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "រយៈទទឹង" + +msgid "Longitude" +msgstr "រយៈបណ្តោយ" + +msgid "Edit" +msgstr "កែសម្រួល​" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "ថ្ងៃខែឆ្នាំចុះឈ្មោះ" @@ -1150,6 +1192,12 @@ msgstr "" msgid "Cancelled" msgstr "បានលុបចោល" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1272,9 +1320,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "កែសម្រួល​" - msgid "tracked entity instance" msgstr "" @@ -1311,13 +1356,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" msgstr "" -msgid "Download as XML" +msgid "Link to an existing {{tetName}}" +msgstr "" + +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1488,12 +1567,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/lo.po b/i18n/lo.po index 0521d1c02a..21bbeecd37 100644 --- a/i18n/lo.po +++ b/i18n/lo.po @@ -1,16 +1,16 @@ # # Translators: -# Thuy Nguyen , 2022 # Saysamone Sibounma, 2023 -# Viktor Varland , 2023 # Philip Larsen Donnelly, 2023 +# Viktor Varland , 2023 +# Thuy Nguyen , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Philip Larsen Donnelly, 2023\n" +"Last-Translator: Thuy Nguyen , 2023\n" "Language-Team: Lao (https://app.transifex.com/hisp-uio/teams/100509/lo/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -523,6 +523,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "" + msgid "No items to display" msgstr "" @@ -941,9 +944,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "" -msgid "No results found" -msgstr "" - msgid "An error occurred loading possible duplicates" msgstr "" @@ -1142,6 +1142,33 @@ msgstr "ໝາຍໄວ້ເພື່ອຕິດຕາມ" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "ເສັ້ນຂະໜານ" + +msgid "Longitude" +msgstr "ເສັ້ນແວງ" + +msgid "Edit" +msgstr "ແກ້ໄຂ" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Coordinates" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "ວັນທີ່ລົງທະບຽນ" @@ -1166,6 +1193,12 @@ msgstr "" msgid "Cancelled" msgstr "ຍົກເລີກແລ້ວ" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1288,9 +1321,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "ແກ້ໄຂ" - msgid "tracked entity instance" msgstr "" @@ -1538,12 +1568,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/my.po b/i18n/my.po index 370b8a7761..d5cd6a50f3 100644 --- a/i18n/my.po +++ b/i18n/my.po @@ -3,14 +3,14 @@ # Aung Kyi Min , 2019 # Viktor Varland , 2021 # Wanda , 2021 -# Philip Larsen Donnelly, 2022 +# Philip Larsen Donnelly, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-04T07:07:59.195Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Philip Larsen Donnelly, 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2023\n" "Language-Team: Burmese (https://app.transifex.com/hisp-uio/teams/100509/my/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -154,6 +154,15 @@ msgstr "Coordinate" msgid "Enrollment" msgstr "" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -172,13 +181,16 @@ msgstr "ပယ်ဖျက်သည်" msgid "Metadata error. see log for details" msgstr "" -msgid "Assigned user" +msgid "{{ stageName }} - Details" msgstr "" -msgid "Search for user" +msgid "{{ stageName }} - {{ sectionName }}" msgstr "" -msgid "Complete event" +msgid "Assigned user" +msgstr "" + +msgid "Search for user" msgstr "" msgid "Basic info" @@ -511,6 +523,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "No results found" + msgid "No items to display" msgstr "" @@ -914,11 +929,20 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" msgstr "" -msgid "No results found" -msgstr "No results found" +msgid "Search {{uniqueAttrName}}" +msgstr "" + +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" +msgstr "" msgid "An error occurred loading possible duplicates" msgstr "" @@ -978,9 +1002,6 @@ msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1032,12 +1053,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1312,13 +1327,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" +msgstr "" + +msgid "Link to an existing {{tetName}}" +msgstr "" + +msgid "An error occurred while adding the relationship" msgstr "" -msgid "Download as XML" +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "ပုံစံ" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" diff --git a/i18n/nb.po b/i18n/nb.po index e562d46e97..00e615f61b 100644 --- a/i18n/nb.po +++ b/i18n/nb.po @@ -1,14 +1,14 @@ # # Translators: -# Karoline Tufte Lien , 2023 # Caroline Hesthagen Holen , 2023 +# Karoline Tufte Lien , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Caroline Hesthagen Holen , 2023\n" +"Last-Translator: Karoline Tufte Lien , 2023\n" "Language-Team: Norwegian Bokmål (https://app.transifex.com/hisp-uio/teams/100509/nb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -540,6 +540,9 @@ msgstr "begynn å skrive for å søke" msgid "suggestions could not be retrieved" msgstr "forslag kunne ikke hentes" +msgid "No results found" +msgstr "Ingen resultater funnet" + msgid "No items to display" msgstr "Ingen elementer å vise" @@ -983,9 +986,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Mulige duplikater funnet" -msgid "No results found" -msgstr "Ingen resultater funnet" - msgid "An error occurred loading possible duplicates" msgstr "En feil oppsto ved lasting av mulige duplikater" @@ -1199,6 +1199,33 @@ msgstr "" "Eksisterende datoer for automatisk genererte hendelser vil ikke bli " "oppdatert." +msgid "Latitude" +msgstr "Breddegrad" + +msgid "Longitude" +msgstr "Lengdegrad" + +msgid "Edit" +msgstr "Rediger" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Koordinater" + +msgid "Delete polygon" +msgstr "Slett polygon" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Sett område" + msgid "Enrollment date" msgstr "Registreringsdato" @@ -1223,6 +1250,12 @@ msgstr "Sist oppdatert {{date}}" msgid "Cancelled" msgstr "Kansellert" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "Kommentarer om denne registreringen" @@ -1351,9 +1384,6 @@ msgstr "Profil -widgeten kunne ikke lastes inn. Prøv igjen senere" msgid "{{TETName}} profile" msgstr "{{TETName}} profil" -msgid "Edit" -msgstr "Rediger" - msgid "tracked entity instance" msgstr "Sporet enhetforekomst" @@ -1601,12 +1631,6 @@ msgstr "Til klokkeslett" msgid "Page {{currentPage}}" msgstr "Side {{currentPage}}" -msgid "Delete polygon" -msgstr "Slett polygon" - -msgid "Set area" -msgstr "Sett område" - msgid "Area on map saved" msgstr "Området på kartet er lagret" diff --git a/i18n/nl.po b/i18n/nl.po index 4a2d38b67b..bc73cff5d8 100644 --- a/i18n/nl.po +++ b/i18n/nl.po @@ -546,6 +546,9 @@ msgstr "begin met typen om te zoeken" msgid "suggestions could not be retrieved" msgstr "suggesties konden niet worden opgehaald" +msgid "No results found" +msgstr "Geen resultaten gevonden" + msgid "No items to display" msgstr "Geen items om weer te geven" @@ -1000,9 +1003,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Mogelijke duplicaten gevonden" -msgid "No results found" -msgstr "Geen resultaten gevonden" - msgid "An error occurred loading possible duplicates" msgstr "Er is een fout opgetreden bij het laden van mogelijke duplicaten" @@ -1220,6 +1220,33 @@ msgstr "Markeer voor vervolg" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Breedtegraad" + +msgid "Longitude" +msgstr "Lengtegraad" + +msgid "Edit" +msgstr "Bewerk" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "" + +msgid "Delete polygon" +msgstr "Polygoon verwijderen" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Gebied instellen" + msgid "Enrollment date" msgstr "Inschrijvingsdatum" @@ -1245,6 +1272,12 @@ msgstr "Laatst bijgewerkt {{datum}}" msgid "Cancelled" msgstr "Geannuleerd" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "Opmerkingen over deze inschrijving" @@ -1378,9 +1411,6 @@ msgstr "Profielwidget kan niet worden geladen. Probeer het later opnieuw" msgid "{{TETName}} profile" msgstr "{{TETNaam}} profiel" -msgid "Edit" -msgstr "Bewerk" - msgid "tracked entity instance" msgstr "gevolgd entiteitsexemplaar" @@ -1633,12 +1663,6 @@ msgstr "Timen" msgid "Page {{currentPage}}" msgstr "Pagina {{huidigePagina}}" -msgid "Delete polygon" -msgstr "Polygoon verwijderen" - -msgid "Set area" -msgstr "Gebied instellen" - msgid "Area on map saved" msgstr "Gebied op kaart opgeslagen" diff --git a/i18n/or.po b/i18n/or.po index 3e1d979be6..572b2755bf 100644 --- a/i18n/or.po +++ b/i18n/or.po @@ -1,14 +1,14 @@ # # Translators: -# SubhamJena , 2022 +# SubhamJena , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2022-08-17T08:14:13.865Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: SubhamJena , 2022\n" -"Language-Team: Odia (https://www.transifex.com/hisp-uio/teams/100509/or/)\n" +"Last-Translator: SubhamJena , 2023\n" +"Language-Team: Odia (https://app.transifex.com/hisp-uio/teams/100509/or/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -36,6 +36,27 @@ msgid "" "again, but be aware that this will close other versions." msgstr "" +msgid "View {{programName}} dashboard" +msgstr "" + +msgid "View dashboard" +msgstr "" + +msgid "View active enrollment" +msgstr "" + +msgid "Re-enroll" +msgstr "" + +msgid "in" +msgstr "" + +msgid "Enrolled" +msgstr "" + +msgid "Previously enrolled" +msgstr "" + msgid "Organisation unit" msgstr "" @@ -45,10 +66,14 @@ msgstr "" msgid "Last updated" msgstr "" -msgid "Enrolled" +msgid "error encountered during field validation" msgstr "" -msgid "Previously enrolled" +msgid "error" +msgstr "" + +msgid "" +"Plugins are not yet available - Please contact your system administrator" msgstr "" msgid "This value is validating" @@ -87,7 +112,7 @@ msgstr "" msgid "Please provide a valid time" msgstr "" -msgid "Please provide a valid percentage" +msgid "Please provide an integer between 0 and 100" msgstr "" msgid "Please provide a valid url" @@ -126,6 +151,18 @@ msgstr "" msgid "Enrollment" msgstr "" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + +msgid "Please select {{categoryName}}" +msgstr "" + msgid "A future date is not allowed" msgstr "" @@ -141,13 +178,16 @@ msgstr "" msgid "Metadata error. see log for details" msgstr "" -msgid "Assigned user" +msgid "{{ stageName }} - Details" msgstr "" -msgid "Search for user" +msgid "{{ stageName }} - {{ sectionName }}" msgstr "" -msgid "Complete event" +msgid "Assigned user" +msgstr "" + +msgid "Search for user" msgstr "" msgid "Basic info" @@ -232,7 +272,7 @@ msgstr "" msgid "Go back to event without saving relationship" msgstr "" -msgid "Unsaved changes" +msgid "Discard unsaved changes?" msgstr "" msgid "" @@ -240,10 +280,10 @@ msgid "" "relationship" msgstr "" -msgid "Yes, discard" +msgid "Yes, discard changes" msgstr "" -msgid "No, stay here" +msgid "No, cancel" msgstr "" msgid "New event" @@ -264,9 +304,6 @@ msgstr "" msgid "enrollment" msgstr "" -msgid "in" -msgstr "" - msgid "Enroll in a program by selecting a program from the top bar." msgstr "" @@ -295,9 +332,6 @@ msgstr "" msgid "Registered person" msgstr "" -msgid "Leaving this page will discard the changes you made to this event." -msgstr "" - msgid "validation failed" msgstr "" @@ -313,10 +347,13 @@ msgstr "" msgid "Warnings" msgstr "" -msgid "Warnings found" +msgid "Generate new event" msgstr "" -msgid "Abort" +msgid "Do you want to create another event?" +msgstr "" + +msgid "Yes, create new event" msgstr "" msgid "Back to form" @@ -340,7 +377,9 @@ msgstr "" msgid "Operations running" msgstr "" -msgid "Sort" +msgid "" +"This event has unsaved changes. Leaving this page without saving will lose " +"these changes. Are you sure you want to discard unsaved changes?" msgstr "" msgid "No events to display" @@ -451,6 +490,12 @@ msgstr "" msgid "Select image" msgstr "" +msgid "Type to filter options" +msgstr "" + +msgid "No match found" +msgstr "" + msgid "Search" msgstr "" @@ -475,6 +520,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "" + msgid "No items to display" msgstr "" @@ -493,6 +541,9 @@ msgstr "" msgid "Reset filter" msgstr "" +msgid "Remove filter" +msgstr "" + msgid "{{fromDate}} to {{toDate}}" msgstr "" @@ -511,6 +562,9 @@ msgstr "" msgid "More filters" msgstr "" +msgid "Stage filters" +msgstr "" + msgid "Rows per page" msgstr "" @@ -535,19 +589,46 @@ msgstr "" msgid "Add comment" msgstr "" -msgid "You dont have access to write comments" +msgid "You don't have access to write comments" msgstr "" msgid "Write comment" msgstr "" +msgid "was blanked out and hidden by your last action" +msgstr "" + +msgid "Notice" +msgstr "" + +msgid "Close the notice" +msgstr "" + msgid "Use new Enrollment dashboard for {{programName}}" msgstr "" msgid "Opt in for {{programName}}" msgstr "" -msgid "No, cancel" +msgid "" +"By clicking opt-in below, you will start using the new enrollment dashboard " +"in the Capture app for this Tracker program. At the moment, there is certain" +" functionality from Tracker Capture that has not yet been added, including " +"relationship and referral functionality. The work on including this Tracker " +"functionality in Capture is ongoing and will be added in upcoming app " +"releases." +msgstr "" + +msgid "" +"The core team appreciates any feedback on this new functionality which is " +"currently being beta tested, please report any issues and feedback in the " +"DHIS2 JIRA project." +msgstr "" + +msgid "" +"Click the button below to opt-in to the new enrollment dashboard " +"functionality in the Capture app (beta) for this Tracker program for all " +"users." msgstr "" msgid "Yes, opt in" @@ -566,7 +647,8 @@ msgid "" "There is an error while opening this enrollment. Please enter a valid url." msgstr "" -msgid "Enrollment with id \"{{enrollmentId}}\" does not exist" +msgid "" +"An error occurred while fetching enrollments. Please enter a valid url." msgstr "" msgid "Enrollment Dashboard" @@ -605,9 +687,21 @@ msgid "" "{{programName}} has categories. Choose all categories to view dashboard." msgstr "" +msgid "Invalid enrollment id {{enrollmentId}}." +msgstr "" + msgid "Choose an enrollment to view the dashboard." msgstr "" +msgid "There are no active enrollments." +msgstr "" + +msgid "Add new enrollment for {{teiDisplayName}} in this program." +msgstr "" + +msgid "No access to program owner." +msgstr "" + msgid "{{teiDisplayName}} is not enrolled in this program." msgstr "" @@ -670,7 +764,7 @@ msgstr "" msgid "Refer" msgstr "" -msgid "You can’t add any more events in this program" +msgid "You can't add any more {{ programStageName }} events" msgstr "" msgid "Cancel without saving" @@ -682,7 +776,7 @@ msgstr "" msgid "Program Stages could not be loaded" msgstr "" -msgid "stage" +msgid "Stage" msgstr "" msgid "Enrollment{{escape}} View Event" @@ -721,9 +815,6 @@ msgstr "" msgid "Save as new" msgstr "" -msgid "View dashboard" -msgstr "" - msgid "View enrollment" msgstr "" @@ -737,7 +828,10 @@ msgstr "" msgid "New Enrollment in program{{escape}} {{programName}}" msgstr "" -msgid "Save new" +msgid "Save {{trackedEntityTypeName}}" +msgstr "" + +msgid "Save {{trackedEntityName}}" msgstr "" msgid "Save new {{trackedEntityTypeName}} and link" @@ -785,213 +879,220 @@ msgstr "" msgid "Register" msgstr "" -msgid "" -"Fill in at least {{minAttributesRequiredToSearch}} attributes to search" +msgid "Back" msgstr "" -msgid "Search {{name}}" +msgid "events" msgstr "" -msgid "Search by {{name}}" +msgid "event" msgstr "" -msgid "Search by attributes" +msgid "You don't have access to edit this event" msgstr "" -msgid "Fill in these fields to search{{escape}} {{ searchableAttributes }}" +msgid "Edit event" +msgstr "" + +msgid "Event details" msgstr "" msgid "" -"Fill in at least {{minAttributesRequiredToSearch}} of these fields to " -"search{{escape}} {{searchableAttributes}}" +"Leaving this page will discard any selections you made for a new " +"relationship" msgstr "" -msgid "Fill in this field to search{{escape}} {{searchableAttributes}}" +msgid "No one is assigned to this event" msgstr "" -msgid "Back" +msgid "Assign" msgstr "" -msgid "Search for {{titleText}}" +msgid "Event assigned to {{name}}" msgstr "" -msgid "Search for" +msgid "Feedbacks" msgstr "" -msgid "" -"You can also choose a program from the top bar and search in that program" +msgid "Show all events" msgstr "" -msgid "No results found" +msgid "Event could not be loaded. Are you sure it exists?" msgstr "" -msgid "" -"You can change your search terms and search again to find what you are " -"looking for." +msgid "Event could not be loaded" msgstr "" -msgid "Register a user" +msgid "Organisation unit could not be loaded" msgstr "" -msgid "Back to search" +msgid "Selected program" msgstr "" -msgid "An error has occurred" +msgid "Search {{uniqueAttrName}}" msgstr "" -msgid "" -"There is a problem with this search, please change the search terms or try " -"again later. For more details open the Console tab of the Developer tools" +msgid "Search by attributes" msgstr "" -msgid "Too many results" +msgid "Could not retrieve metadata. Please try again later." msgstr "" -msgid "" -"This search returned too many results to show. Try changing search terms or " -"searching by more attributes to narrow down the results." +msgid "Possible duplicates found" msgstr "" -msgid "Cannot search in all programs" +msgid "An error occurred loading possible duplicates" msgstr "" -msgid "Choose a type to start searching" +msgid "You don't have access to delete this relationship" msgstr "" -msgid "View active enrollment" +msgid "You don't have access to create any relationships" msgstr "" -msgid "Re-enroll" +msgid "Add relationship" msgstr "" -msgid "all programs" +msgid "No results found for " msgstr "" -msgid "If none of search results match, you can create new " +msgid "Registering unit" msgstr "" -msgid "Create new" +msgid "Choose a registering unit" msgstr "" -msgid "" -"Not finding the results you were looking for? Try to search all programs " -"that use type " +msgid "Clear selection" msgstr "" -msgid "Search in all programs" +msgid "No programs available." msgstr "" -msgid "events" +msgid "Search for a program" msgstr "" -msgid "event" +msgid "Some programs are being filtered by the chosen registering unit" msgstr "" -msgid "You dont have access to edit this event" +msgid "Show all programs" msgstr "" -msgid "Edit event" +msgid "Choose a program" msgstr "" -msgid "Event details" +msgid "Search for {{titleText}}" +msgstr "" + +msgid "Search for" msgstr "" msgid "" -"Leaving this page will discard any selections you made for a new " -"relationship" +"You can also choose a program from the top bar and search in that program" msgstr "" -msgid "No one is assigned to this event" +msgid "Choose a type to start searching" msgstr "" -msgid "Assign" +msgid "Search {{name}}" msgstr "" -msgid "Event assigned to {{name}}" +msgid "Search by {{name}}" msgstr "" -msgid "Feedbacks" +msgid "all programs" msgstr "" -msgid "Show all events" +msgid "" +"Not finding the results you were looking for? Try to search all programs " +"that use type " msgstr "" -msgid "Event could not be loaded. Are you sure it exists?" +msgid "Search in all programs" msgstr "" -msgid "Event could not be loaded" +msgid "If none of search results match, you can create a new " msgstr "" -msgid "Organisation unit could not be loaded" +msgid "Create new" msgstr "" -msgid "Possible duplicates found" +msgid "Fill in these fields to search{{escape}} {{ searchableAttributes }}" msgstr "" -msgid "An error occurred loading possible duplicates" +msgid "" +"Fill in at least {{minAttributesRequiredToSearch}} of these fields to " +"search{{escape}} {{searchableAttributes}}" msgstr "" -msgid "You dont have access to delete this relationship" +msgid "Fill in this field to search{{escape}} {{searchableAttributes}}" msgstr "" -msgid "You dont have access to create any relationships" +msgid "" +"You can change your search terms and search again to find what you are " +"looking for." msgstr "" -msgid "Add relationship" +msgid "Back to search" msgstr "" -msgid "Selected registering unit" +msgid "An error has occurred" msgstr "" -msgid "Start Again" +msgid "Too many results" msgstr "" -msgid "Are you sure? All unsaved data will be lost." +msgid "Cannot search in all programs" msgstr "" -msgid "Accept" +msgid "Missing search criteria" msgstr "" -msgid "Registering Organisation Unit" +msgid "Results found" msgstr "" -msgid "Select" +msgid "Saved lists in this program" msgstr "" -msgid "No programs available." +msgid "Saved lists offer quick access to your most used views in a program." msgstr "" -msgid "Selected program" +msgid "" +"There are no saved lists in this program yet, create one using the button " +"below." msgstr "" -msgid "Select program" +msgid "Create saved list" msgstr "" -msgid "Selected" +msgid "New {{trackedEntityName}} in {{programName}}" msgstr "" -msgid "Results found" +msgid "Search for a {{trackedEntityName}} in {{programName}}" msgstr "" -msgid "Search {{uniqueAttrName}}" +msgid "To work with the selected program," msgstr "" -msgid "Fill in at least {{minAttributesRequired}} attributes to search" +msgid "open the Tracker Capture app" msgstr "" -msgid "New {{trackedEntityName}} in {{programName}}" +msgid "This program is protected" msgstr "" -msgid "Search for a {{trackedEntityName}} in {{programName}}" +msgid "Reason to check for enrollments" msgstr "" -msgid "Clear selections" +msgid "" +"Describe the reason you are checking for enrollments in this protected " +"program" msgstr "" -msgid "To work with the selected program," +msgid "Check for enrollments" msgstr "" -msgid "open the Tracker Capture app" +msgid "" +"You must provide a reason to check for enrollments in this protected " +"program. All activity will be logged." msgstr "" msgid "Save comment" @@ -1003,10 +1104,10 @@ msgstr "" msgid "We are processing your request." msgstr "" -msgid "Add new" +msgid "Only one enrollment per {{tetName}} is allowed in this program" msgstr "" -msgid "Only one enrollment per {{tetName}} is allowed in this program" +msgid "Add new" msgstr "" msgid "Reactivate" @@ -1035,6 +1136,36 @@ msgstr "" msgid "Mark for follow-up" msgstr "" +msgid "Existing dates for auto-generated events will not be updated." +msgstr "" + +msgid "Latitude" +msgstr "ଅକ୍ଷାଂଶ" + +msgid "Longitude" +msgstr "ଦ୍ରାଘିମା" + +msgid "Edit" +msgstr "" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "" @@ -1059,6 +1190,12 @@ msgstr "" msgid "Cancelled" msgstr "" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1101,40 +1238,34 @@ msgstr "" msgid "Yes, delete event" msgstr "" +msgid "Go to “Schedule” tab to reschedule this event" +msgstr "" + +msgid "Scheduled date cannot be changed for {{ eventStatus }} events" +msgstr "" + msgid "Event completed" msgstr "" msgid "Back to all stages and events" msgstr "" -msgid "You don't have access to edit this event" +msgid "Schedule date info" msgstr "" -msgid "Schedule date info" +msgid "Scheduled automatically for {{suggestedScheduleDate}}" msgstr "" msgid "" -"This date is the suggested scheduled date based on the intervals defined, \n" -" it can be adjusted if needed." +"The scheduled date matches the suggested date, but can be changed if needed." msgstr "" -msgid "This date is {{count}} days {{position}} the suggested date." -msgid_plural "This date is {{count}} days {{position}} the suggested date." -msgstr[0] "" -msgstr[1] "" - msgid "after" msgstr "" msgid "before" msgstr "" -msgid "There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgid_plural "" -"There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgstr[0] "" -msgstr[1] "" - msgid "" "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" msgstr "" @@ -1178,9 +1309,6 @@ msgstr "" msgid "Try again or contact your system administrator for support" msgstr "" -msgid "tracked entity instance" -msgstr "" - msgid "Fix errors in the form to continue." msgstr "" @@ -1190,13 +1318,13 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" +msgid "tracked entity instance" msgstr "" msgid "New {{ eventName }} event" msgstr "" -msgid "This event is not yet preserved and cannot be edited" +msgid "To open this event, please wait until saving is complete" msgstr "" msgid "Show {{ rest }} more" @@ -1214,9 +1342,6 @@ msgstr "" msgid "Events could not be retrieved. Please try again later." msgstr "" -msgid "Registering unit" -msgstr "" - msgid "{{ totalEvents }} events" msgstr "" @@ -1229,13 +1354,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" +msgstr "" + +msgid "Link to an existing {{tetName}}" +msgstr "" + +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" msgstr "" -msgid "Download as XML" +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1250,9 +1409,6 @@ msgstr "" msgid "an error occurred loading working lists" msgstr "" -msgid "Enrollment status" -msgstr "" - msgid "Assigned to" msgstr "" @@ -1262,9 +1418,27 @@ msgstr "" msgid "Inactive" msgstr "" +msgid "Enrollment status" +msgstr "" + +msgid "Choose a program stage to filter by {{label}}" +msgstr "" + +msgid "Active enrollments" +msgstr "" + +msgid "Completed enrollments" +msgstr "" + +msgid "Cancelled enrollments" +msgstr "" + msgid "Working list could not be updated" msgstr "" +msgid "an error occurred loading the working lists" +msgstr "" + msgid "an error occurred loading Tracked entity instance lists" msgstr "" @@ -1319,6 +1493,9 @@ msgstr "" msgid "Skipped" msgstr "" +msgid "Visited" +msgstr "" + msgid "{{trackedEntityName}} in program{{escape}} {{programName}}" msgstr "" @@ -1367,9 +1544,6 @@ msgstr "" msgid "Set coordinate" msgstr "" -msgid "Page {{currentPage}}" -msgstr "" - msgid "Date" msgstr "" @@ -1388,16 +1562,10 @@ msgstr "" msgid "To time" msgstr "" -msgid "error encountered during field validation" -msgstr "" - -msgid "error" -msgstr "" - -msgid "Delete polygon" +msgid "Page {{currentPage}}" msgstr "" -msgid "Set area" +msgid "Area on map saved" msgstr "" msgid "Compatibility mode" diff --git a/i18n/prs.po b/i18n/prs.po index 4ae583ebe5..f7a6858ec0 100644 --- a/i18n/prs.po +++ b/i18n/prs.po @@ -520,6 +520,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "هیج نتیجه ی یافت نشد" + msgid "No items to display" msgstr "" @@ -938,9 +941,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "" -msgid "No results found" -msgstr "هیج نتیجه ی یافت نشد" - msgid "An error occurred loading possible duplicates" msgstr "" @@ -1139,6 +1139,33 @@ msgstr "نشانی برای پیگیری" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "عرض البلد" + +msgid "Longitude" +msgstr "طول البلد" + +msgid "Edit" +msgstr "تجدید" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "ځانګړتیاوې" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "تاریخ شمولیت" @@ -1163,6 +1190,12 @@ msgstr "" msgid "Cancelled" msgstr "" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1285,9 +1318,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "تجدید" - msgid "tracked entity instance" msgstr "" @@ -1535,12 +1565,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/ps.po b/i18n/ps.po index eadda919e9..6e904c62e5 100644 --- a/i18n/ps.po +++ b/i18n/ps.po @@ -1,12 +1,12 @@ # # Translators: -# phil_dhis2, 2022 +# Philip Larsen Donnelly, 2023 # Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-06-27T06:20:33.460Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Viktor Varland , 2023\n" "Language-Team: Pashto (https://app.transifex.com/hisp-uio/teams/100509/ps/)\n" @@ -67,6 +67,16 @@ msgstr "" msgid "Last updated" msgstr "د وروستي ځل لپاره تجدید شوې" +msgid "error encountered during field validation" +msgstr "" + +msgid "error" +msgstr "" + +msgid "" +"Plugins are not yet available - Please contact your system administrator" +msgstr "" + msgid "This value is validating" msgstr "" @@ -142,6 +152,15 @@ msgstr "همغږي کول" msgid "Enrollment" msgstr "شاملول یا نوم لیکنه" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -160,13 +179,16 @@ msgstr "رد یې کړئ [ ژباړه ـ ردول ]" msgid "Metadata error. see log for details" msgstr "" -msgid "Assigned user" +msgid "{{ stageName }} - Details" msgstr "" -msgid "Search for user" +msgid "{{ stageName }} - {{ sectionName }}" msgstr "" -msgid "Complete event" +msgid "Assigned user" +msgstr "" + +msgid "Search for user" msgstr "" msgid "Basic info" @@ -356,9 +378,6 @@ msgstr "" msgid "Operations running" msgstr "" -msgid "Sort" -msgstr "" - msgid "" "This event has unsaved changes. Leaving this page without saving will lose " "these changes. Are you sure you want to discard unsaved changes?" @@ -472,6 +491,12 @@ msgstr "" msgid "Select image" msgstr "" +msgid "Type to filter options" +msgstr "" + +msgid "No match found" +msgstr "" + msgid "Search" msgstr "جستجو" @@ -496,6 +521,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "هېڅ پایله ترلاسه نه شوه" + msgid "No items to display" msgstr "" @@ -737,7 +765,7 @@ msgstr "مهالوېش یې وټاکئ" msgid "Refer" msgstr "" -msgid "You can’t add any more {{ programStageName }} events" +msgid "You can't add any more {{ programStageName }} events" msgstr "" msgid "Cancel without saving" @@ -801,6 +829,9 @@ msgstr "" msgid "New Enrollment in program{{escape}} {{programName}}" msgstr "" +msgid "Save {{trackedEntityTypeName}}" +msgstr "" + msgid "Save {{trackedEntityName}}" msgstr "" @@ -896,11 +927,20 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" +msgstr "انتخاب شوی پروګرام" + +msgid "Search {{uniqueAttrName}}" msgstr "" -msgid "No results found" -msgstr "هېڅ پایله ترلاسه نه شوه" +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" +msgstr "" msgid "An error occurred loading possible duplicates" msgstr "" @@ -954,20 +994,12 @@ msgstr "" msgid "Choose a type to start searching" msgstr "" -msgid "Fill in at least {{count}} attribute to search" -msgid_plural "Fill in at least {{count}} attribute to search" -msgstr[0] "" -msgstr[1] "" - msgid "Search {{name}}" msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1019,12 +1051,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "انتخاب شوی پروګرام" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1111,6 +1137,36 @@ msgstr "" msgid "Mark for follow-up" msgstr "د تعقيب لپاره يې په نښه کړئ" +msgid "Existing dates for auto-generated events will not be updated." +msgstr "" + +msgid "Latitude" +msgstr "لېټېټيوډ" + +msgid "Longitude" +msgstr "لانګېټيوډ" + +msgid "Edit" +msgstr "درست یې کړئ" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "مختصات" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "د نوم ثبتونې نېټه" @@ -1135,6 +1191,12 @@ msgstr "" msgid "Cancelled" msgstr "لغوه شوی" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1199,24 +1261,12 @@ msgid "" "The scheduled date matches the suggested date, but can be changed if needed." msgstr "" -msgid "The scheduled date is {{count}} days {{position}} the suggested date." -msgid_plural "" -"The scheduled date is {{count}} days {{position}} the suggested date." -msgstr[0] "" -msgstr[1] "" - msgid "after" msgstr "" msgid "before" msgstr "" -msgid "There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgid_plural "" -"There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgstr[0] "" -msgstr[1] "" - msgid "" "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" msgstr "" @@ -1260,9 +1310,6 @@ msgstr "" msgid "Try again or contact your system administrator for support" msgstr "" -msgid "tracked entity instance" -msgstr "" - msgid "Fix errors in the form to continue." msgstr "" @@ -1272,8 +1319,8 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "درست یې کړئ" +msgid "tracked entity instance" +msgstr "" msgid "New {{ eventName }} event" msgstr "" @@ -1308,13 +1355,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" msgstr "" -msgid "Download as XML" +msgid "Link to an existing {{tetName}}" +msgstr "" + +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "نمونه/ډول" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "د پروګرام د پړاو یا مرحلې نوم" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1464,9 +1545,6 @@ msgstr "" msgid "Set coordinate" msgstr "همغږي ترتیب کړئ" -msgid "Page {{currentPage}}" -msgstr "" - msgid "Date" msgstr "نېټه" @@ -1485,16 +1563,7 @@ msgstr "" msgid "To time" msgstr "" -msgid "error encountered during field validation" -msgstr "" - -msgid "error" -msgstr "" - -msgid "Delete polygon" -msgstr "" - -msgid "Set area" +msgid "Page {{currentPage}}" msgstr "" msgid "Area on map saved" diff --git a/i18n/pt.po b/i18n/pt.po index cea52926b0..102c4482d0 100644 --- a/i18n/pt.po +++ b/i18n/pt.po @@ -547,6 +547,9 @@ msgstr "comece a digitar para pesquisar" msgid "suggestions could not be retrieved" msgstr "sugestões não puderam ser recuperadas" +msgid "No results found" +msgstr "Nenhum resultado encontrado" + msgid "No items to display" msgstr "Nenhum item para exibir" @@ -991,9 +994,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Possíveis duplicatas encontradas" -msgid "No results found" -msgstr "Nenhum resultado encontrado" - msgid "An error occurred loading possible duplicates" msgstr "Ocorreu um erro ao carregar possíveis duplicatas" @@ -1211,6 +1211,33 @@ msgstr "Marcar para acompanhamento" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Latitude" + +msgid "Longitude" +msgstr "Longitude" + +msgid "Edit" +msgstr "Editar" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Coordenadas" + +msgid "Delete polygon" +msgstr "Excluir polígono" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Definir área" + msgid "Enrollment date" msgstr "Data de inscrição" @@ -1237,6 +1264,12 @@ msgstr "Última atualização em {{date}}" msgid "Cancelled" msgstr "Cancelado" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "Comentários sobre esta inscrição" @@ -1371,9 +1404,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "{{TETName}} perfil" -msgid "Edit" -msgstr "Editar" - msgid "tracked entity instance" msgstr "instância de entidade rastreada" @@ -1625,12 +1655,6 @@ msgstr "Para o tempo" msgid "Page {{currentPage}}" msgstr "Página {{currentPage}}" -msgid "Delete polygon" -msgstr "Excluir polígono" - -msgid "Set area" -msgstr "Definir área" - msgid "Area on map saved" msgstr "Área no mapa gravada" diff --git a/i18n/pt_BR.po b/i18n/pt_BR.po index 0ec8fcac15..fa227d2a98 100644 --- a/i18n/pt_BR.po +++ b/i18n/pt_BR.po @@ -1,15 +1,15 @@ # # Translators: # Oscar Mesones Lapouble , 2021 -# Viktor Varland , 2021 -# Philip Larsen Donnelly, 2022 +# Viktor Varland , 2023 +# Philip Larsen Donnelly, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-04T07:07:59.195Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Philip Larsen Donnelly, 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2023\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/hisp-uio/teams/100509/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -153,6 +153,15 @@ msgstr "" msgid "Enrollment" msgstr "" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -171,13 +180,16 @@ msgstr "Cancelar" msgid "Metadata error. see log for details" msgstr "" -msgid "Assigned user" +msgid "{{ stageName }} - Details" msgstr "" -msgid "Search for user" +msgid "{{ stageName }} - {{ sectionName }}" msgstr "" -msgid "Complete event" +msgid "Assigned user" +msgstr "" + +msgid "Search for user" msgstr "" msgid "Basic info" @@ -510,6 +522,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "" + msgid "No items to display" msgstr "" @@ -913,10 +928,19 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" msgstr "" -msgid "No results found" +msgid "Search {{uniqueAttrName}}" +msgstr "" + +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" msgstr "" msgid "An error occurred loading possible duplicates" @@ -977,9 +1001,6 @@ msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1031,12 +1052,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1126,6 +1141,33 @@ msgstr "" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Latitude" + +msgid "Longitude" +msgstr "Longitude" + +msgid "Edit" +msgstr "Editar" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Coordenadas" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "" @@ -1150,6 +1192,12 @@ msgstr "" msgid "Cancelled" msgstr "" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1272,9 +1320,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Editar" - msgid "tracked entity instance" msgstr "" @@ -1311,13 +1356,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" +msgstr "" + +msgid "Link to an existing {{tetName}}" msgstr "" -msgid "Download as XML" +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "Tipo" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "Nome do estágio do programa" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1488,12 +1567,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/ru.po b/i18n/ru.po index 3ba772b823..95c94ed984 100644 --- a/i18n/ru.po +++ b/i18n/ru.po @@ -543,6 +543,9 @@ msgstr "Начинайте вводить для поиска" msgid "suggestions could not be retrieved" msgstr "Подсказка не может быть найдена" +msgid "No results found" +msgstr "Результаты не найдены" + msgid "No items to display" msgstr "Нет элементов для отображения" @@ -964,9 +967,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Найдены возможные дубликаты" -msgid "No results found" -msgstr "Результаты не найдены" - msgid "An error occurred loading possible duplicates" msgstr "" @@ -1165,6 +1165,33 @@ msgstr "Отметить для последующего наблюдения" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Широта" + +msgid "Longitude" +msgstr "Долгота" + +msgid "Edit" +msgstr "Редактировать" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Координаты" + +msgid "Delete polygon" +msgstr "Удалить полигон" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Установить площадь" + msgid "Enrollment date" msgstr "Дата зачисления" @@ -1189,6 +1216,12 @@ msgstr "" msgid "Cancelled" msgstr "Отмененный" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1311,9 +1344,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Редактировать" - msgid "tracked entity instance" msgstr "Элемент отслеживаемого объекта" @@ -1561,12 +1591,6 @@ msgstr "ко времени" msgid "Page {{currentPage}}" msgstr "Страница {{currentPage}}" -msgid "Delete polygon" -msgstr "Удалить полигон" - -msgid "Set area" -msgstr "Установить площадь" - msgid "Area on map saved" msgstr "" diff --git a/i18n/sv.po b/i18n/sv.po index 095c7c794d..980919e391 100644 --- a/i18n/sv.po +++ b/i18n/sv.po @@ -1,15 +1,15 @@ # # Translators: -# Philip Larsen Donnelly, 2022 -# Viktor Varland , 2023 # Jason Pickering , 2023 +# Philip Larsen Donnelly, 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-04T07:07:59.195Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Jason Pickering , 2023\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Swedish (https://app.transifex.com/hisp-uio/teams/100509/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -153,6 +153,15 @@ msgstr "Coordinate" msgid "Enrollment" msgstr "" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -171,13 +180,16 @@ msgstr "Annullera" msgid "Metadata error. see log for details" msgstr "" -msgid "Assigned user" +msgid "{{ stageName }} - Details" msgstr "" -msgid "Search for user" +msgid "{{ stageName }} - {{ sectionName }}" msgstr "" -msgid "Complete event" +msgid "Assigned user" +msgstr "" + +msgid "Search for user" msgstr "" msgid "Basic info" @@ -510,6 +522,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "Inga resultat hittades" + msgid "No items to display" msgstr "" @@ -913,11 +928,20 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" msgstr "" -msgid "No results found" -msgstr "Inga resultat hittades" +msgid "Search {{uniqueAttrName}}" +msgstr "" + +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" +msgstr "" msgid "An error occurred loading possible duplicates" msgstr "" @@ -977,9 +1001,6 @@ msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1031,12 +1052,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1126,6 +1141,33 @@ msgstr "" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Latitud" + +msgid "Longitude" +msgstr "Longitud" + +msgid "Edit" +msgstr "Redigera" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "koordinater" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "inskrivning datum" @@ -1150,6 +1192,12 @@ msgstr "" msgid "Cancelled" msgstr "Avbruten" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1272,9 +1320,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Redigera" - msgid "tracked entity instance" msgstr "" @@ -1311,13 +1356,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" +msgstr "" + +msgid "Link to an existing {{tetName}}" msgstr "" -msgid "Download as XML" +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "Typ" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1488,12 +1567,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/tet.po b/i18n/tet.po index 316f197e1b..12ce131f18 100644 --- a/i18n/tet.po +++ b/i18n/tet.po @@ -1,14 +1,14 @@ # # Translators: # Viktor Varland , 2019 -# phil_dhis2, 2022 +# Philip Larsen Donnelly, 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-05-15T10:06:53.276Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: phil_dhis2, 2022\n" +"Last-Translator: Philip Larsen Donnelly, 2023\n" "Language-Team: Tetum (Tetun) (https://app.transifex.com/hisp-uio/teams/100509/tet/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -67,6 +67,16 @@ msgstr "" msgid "Last updated" msgstr "Atualizasaun ikus" +msgid "error encountered during field validation" +msgstr "" + +msgid "error" +msgstr "" + +msgid "" +"Plugins are not yet available - Please contact your system administrator" +msgstr "" + msgid "This value is validating" msgstr "" @@ -142,6 +152,15 @@ msgstr "" msgid "Enrollment" msgstr "" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -160,13 +179,16 @@ msgstr "Kansela" msgid "Metadata error. see log for details" msgstr "" -msgid "Assigned user" +msgid "{{ stageName }} - Details" msgstr "" -msgid "Search for user" +msgid "{{ stageName }} - {{ sectionName }}" msgstr "" -msgid "Complete event" +msgid "Assigned user" +msgstr "" + +msgid "Search for user" msgstr "" msgid "Basic info" @@ -251,7 +273,7 @@ msgstr "" msgid "Go back to event without saving relationship" msgstr "" -msgid "Unsaved changes" +msgid "Discard unsaved changes?" msgstr "" msgid "" @@ -259,10 +281,10 @@ msgid "" "relationship" msgstr "" -msgid "Yes, discard" +msgid "Yes, discard changes" msgstr "" -msgid "No, stay here" +msgid "No, cancel" msgstr "" msgid "New event" @@ -332,21 +354,9 @@ msgstr "" msgid "Do you want to create another event?" msgstr "" -msgid "No, cancel" -msgstr "" - msgid "Yes, create new event" msgstr "" -msgid "Leaving this page will discard the changes you made to this event." -msgstr "" - -msgid "Warnings found" -msgstr "" - -msgid "Abort" -msgstr "" - msgid "Back to form" msgstr "" @@ -368,7 +378,9 @@ msgstr "" msgid "Operations running" msgstr "" -msgid "Sort" +msgid "" +"This event has unsaved changes. Leaving this page without saving will lose " +"these changes. Are you sure you want to discard unsaved changes?" msgstr "" msgid "No events to display" @@ -479,6 +491,12 @@ msgstr "" msgid "Select image" msgstr "" +msgid "Type to filter options" +msgstr "" + +msgid "No match found" +msgstr "" + msgid "Search" msgstr "" @@ -503,6 +521,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "" + msgid "No items to display" msgstr "" @@ -575,12 +596,42 @@ msgstr "" msgid "Write comment" msgstr "" +msgid "was blanked out and hidden by your last action" +msgstr "" + +msgid "Notice" +msgstr "" + +msgid "Close the notice" +msgstr "" + msgid "Use new Enrollment dashboard for {{programName}}" msgstr "" msgid "Opt in for {{programName}}" msgstr "" +msgid "" +"By clicking opt-in below, you will start using the new enrollment dashboard " +"in the Capture app for this Tracker program. At the moment, there is certain" +" functionality from Tracker Capture that has not yet been added, including " +"relationship and referral functionality. The work on including this Tracker " +"functionality in Capture is ongoing and will be added in upcoming app " +"releases." +msgstr "" + +msgid "" +"The core team appreciates any feedback on this new functionality which is " +"currently being beta tested, please report any issues and feedback in the " +"DHIS2 JIRA project." +msgstr "" + +msgid "" +"Click the button below to opt-in to the new enrollment dashboard " +"functionality in the Capture app (beta) for this Tracker program for all " +"users." +msgstr "" + msgid "Yes, opt in" msgstr "" @@ -714,7 +765,7 @@ msgstr "" msgid "Refer" msgstr "" -msgid "You can’t add any more {{ programStageName }} events" +msgid "You can't add any more {{ programStageName }} events" msgstr "" msgid "Cancel without saving" @@ -778,6 +829,9 @@ msgstr "" msgid "New Enrollment in program{{escape}} {{programName}}" msgstr "" +msgid "Save {{trackedEntityTypeName}}" +msgstr "" + msgid "Save {{trackedEntityName}}" msgstr "" @@ -826,196 +880,189 @@ msgstr "" msgid "Register" msgstr "" -msgid "" -"Fill in at least {{minAttributesRequiredToSearch}} attributes to search" -msgstr "" - -msgid "Search {{name}}" -msgstr "" - -msgid "Search by {{name}}" -msgstr "" +msgid "Back" +msgstr "Ba kotuk" -msgid "Search by attributes" +msgid "events" msgstr "" -msgid "Fill in these fields to search{{escape}} {{ searchableAttributes }}" +msgid "event" msgstr "" -msgid "" -"Fill in at least {{minAttributesRequiredToSearch}} of these fields to " -"search{{escape}} {{searchableAttributes}}" +msgid "You don't have access to edit this event" msgstr "" -msgid "Fill in this field to search{{escape}} {{searchableAttributes}}" +msgid "Edit event" msgstr "" -msgid "No results found" +msgid "Event details" msgstr "" msgid "" -"You can change your search terms and search again to find what you are " -"looking for." +"Leaving this page will discard any selections you made for a new " +"relationship" msgstr "" -msgid "Register a user" +msgid "No one is assigned to this event" msgstr "" -msgid "Back to search" +msgid "Assign" msgstr "" -msgid "An error has occurred" +msgid "Event assigned to {{name}}" msgstr "" -msgid "Too many results" +msgid "Feedbacks" msgstr "" -msgid "Cannot search in all programs" +msgid "Show all events" msgstr "" -msgid "Back" -msgstr "Ba kotuk" - -msgid "Search for {{titleText}}" +msgid "Event could not be loaded. Are you sure it exists?" msgstr "" -msgid "Search for" +msgid "Event could not be loaded" msgstr "" -msgid "" -"You can also choose a program from the top bar and search in that program" +msgid "Organisation unit could not be loaded" msgstr "" -msgid "Choose a type to start searching" +msgid "Selected program" msgstr "" -msgid "all programs" +msgid "Search {{uniqueAttrName}}" msgstr "" -msgid "" -"Not finding the results you were looking for? Try to search all programs " -"that use type " +msgid "Search by attributes" msgstr "" -msgid "Search in all programs" +msgid "Could not retrieve metadata. Please try again later." msgstr "" -msgid "If none of search results match, you can create a new " +msgid "Possible duplicates found" msgstr "" -msgid "Create new" +msgid "An error occurred loading possible duplicates" msgstr "" -msgid "Saved lists in this program" +msgid "You don't have access to delete this relationship" msgstr "" -msgid "Saved lists offer quick access to your most used views in a program." +msgid "You don't have access to create any relationships" msgstr "" -msgid "" -"There are no saved lists in this program yet, create one using the button " -"below." +msgid "Add relationship" msgstr "" -msgid "Create saved list" +msgid "No results found for " msgstr "" -msgid "events" +msgid "Registering unit" msgstr "" -msgid "event" +msgid "Choose a registering unit" msgstr "" -msgid "You don't have access to edit this event" +msgid "Clear selection" msgstr "" -msgid "Edit event" +msgid "No programs available." msgstr "" -msgid "Event details" +msgid "Search for a program" msgstr "" -msgid "" -"Leaving this page will discard any selections you made for a new " -"relationship" +msgid "Some programs are being filtered by the chosen registering unit" msgstr "" -msgid "No one is assigned to this event" +msgid "Show all programs" msgstr "" -msgid "Assign" +msgid "Choose a program" msgstr "" -msgid "Event assigned to {{name}}" +msgid "Search for {{titleText}}" msgstr "" -msgid "Feedbacks" +msgid "Search for" msgstr "" -msgid "Show all events" +msgid "" +"You can also choose a program from the top bar and search in that program" msgstr "" -msgid "Event could not be loaded. Are you sure it exists?" +msgid "Choose a type to start searching" msgstr "" -msgid "Event could not be loaded" +msgid "Search {{name}}" msgstr "" -msgid "Organisation unit could not be loaded" +msgid "Search by {{name}}" msgstr "" -msgid "Possible duplicates found" +msgid "all programs" msgstr "" -msgid "An error occurred loading possible duplicates" +msgid "" +"Not finding the results you were looking for? Try to search all programs " +"that use type " msgstr "" -msgid "You don't have access to delete this relationship" +msgid "Search in all programs" msgstr "" -msgid "You don't have access to create any relationships" +msgid "If none of search results match, you can create a new " msgstr "" -msgid "Add relationship" +msgid "Create new" msgstr "" -msgid "No results found for " +msgid "Fill in these fields to search{{escape}} {{ searchableAttributes }}" msgstr "" -msgid "Registering unit" +msgid "" +"Fill in at least {{minAttributesRequiredToSearch}} of these fields to " +"search{{escape}} {{searchableAttributes}}" msgstr "" -msgid "Choose a registering unit" +msgid "Fill in this field to search{{escape}} {{searchableAttributes}}" msgstr "" -msgid "Clear selection" +msgid "" +"You can change your search terms and search again to find what you are " +"looking for." msgstr "" -msgid "No programs available." +msgid "Back to search" msgstr "" -msgid "Search for a program" +msgid "An error has occurred" msgstr "" -msgid "Some programs are being filtered by the chosen registering unit" +msgid "Too many results" msgstr "" -msgid "Show all programs" +msgid "Cannot search in all programs" msgstr "" -msgid "Choose a program" +msgid "Missing search criteria" msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" +msgid "Saved lists in this program" msgstr "" -msgid "Search {{uniqueAttrName}}" +msgid "Saved lists offer quick access to your most used views in a program." +msgstr "" + +msgid "" +"There are no saved lists in this program yet, create one using the button " +"below." msgstr "" -msgid "Fill in at least {{minAttributesRequired}} attributes to search" +msgid "Create saved list" msgstr "" msgid "New {{trackedEntityName}} in {{programName}}" @@ -1090,6 +1137,9 @@ msgstr "" msgid "Mark for follow-up" msgstr "" +msgid "Existing dates for auto-generated events will not be updated." +msgstr "" + msgid "Enrollment date" msgstr "" @@ -1178,22 +1228,12 @@ msgid "" "The scheduled date matches the suggested date, but can be changed if needed." msgstr "" -msgid "The scheduled date is {{count}} days {{position}} the suggested date." -msgid_plural "" -"The scheduled date is {{count}} days {{position}} the suggested date." -msgstr[0] "" - msgid "after" msgstr "" msgid "before" msgstr "" -msgid "There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgid_plural "" -"There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgstr[0] "" - msgid "" "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" msgstr "" @@ -1237,9 +1277,6 @@ msgstr "" msgid "Try again or contact your system administrator for support" msgstr "" -msgid "tracked entity instance" -msgstr "" - msgid "Fix errors in the form to continue." msgstr "" @@ -1252,10 +1289,13 @@ msgstr "" msgid "Edit" msgstr "Edita" +msgid "tracked entity instance" +msgstr "" + msgid "New {{ eventName }} event" msgstr "" -msgid "This event is not yet preserved and cannot be edited" +msgid "To open this event, please wait until saving is complete" msgstr "" msgid "Show {{ rest }} more" @@ -1285,13 +1325,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" +msgstr "" + +msgid "Link to an existing {{tetName}}" +msgstr "" + +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "Tipu" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" msgstr "" -msgid "Download as XML" +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1306,6 +1380,9 @@ msgstr "" msgid "an error occurred loading working lists" msgstr "" +msgid "Assigned to" +msgstr "" + msgid "Registration Date" msgstr "" @@ -1315,10 +1392,16 @@ msgstr "" msgid "Enrollment status" msgstr "" -msgid "Assigned to" +msgid "Choose a program stage to filter by {{label}}" msgstr "" -msgid "Choose a program stage to filter by {{label}}" +msgid "Active enrollments" +msgstr "" + +msgid "Completed enrollments" +msgstr "" + +msgid "Cancelled enrollments" msgstr "" msgid "Working list could not be updated" @@ -1432,9 +1515,6 @@ msgstr "" msgid "Set coordinate" msgstr "" -msgid "Page {{currentPage}}" -msgstr "" - msgid "Date" msgstr "Data" @@ -1453,10 +1533,7 @@ msgstr "" msgid "To time" msgstr "" -msgid "error encountered during field validation" -msgstr "" - -msgid "error" +msgid "Page {{currentPage}}" msgstr "" msgid "Delete polygon" @@ -1465,5 +1542,8 @@ msgstr "" msgid "Set area" msgstr "" +msgid "Area on map saved" +msgstr "" + msgid "Compatibility mode" msgstr "" diff --git a/i18n/tg.po b/i18n/tg.po index 6280c6636c..76ef7fdb4d 100644 --- a/i18n/tg.po +++ b/i18n/tg.po @@ -1,14 +1,14 @@ # # Translators: -# Viktor Varland , 2022 -# Philip Larsen Donnelly, 2022 +# Philip Larsen Donnelly, 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-09-04T07:07:59.195Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Philip Larsen Donnelly, 2022\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Tajik (https://app.transifex.com/hisp-uio/teams/100509/tg/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -152,6 +152,15 @@ msgstr "" msgid "Enrollment" msgstr "" +msgid "Complete event" +msgstr "Рӯйдоди анҷомёфта" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -170,15 +179,18 @@ msgstr "Бекор кардан" msgid "Metadata error. see log for details" msgstr "" +msgid "{{ stageName }} - Details" +msgstr "" + +msgid "{{ stageName }} - {{ sectionName }}" +msgstr "" + msgid "Assigned user" msgstr "" msgid "Search for user" msgstr "" -msgid "Complete event" -msgstr "Рӯйдоди анҷомёфта" - msgid "Basic info" msgstr "" @@ -509,6 +521,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "" + msgid "No items to display" msgstr "" @@ -912,10 +927,19 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" msgstr "" -msgid "No results found" +msgid "Search {{uniqueAttrName}}" +msgstr "" + +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" msgstr "" msgid "An error occurred loading possible duplicates" @@ -976,9 +1000,6 @@ msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1030,12 +1051,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1125,6 +1140,33 @@ msgstr "" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Тӯл" + +msgid "Longitude" +msgstr "Арз" + +msgid "Edit" +msgstr "Таҳрир" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Координатаҳо" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "Санаи номнавис" @@ -1149,6 +1191,12 @@ msgstr "" msgid "Cancelled" msgstr "" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1271,9 +1319,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Таҳрир" - msgid "tracked entity instance" msgstr "" @@ -1310,13 +1355,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" +msgstr "" + +msgid "Link to an existing {{tetName}}" msgstr "" -msgid "Download as XML" +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "Навъ" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "Номи марҳилаи барнома" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1487,12 +1566,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/uk.po b/i18n/uk.po index 4d2411c675..610b8d9f56 100644 --- a/i18n/uk.po +++ b/i18n/uk.po @@ -1,17 +1,17 @@ # # Translators: # Wanda , 2021 -# phil_dhis2, 2022 # Nadiia , 2023 -# Viktor Varland , 2023 # Éva Tamási, 2023 +# Philip Larsen Donnelly, 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2023-06-27T06:20:33.460Z\n" +"POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Éva Tamási, 2023\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Ukrainian (https://app.transifex.com/hisp-uio/teams/100509/uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -70,6 +70,16 @@ msgstr "" msgid "Last updated" msgstr "Останнє оновлення" +msgid "error encountered during field validation" +msgstr "" + +msgid "error" +msgstr "" + +msgid "" +"Plugins are not yet available - Please contact your system administrator" +msgstr "" + msgid "This value is validating" msgstr "" @@ -145,6 +155,15 @@ msgstr "Координата" msgid "Enrollment" msgstr "Реєстрація" +msgid "Complete event" +msgstr "" + +msgid "{{ stageName }} - Basic info" +msgstr "" + +msgid "{{ stageName }} - Status" +msgstr "" + msgid "Please select {{categoryName}}" msgstr "" @@ -163,15 +182,18 @@ msgstr "Скасувати" msgid "Metadata error. see log for details" msgstr "" +msgid "{{ stageName }} - Details" +msgstr "" + +msgid "{{ stageName }} - {{ sectionName }}" +msgstr "" + msgid "Assigned user" msgstr "Призначений користувач" msgid "Search for user" msgstr "" -msgid "Complete event" -msgstr "" - msgid "Basic info" msgstr "" @@ -359,9 +381,6 @@ msgstr "" msgid "Operations running" msgstr "" -msgid "Sort" -msgstr "" - msgid "" "This event has unsaved changes. Leaving this page without saving will lose " "these changes. Are you sure you want to discard unsaved changes?" @@ -505,6 +524,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "Результатів не знайдено" + msgid "No items to display" msgstr "" @@ -746,7 +768,7 @@ msgstr "Запланувати" msgid "Refer" msgstr "" -msgid "You can’t add any more {{ programStageName }} events" +msgid "You can't add any more {{ programStageName }} events" msgstr "" msgid "Cancel without saving" @@ -810,6 +832,9 @@ msgstr "" msgid "New Enrollment in program{{escape}} {{programName}}" msgstr "" +msgid "Save {{trackedEntityTypeName}}" +msgstr "" + msgid "Save {{trackedEntityName}}" msgstr "" @@ -905,11 +930,20 @@ msgstr "" msgid "Organisation unit could not be loaded" msgstr "" -msgid "Possible duplicates found" +msgid "Selected program" +msgstr "Обрані програми" + +msgid "Search {{uniqueAttrName}}" msgstr "" -msgid "No results found" -msgstr "Результатів не знайдено" +msgid "Search by attributes" +msgstr "" + +msgid "Could not retrieve metadata. Please try again later." +msgstr "" + +msgid "Possible duplicates found" +msgstr "" msgid "An error occurred loading possible duplicates" msgstr "" @@ -963,22 +997,12 @@ msgstr "" msgid "Choose a type to start searching" msgstr "" -msgid "Fill in at least {{count}} attribute to search" -msgid_plural "Fill in at least {{count}} attribute to search" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" - msgid "Search {{name}}" msgstr "" msgid "Search by {{name}}" msgstr "" -msgid "Search by attributes" -msgstr "" - msgid "all programs" msgstr "" @@ -1030,12 +1054,6 @@ msgstr "" msgid "Results found" msgstr "" -msgid "Selected program" -msgstr "Обрані програми" - -msgid "Search {{uniqueAttrName}}" -msgstr "" - msgid "Saved lists in this program" msgstr "" @@ -1122,6 +1140,36 @@ msgstr "" msgid "Mark for follow-up" msgstr "Позначте для подальших дій" +msgid "Existing dates for auto-generated events will not be updated." +msgstr "" + +msgid "Latitude" +msgstr "Широта" + +msgid "Longitude" +msgstr "Довгота" + +msgid "Edit" +msgstr "Редагувати" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "Дата реєстрації" @@ -1146,6 +1194,12 @@ msgstr "" msgid "Cancelled" msgstr "Скасовано" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1210,28 +1264,12 @@ msgid "" "The scheduled date matches the suggested date, but can be changed if needed." msgstr "" -msgid "The scheduled date is {{count}} days {{position}} the suggested date." -msgid_plural "" -"The scheduled date is {{count}} days {{position}} the suggested date." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" - msgid "after" msgstr "" msgid "before" msgstr "" -msgid "There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgid_plural "" -"There are {{count}} scheduled event in {{orgUnitName}} on this day." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" - msgid "" "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" msgstr "" @@ -1284,9 +1322,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Редагувати" - msgid "tracked entity instance" msgstr "" @@ -1323,13 +1358,47 @@ msgstr "" msgid "Stages and Events" msgstr "" -msgid "Working list could not be loaded" +msgid "New TEI Relationship" msgstr "" -msgid "Download as JSON" +msgid "Missing implementation step" +msgstr "" + +msgid "Go back without saving relationship" +msgstr "" + +msgid "New Relationship" +msgstr "" + +msgid "Link to an existing {{tetName}}" msgstr "" -msgid "Download as XML" +msgid "An error occurred while adding the relationship" +msgstr "" + +msgid "" +"Something went wrong while loading relationships. Please try again later." +msgstr "" + +msgid "{{trackedEntityTypeName}} relationships" +msgstr "" + +msgid "To open this relationship, please wait until saving is complete" +msgstr "" + +msgid "Type" +msgstr "Тип" + +msgid "Created date" +msgstr "" + +msgid "Program stage name" +msgstr "Назва етапу програми" + +msgid "Working list could not be loaded" +msgstr "" + +msgid "Download as JSON" msgstr "" msgid "Download as CSV" @@ -1479,9 +1548,6 @@ msgstr "" msgid "Set coordinate" msgstr "Встановити координату" -msgid "Page {{currentPage}}" -msgstr "" - msgid "Date" msgstr "Дата" @@ -1500,16 +1566,7 @@ msgstr "" msgid "To time" msgstr "" -msgid "error encountered during field validation" -msgstr "" - -msgid "error" -msgstr "" - -msgid "Delete polygon" -msgstr "" - -msgid "Set area" +msgid "Page {{currentPage}}" msgstr "" msgid "Area on map saved" diff --git a/i18n/ur.po b/i18n/ur.po index c87c2e613a..458326829b 100644 --- a/i18n/ur.po +++ b/i18n/ur.po @@ -1,14 +1,14 @@ # # Translators: -# Viktor Varland , 2023 # Philip Larsen Donnelly, 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Philip Larsen Donnelly, 2023\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Urdu (https://app.transifex.com/hisp-uio/teams/100509/ur/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -521,6 +521,9 @@ msgstr "" msgid "suggestions could not be retrieved" msgstr "" +msgid "No results found" +msgstr "کوئی نتیجہ نہیں ملا" + msgid "No items to display" msgstr "" @@ -939,9 +942,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "" -msgid "No results found" -msgstr "کوئی نتیجہ نہیں ملا" - msgid "An error occurred loading possible duplicates" msgstr "" @@ -1140,6 +1140,33 @@ msgstr "اپ کے لئے نشان زد کریں" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "طول" + +msgid "Longitude" +msgstr "لمبائی" + +msgid "Edit" +msgstr "ترمیم" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "سماج" + +msgid "Delete polygon" +msgstr "" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "" + msgid "Enrollment date" msgstr "اندراج کی تاریخ" @@ -1164,6 +1191,12 @@ msgstr "" msgid "Cancelled" msgstr "منسوخ" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1286,9 +1319,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "ترمیم" - msgid "tracked entity instance" msgstr "" @@ -1536,12 +1566,6 @@ msgstr "" msgid "Page {{currentPage}}" msgstr "" -msgid "Delete polygon" -msgstr "" - -msgid "Set area" -msgstr "" - msgid "Area on map saved" msgstr "" diff --git a/i18n/uz_UZ_Cyrl.po b/i18n/uz_UZ_Cyrl.po index 5e07eebebb..0b7928980e 100644 --- a/i18n/uz_UZ_Cyrl.po +++ b/i18n/uz_UZ_Cyrl.po @@ -177,6 +177,7 @@ msgstr "" msgid "Saving a {{trackedEntityName}} in {{programName}} in {{orgUnitName}}." msgstr "" +"Сақланмоқда {{trackedEntityName}} да {{programName}} да {{orgUnitName}}." msgid "Cancel" msgstr "Бекор қилиш" @@ -212,7 +213,7 @@ msgid "Assignee" msgstr "Ваколат берилган шахс" msgid "Saving to {{programName}} in {{orgUnitName}}" -msgstr "" +msgstr "Сақланади {{programName}} да {{orgUnitName}}" msgid "" "This is not an event program or the metadata is corrupt. See log for " @@ -250,7 +251,7 @@ msgid "Finish" msgstr "Тугатиш" msgid "Save without completing" -msgstr "" +msgstr "Тугалланмасдан сақлаш" msgid "Complete" msgstr "Тўлдириш" @@ -293,7 +294,7 @@ msgstr "" " қилинади." msgid "Yes, discard changes" -msgstr "" +msgstr "Ҳа, ўзгартиришлар бекор қилинсин" msgid "No, cancel" msgstr "Йўқ, бекор қилинг" @@ -512,7 +513,7 @@ msgid "Type to filter options" msgstr "" msgid "No match found" -msgstr "" +msgstr "Ҳеч нима топилмади" msgid "Search" msgstr "Излаш" @@ -538,6 +539,9 @@ msgstr "қидириш учун ёзишни бошланг" msgid "suggestions could not be retrieved" msgstr "таклифлар қабул қилинмади" +msgid "No results found" +msgstr "Ҳеч қандай натижа топилмади" + msgid "No items to display" msgstr "Кўрсатиладиган ахборот йўқ" @@ -554,7 +558,7 @@ msgid "Update" msgstr "Янгилаш" msgid "Reset filter" -msgstr "" +msgstr "Фильтрни қайта созлаш" msgid "Remove filter" msgstr "Фильтрни олиб ташлаш" @@ -670,36 +674,38 @@ msgid "" msgstr "" msgid "Enrollment Dashboard" -msgstr "" +msgstr "Қайд этиш панели" msgid "No indicator output for this enrollment yet" -msgstr "" +msgstr "Ушбу қайд этишда индикатор ахбороти мавжуд эмас" msgid "No feedback for this enrollment yet" -msgstr "" +msgstr "Ушбу қайд этишда қайта алоқа ахбороти мавжуд эмас" msgid "Quick actions" -msgstr "" +msgstr "Тезкор харакатлар" msgid "New Event" -msgstr "" +msgstr "Янги Ҳодиса/Ҳолат" msgid "Schedule an event" -msgstr "" +msgstr "Ҳодиса/Ҳолатни режалаш" msgid "Make referral" msgstr "Йўналиш яратинг" msgid "No available program stages" -msgstr "" +msgstr "Дастур босқичлари мавжуд эмас" msgid "Program stage not found" -msgstr "" +msgstr "Дастур босқичлари топилмади" msgid "" "Choose a program to add new or see existing enrollments for " "{{teiDisplayName}}" msgstr "" +" {{teiDisplayName}} учун янги рўйхатга ёки мавжуд рўйхатга олишларини кўриш " +"учун дастурни танланг." msgid "" "{{programName}} has categories. Choose all categories to view dashboard." @@ -710,7 +716,7 @@ msgid "Invalid enrollment id {{enrollmentId}}." msgstr "" msgid "Choose an enrollment to view the dashboard." -msgstr "" +msgstr "бошқарув панелини кўриш учун рўйхатдан ўтишни танланг." msgid "There are no active enrollments." msgstr "" @@ -748,19 +754,19 @@ msgid "View working list in this program." msgstr "Ушбу дастурдаги ишчи рўйхатни кўриб чиқиш" msgid "Page is missing required values from URL" -msgstr "" +msgstr "URL саҳифасида керакли қийматлар йўқ" msgid "Program is not valid" -msgstr "" +msgstr "Дастур яроқли эмас" msgid "Org unit is not valid with current program" -msgstr "" +msgstr "Жорий дастур учун ташкилий бирлик яроқли эмас" msgid "There was an error opening the Page" -msgstr "" +msgstr "Саҳифани очишда хатолик мавжуд" msgid "Enrollment{{escape}} New Event" -msgstr "" +msgstr "Рўйхатга олишда {{escape}} Янги Ҳодиса/Ҳолат" msgid "There was an error loading the page" msgstr "" @@ -967,9 +973,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Бўлиши мумкин бўлган нусхалар топилди" -msgid "No results found" -msgstr "Ҳеч қандай натижа топилмади" - msgid "An error occurred loading possible duplicates" msgstr "Эҳтимолий нусхаларни юклашда хатолик юз берди" @@ -1174,6 +1177,33 @@ msgstr "Кузатув учун белгиланг" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Кенглик" + +msgid "Longitude" +msgstr "Узунлик" + +msgid "Edit" +msgstr "Таҳрирлаш" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Координаталар" + +msgid "Delete polygon" +msgstr "Полигон (кўпбурчак) ни ўчириб ташлаш" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Ҳудудни белгиланг" + msgid "Enrollment date" msgstr "Қайд этилган сана" @@ -1199,6 +1229,12 @@ msgstr " {{date}}да охирги марта янгиланган" msgid "Cancelled" msgstr "Бекор қилинди" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1322,9 +1358,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Таҳрирлаш" - msgid "tracked entity instance" msgstr "кузатилаётган объект намунаси" @@ -1572,12 +1605,6 @@ msgstr "Шу вақтгача" msgid "Page {{currentPage}}" msgstr "{{currentPage}} саҳифаси" -msgid "Delete polygon" -msgstr "Полигон (кўпбурчак) ни ўчириб ташлаш" - -msgid "Set area" -msgstr "Ҳудудни белгиланг" - msgid "Area on map saved" msgstr "" diff --git a/i18n/uz_UZ_Latn.po b/i18n/uz_UZ_Latn.po index 944b63b094..7e11d32a5a 100644 --- a/i18n/uz_UZ_Latn.po +++ b/i18n/uz_UZ_Latn.po @@ -541,6 +541,9 @@ msgstr "qidirish uchun yozishni boshlang" msgid "suggestions could not be retrieved" msgstr "takliflar qabul qilinmadi" +msgid "No results found" +msgstr "Hech qanday natija topilmadi" + msgid "No items to display" msgstr "Koʼrsatiladigan axborot yoʼq" @@ -963,9 +966,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Boʼlishi mumkin boʼlgan nusxalar topildi" -msgid "No results found" -msgstr "Hech qanday natija topilmadi" - msgid "An error occurred loading possible duplicates" msgstr "" @@ -1168,6 +1168,33 @@ msgstr "Kuzatuv uchun belgilang" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Kenglik" + +msgid "Longitude" +msgstr "Kenglik" + +msgid "Edit" +msgstr "Tahrirlash" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Координаталар" + +msgid "Delete polygon" +msgstr "Poligon (koʼpburchak) ni oʼchirib tashlash" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Hududni belgilang" + msgid "Enrollment date" msgstr "Qayd qilingan sana" @@ -1192,6 +1219,12 @@ msgstr "" msgid "Cancelled" msgstr "Bekor qilindi" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1314,9 +1347,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Tahrirlash" - msgid "tracked entity instance" msgstr "kuzatilayotgan obʼekt namunasi" @@ -1565,12 +1595,6 @@ msgstr "Shu vaqtgacha" msgid "Page {{currentPage}}" msgstr "{{currentPage}} sahifasi" -msgid "Delete polygon" -msgstr "Poligon (koʼpburchak) ni oʼchirib tashlash" - -msgid "Set area" -msgstr "Hududni belgilang" - msgid "Area on map saved" msgstr "" diff --git a/i18n/vi.po b/i18n/vi.po index a2b56bfc1e..ac142f4ea7 100644 --- a/i18n/vi.po +++ b/i18n/vi.po @@ -2,15 +2,15 @@ # Translators: # Mai Nguyen , 2022 # Thuy Nguyen , 2023 -# Viktor Varland , 2023 # Philip Larsen Donnelly, 2023 +# Viktor Varland , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Philip Larsen Donnelly, 2023\n" +"Last-Translator: Viktor Varland , 2023\n" "Language-Team: Vietnamese (https://app.transifex.com/hisp-uio/teams/100509/vi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -537,6 +537,9 @@ msgstr "bắt đầu nhập để tìm kiếm" msgid "suggestions could not be retrieved" msgstr "đề xuất không thể được truy xuất" +msgid "No results found" +msgstr "Không tìm thấy kết quả nào" + msgid "No items to display" msgstr "Không có mục để hiển thị" @@ -957,9 +960,6 @@ msgstr "" msgid "Possible duplicates found" msgstr "Có thể tìm thấy các bản sao" -msgid "No results found" -msgstr "Không tìm thấy kết quả nào" - msgid "An error occurred loading possible duplicates" msgstr "" @@ -1164,6 +1164,33 @@ msgstr "Đánh dấu để theo-dõi" msgid "Existing dates for auto-generated events will not be updated." msgstr "" +msgid "Latitude" +msgstr "Vĩ độ" + +msgid "Longitude" +msgstr "Kinh độ" + +msgid "Edit" +msgstr "Chỉnh sửa" + +msgid "Set coordinates" +msgstr "" + +msgid "Coordinates" +msgstr "Toạ độ" + +msgid "Delete polygon" +msgstr "Xóa đa giác" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "Đặt khu vực" + msgid "Enrollment date" msgstr "Ngày ghi vào" @@ -1188,6 +1215,12 @@ msgstr "" msgid "Cancelled" msgstr "Đã hủy" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "" @@ -1310,9 +1343,6 @@ msgstr "" msgid "{{TETName}} profile" msgstr "" -msgid "Edit" -msgstr "Chỉnh sửa" - msgid "tracked entity instance" msgstr "thực thể được theo dõi" @@ -1560,12 +1590,6 @@ msgstr "Đến " msgid "Page {{currentPage}}" msgstr "Trang {{currentPage}}" -msgid "Delete polygon" -msgstr "Xóa đa giác" - -msgid "Set area" -msgstr "Đặt khu vực" - msgid "Area on map saved" msgstr "" diff --git a/i18n/zh.po b/i18n/zh.po index be0d020568..16ff6d9de3 100644 --- a/i18n/zh.po +++ b/i18n/zh.po @@ -1,16 +1,16 @@ # # Translators: # Philip Larsen Donnelly, 2022 -# easylin , 2023 -# Viktor Varland , 2023 # 晓东 林 <13981924470@126.com>, 2023 +# Viktor Varland , 2023 +# easylin , 2023 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2023-09-12T06:24:49.265Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: 晓东 林 <13981924470@126.com>, 2023\n" +"Last-Translator: easylin , 2023\n" "Language-Team: Chinese (https://app.transifex.com/hisp-uio/teams/100509/zh/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -525,6 +525,9 @@ msgstr "开始输入搜索内容" msgid "suggestions could not be retrieved" msgstr "建议不能提取" +msgid "No results found" +msgstr "没有结果" + msgid "No items to display" msgstr "无条目可显示" @@ -941,14 +944,11 @@ msgid "Search by attributes" msgstr "按属性搜索" msgid "Could not retrieve metadata. Please try again later." -msgstr "" +msgstr "无法检索元数据。请稍后再试。" msgid "Possible duplicates found" msgstr "发现可能的重复" -msgid "No results found" -msgstr "没有结果" - msgid "An error occurred loading possible duplicates" msgstr "加载可能的重复项时发生错误" @@ -1149,6 +1149,33 @@ msgstr "后续标记" msgid "Existing dates for auto-generated events will not be updated." msgstr "自动生成的事件的现有日期将不会更新。" +msgid "Latitude" +msgstr "维度" + +msgid "Longitude" +msgstr "经度" + +msgid "Edit" +msgstr "编辑" + +msgid "Set coordinates" +msgstr "设置坐标" + +msgid "Coordinates" +msgstr "坐标" + +msgid "Delete polygon" +msgstr "删除多边形" + +msgid "Close without saving" +msgstr "关闭而不保存" + +msgid "Finish drawing before saving" +msgstr "保存前完成绘制" + +msgid "Set area" +msgstr "设置区域" + msgid "Enrollment date" msgstr "报名日期" @@ -1173,6 +1200,12 @@ msgstr "最近更新的{{date}}" msgid "Cancelled" msgstr "已取消" +msgid "Add coordinates" +msgstr "添加坐标" + +msgid "Add area" +msgstr "添加区域" + msgid "Comments about this enrollment" msgstr "关于本次报名的评论" @@ -1295,9 +1328,6 @@ msgstr "无法加载个人资料窗口小部件。请稍后再试" msgid "{{TETName}} profile" msgstr "{{TETName}} 基本信息" -msgid "Edit" -msgstr "编辑" - msgid "tracked entity instance" msgstr "跟踪的实体实例" @@ -1335,38 +1365,38 @@ msgid "Stages and Events" msgstr "阶段与活动" msgid "New TEI Relationship" -msgstr "" +msgstr "新加TEI 关系" msgid "Missing implementation step" -msgstr "" +msgstr "缺少实施步骤" msgid "Go back without saving relationship" -msgstr "" +msgstr "不保存关系返回" msgid "New Relationship" -msgstr "" +msgstr "新建关系" msgid "Link to an existing {{tetName}}" -msgstr "" +msgstr "链接到现有的 {{tetName}}" msgid "An error occurred while adding the relationship" -msgstr "" +msgstr "添加关系时出错" msgid "" "Something went wrong while loading relationships. Please try again later." -msgstr "" +msgstr "加载关系时出现问题。请稍后再试。" msgid "{{trackedEntityTypeName}} relationships" -msgstr "" +msgstr "{{trackedEntityTypeName}} 关系" msgid "To open this relationship, please wait until saving is complete" -msgstr "" +msgstr "要打开此关系,请等待保存完成" msgid "Type" msgstr "类型" msgid "Created date" -msgstr "" +msgstr "创建日期" msgid "Program stage name" msgstr "项目阶段名称" @@ -1545,12 +1575,6 @@ msgstr "到时间" msgid "Page {{currentPage}}" msgstr "第{{currentPage}}页" -msgid "Delete polygon" -msgstr "删除多边形" - -msgid "Set area" -msgstr "设置区域" - msgid "Area on map saved" msgstr "地图上的区域已保存" diff --git a/i18n/zh_CN.po b/i18n/zh_CN.po index 518c9ef3d8..6ebfc4ee69 100644 --- a/i18n/zh_CN.po +++ b/i18n/zh_CN.po @@ -77,7 +77,7 @@ msgstr "错误" msgid "" "Plugins are not yet available - Please contact your system administrator" -msgstr "" +msgstr "插件尚不可用 - 请联系您的系统管理员" msgid "This value is validating" msgstr "值正在被验证" @@ -158,10 +158,10 @@ msgid "Complete event" msgstr "完成事件" msgid "{{ stageName }} - Basic info" -msgstr "" +msgstr "{{ stageName }} - 基本信息" msgid "{{ stageName }} - Status" -msgstr "" +msgstr "{{ stageName }} - 状态" msgid "Please select {{categoryName}}" msgstr "请选择{{categoryName}}" @@ -182,10 +182,10 @@ msgid "Metadata error. see log for details" msgstr "元数据错误" msgid "{{ stageName }} - Details" -msgstr "" +msgstr "{{ stageName }} - 详细信息" msgid "{{ stageName }} - {{ sectionName }}" -msgstr "" +msgstr "{{ 阶段名称 }} - {{ 部分名称 }}" msgid "Assigned user" msgstr "分配的用户" @@ -284,7 +284,7 @@ msgid "" msgstr "离开本页将丢掉你的新关系的选择" msgid "Yes, discard changes" -msgstr "" +msgstr "是的,放弃更改" msgid "No, cancel" msgstr "不,取消" @@ -383,7 +383,7 @@ msgstr "运作中" msgid "" "This event has unsaved changes. Leaving this page without saving will lose " "these changes. Are you sure you want to discard unsaved changes?" -msgstr "" +msgstr "此事件有未保存的更改。离开此页面而不保存将丢失这些更改。您确定要放弃未保存的更改吗?" msgid "No events to display" msgstr "无事件可显示" @@ -494,7 +494,7 @@ msgid "Select image" msgstr "选择图像" msgid "Type to filter options" -msgstr "" +msgstr "输入筛选选项" msgid "No match found" msgstr "未找到匹配项" @@ -523,6 +523,9 @@ msgstr "开始输入搜索内容" msgid "suggestions could not be retrieved" msgstr "建议不能提取" +msgid "No results found" +msgstr "没有结果" + msgid "No items to display" msgstr "无条目可显示" @@ -596,13 +599,13 @@ msgid "Write comment" msgstr "写备注" msgid "was blanked out and hidden by your last action" -msgstr "" +msgstr "被你的最后一个动作清空并隐藏" msgid "Notice" -msgstr "" +msgstr "注意" msgid "Close the notice" -msgstr "" +msgstr "关闭通知" msgid "Use new Enrollment dashboard for {{programName}}" msgstr "为 {{programName}} 使用新的注册仪表板" @@ -768,7 +771,7 @@ msgid "Refer" msgstr "转诊" msgid "You can't add any more {{ programStageName }} events" -msgstr "" +msgstr "您无法添加更多的 {{ programStageName }} 事件" msgid "Cancel without saving" msgstr "取消而不保存" @@ -832,7 +835,7 @@ msgid "New Enrollment in program{{escape}} {{programName}}" msgstr "项目{{escape}} {{programName}}的新报名" msgid "Save {{trackedEntityTypeName}}" -msgstr "" +msgstr "保存{{trackedEntityTypeName}}" msgid "Save {{trackedEntityName}}" msgstr "保存{{trackedEntityName}}" @@ -939,14 +942,11 @@ msgid "Search by attributes" msgstr "按属性搜索" msgid "Could not retrieve metadata. Please try again later." -msgstr "" +msgstr "无法检索元数据。请稍后再试。" msgid "Possible duplicates found" msgstr "发现可能的重复" -msgid "No results found" -msgstr "没有结果" - msgid "An error occurred loading possible duplicates" msgstr "加载可能的重复项时发生错误" @@ -1145,8 +1145,35 @@ msgid "Mark for follow-up" msgstr "后续标记" msgid "Existing dates for auto-generated events will not be updated." +msgstr "自动生成的事件的现有日期将不会更新。" + +msgid "Latitude" +msgstr "纬度" + +msgid "Longitude" +msgstr "经度" + +msgid "Edit" +msgstr "编辑" + +msgid "Set coordinates" msgstr "" +msgid "Coordinates" +msgstr "坐标" + +msgid "Delete polygon" +msgstr "删除多边形" + +msgid "Close without saving" +msgstr "" + +msgid "Finish drawing before saving" +msgstr "" + +msgid "Set area" +msgstr "设置区域" + msgid "Enrollment date" msgstr "报名日期" @@ -1171,6 +1198,12 @@ msgstr "最近更新的{{date}}" msgid "Cancelled" msgstr "已取消" +msgid "Add coordinates" +msgstr "" + +msgid "Add area" +msgstr "" + msgid "Comments about this enrollment" msgstr "关于本次报名的评论" @@ -1293,9 +1326,6 @@ msgstr "无法加载个人资料窗口小部件。请稍后再试" msgid "{{TETName}} profile" msgstr "{{TETName}}个人资料" -msgid "Edit" -msgstr "编辑" - msgid "tracked entity instance" msgstr "跟踪的实体实例" @@ -1333,38 +1363,38 @@ msgid "Stages and Events" msgstr "阶段与活动" msgid "New TEI Relationship" -msgstr "" +msgstr "新的 TEI 关系" msgid "Missing implementation step" -msgstr "" +msgstr "缺少实施步骤" msgid "Go back without saving relationship" -msgstr "" +msgstr "不保存关系返回" msgid "New Relationship" -msgstr "" +msgstr "新关系" msgid "Link to an existing {{tetName}}" -msgstr "" +msgstr "链接到现有的 {{tetName}}" msgid "An error occurred while adding the relationship" -msgstr "" +msgstr "添加关系时出错" msgid "" "Something went wrong while loading relationships. Please try again later." -msgstr "" +msgstr "加载关系时出现问题。请稍后再试。" msgid "{{trackedEntityTypeName}} relationships" -msgstr "" +msgstr "{{trackedEntityTypeName}} 关系" msgid "To open this relationship, please wait until saving is complete" -msgstr "" +msgstr "要打开此关系,请等待保存完成" msgid "Type" msgstr "类型" msgid "Created date" -msgstr "" +msgstr "创建日期" msgid "Program stage name" msgstr "项目阶段名称" @@ -1543,12 +1573,6 @@ msgstr "到时间" msgid "Page {{currentPage}}" msgstr "第{{currentPage}}页" -msgid "Delete polygon" -msgstr "删除多边形" - -msgid "Set area" -msgstr "设置区域" - msgid "Area on map saved" msgstr "地图上的区域已保存" diff --git a/package.json b/package.json index 99bb762f2f..8220158c9b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "capture-app", "homepage": ".", - "version": "100.42.0", + "version": "100.49.0", "cacheVersion": "7", "serverVersion": "38", "license": "BSD-3-Clause", @@ -10,7 +10,7 @@ "packages/rules-engine" ], "dependencies": { - "@dhis2/rules-engine-javascript": "100.42.0", + "@dhis2/rules-engine-javascript": "100.49.0", "@dhis2/app-runtime": "^3.9.3", "@dhis2/d2-i18n": "^1.1.0", "@dhis2/d2-icons": "^1.0.1", diff --git a/packages/rules-engine/package.json b/packages/rules-engine/package.json index 96f533f035..d883e1427b 100644 --- a/packages/rules-engine/package.json +++ b/packages/rules-engine/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/rules-engine-javascript", - "version": "100.42.0", + "version": "100.49.0", "license": "BSD-3-Clause", "main": "./build/cjs/index.js", "scripts": { diff --git a/packages/rules-engine/src/RulesEngine.js b/packages/rules-engine/src/RulesEngine.js index bbd6f279d1..7b70025632 100644 --- a/packages/rules-engine/src/RulesEngine.js +++ b/packages/rules-engine/src/RulesEngine.js @@ -1,6 +1,6 @@ // @flow import log from 'loglevel'; -import { VariableService } from './services/VariableService/VariableService'; +import { VariableService } from './services/VariableService'; import { ValueProcessor } from './processors/ValueProcessor'; import { executeExpression } from './services/expressionService'; import { getD2Functions } from './d2Functions'; @@ -10,6 +10,7 @@ import type { IConvertInputRulesValue, IConvertOutputRulesEffectsValue, IDateUtils, + Flag, } from './rulesEngine.types'; import { getRulesEffectsProcessor } from './processors/rulesEffectsProcessor/rulesEffectsProcessor'; import { effectActions, typeof environmentTypes } from './constants'; @@ -21,18 +22,21 @@ export class RulesEngine { variableService: VariableService; dateUtils: IDateUtils; userRoles: Array; + flags: Flag; constructor( inputConverter: IConvertInputRulesValue, outputConverter: IConvertOutputRulesEffectsValue, dateUtils: IDateUtils, environment: $Values, + flags?: Flag, ) { this.inputConverter = inputConverter; this.outputConverter = outputConverter; this.valueProcessor = new ValueProcessor(inputConverter); this.variableService = new VariableService(this.valueProcessor.processValue, dateUtils, environment); this.dateUtils = dateUtils; + this.flags = flags ?? {}; } /** @@ -114,10 +118,15 @@ export class RulesEngine { expression, dhisFunctions, variablesHash, + flags: this.flags, onError: (error, injectedExpression) => log.warn( `Expression with id rule:${rule.id} could not be run. ` + `Original condition was: ${expression} - ` + `Evaluation ended up as:${injectedExpression} - error message:${error}`), + onVerboseLog: injectedExpression => console.log( + `Expression with id rule:${rule.id} was run. ` + + `Original condition was: ${expression} - ` + + `Evaluation ended up as:${injectedExpression}`), }); } else { log.warn(`Rule id:'${rule.id}' and name:'${rule.displayName}' ` + @@ -149,10 +158,15 @@ export class RulesEngine { expression: actionExpression, dhisFunctions, variablesHash, + flags: this.flags, onError: (error, injectedExpression) => log.warn( `Expression with id rule: action:${id} could not be run. ` + `Original condition was: ${actionExpression} - ` + `Evaluation ended up as:${injectedExpression} - error message:${error}`), + onVerboseLog: injectedExpression => log.info( + `Expression with id rule: action:${id} was run. ` + + `Original condition was: ${actionExpression} - ` + + `Evaluation ended up as: ${injectedExpression}`), }); } @@ -196,4 +210,12 @@ export class RulesEngine { setSelectedUserRoles(userRoles: Array) { this.userRoles = userRoles; } + + setFlags(flags: Flag) { + this.flags = flags; + } + + getFlags(): Flag { + return this.flags; + } } diff --git a/packages/rules-engine/src/helpers/previousValueCheck.js b/packages/rules-engine/src/helpers/previousValueCheck.js index 628900c6be..5745315d04 100644 --- a/packages/rules-engine/src/helpers/previousValueCheck.js +++ b/packages/rules-engine/src/helpers/previousValueCheck.js @@ -59,7 +59,7 @@ export const getOutputEffectsWithPreviousValueCheck = ({ onProcessValue: (value: any, type: $Values) => any, }) => outputEffects.reduce((acc, outputEffect) => { - if (formValues && outputEffect.targetDataType) { + if (formValues && Object.keys(formValues).length !== 0 && outputEffect.targetDataType) { const formValue = formValues[outputEffect.id]; const rawValue = mapByTargetDataTypes[outputEffect.targetDataType]({ dataElementId, diff --git a/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js b/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js index 15951dbc57..72cbd2f20f 100644 --- a/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js +++ b/packages/rules-engine/src/processors/rulesEffectsProcessor/rulesEffectsProcessor.js @@ -296,26 +296,23 @@ export function getRulesEffectsProcessor( formValues, onProcessValue, }: { - effects: ?Array, + effects: Array, dataElements: ?DataElements, trackedEntityAttributes: ?TrackedEntityAttributes, formValues?: ?{ [key: string]: any }, onProcessValue: (value: any, type: $Values) => any, }): OutputEffects { - if (effects) { - return effects - .filter(({ action }) => mapActionsToProcessor[action]) - .flatMap(effect => mapActionsToProcessor[effect.action]( - effect, - dataElements, - trackedEntityAttributes, - formValues, - onProcessValue, - )) - // when mapActionsToProcessor function returns `null` we filter those value out. - .filter(keepTruthyValues => keepTruthyValues); - } - return []; + return effects + .filter(({ action }) => mapActionsToProcessor[action]) + .flatMap(effect => mapActionsToProcessor[effect.action]( + effect, + dataElements, + trackedEntityAttributes, + formValues, + onProcessValue, + )) + // when mapActionsToProcessor function returns `null` we filter those value out. + .filter(keepTruthyValues => keepTruthyValues); } return processRulesEffects; diff --git a/packages/rules-engine/src/rulesEngine.types.js b/packages/rules-engine/src/rulesEngine.types.js index 3f1d422303..59b2fc9b62 100644 --- a/packages/rules-engine/src/rulesEngine.types.js +++ b/packages/rules-engine/src/rulesEngine.types.js @@ -230,5 +230,5 @@ export type IConvertOutputRulesEffectsValue = {| |}; export type Flag = { - debug: boolean + verbose: boolean, } diff --git a/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js b/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js index e0e1422616..7c4fcab7f8 100644 --- a/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js +++ b/packages/rules-engine/src/services/VariableService/helpers/structureEvents.js @@ -38,9 +38,9 @@ export const getStructureEvents = (compareDates: CompareDates) => { event.eventId !== currentEvent.eventId, ); - const events = [...otherEventsFiltered, currentEvent] - .sort(compareEvents); + const events = Object.keys(currentEvent).length !== 0 ? otherEventsFiltered.concat(currentEvent) : otherEventsFiltered; + const sortedEvents = events.sort(compareEvents); - return createEventsContainer(events); + return createEventsContainer(sortedEvents); }; }; diff --git a/packages/rules-engine/src/services/VariableService/variableService.types.js b/packages/rules-engine/src/services/VariableService/variableService.types.js index 5529c58a43..0a0b7e65d2 100644 --- a/packages/rules-engine/src/services/VariableService/variableService.types.js +++ b/packages/rules-engine/src/services/VariableService/variableService.types.js @@ -20,7 +20,6 @@ type EventMain = { +programStageId?: string, +programStageName?: string, +orgUnitId?: string, - +orgUnitName?: string, +trackedEntityInstanceId?: string, +enrollmentId?: string, +enrollmentStatus?: string, diff --git a/packages/rules-engine/src/services/expressionService/executeExpression.js b/packages/rules-engine/src/services/expressionService/executeExpression.js index 3e7c9c3256..470105f032 100644 --- a/packages/rules-engine/src/services/expressionService/executeExpression.js +++ b/packages/rules-engine/src/services/expressionService/executeExpression.js @@ -177,7 +177,9 @@ export const executeExpression = ({ expression, dhisFunctions, variablesHash, + flags = {}, onError, + onVerboseLog, }: ExecuteExpressionInput) => { const expressionWithInjectedVariableValues = injectVariableValues(expression, variablesHash); @@ -191,6 +193,10 @@ export const executeExpression = ({ removeNewLinesFromNonStrings(expressionWithInjectedVariableValues, expressionModuloStrings), onError, ); + + if (flags.verbose) { + onVerboseLog(expressionWithInjectedVariableValues); + } } catch (error) { onError(error.message, expressionWithInjectedVariableValues); } diff --git a/packages/rules-engine/src/services/expressionService/executeExpression.types.js b/packages/rules-engine/src/services/expressionService/executeExpression.types.js index ba88c7ed53..d680758864 100644 --- a/packages/rules-engine/src/services/expressionService/executeExpression.types.js +++ b/packages/rules-engine/src/services/expressionService/executeExpression.types.js @@ -1,5 +1,5 @@ // @flow -import type { RuleVariables } from '../../rulesEngine.types'; +import type { Flag, RuleVariables } from '../../rulesEngine.types'; import type { D2Functions, D2FunctionConfig } from '../../d2Functions'; export type ExpressionSet = $ReadOnly<{| @@ -23,5 +23,7 @@ export type ExecuteExpressionInput = $ReadOnly<{| expression: string, dhisFunctions: D2Functions, variablesHash: RuleVariables, + flags?: Flag, onError: ErrorHandler, + onVerboseLog: (expressionWithInjectedVariableValues: string) => void, |}>; diff --git a/src/components/App/App.component.js b/src/components/App/App.component.js index 694e2b1379..949eb69f6c 100644 --- a/src/components/App/App.component.js +++ b/src/components/App/App.component.js @@ -1,10 +1,14 @@ // @flow /* eslint-disable import/first */ import './app.css'; -import * as React from 'react'; +import React from 'react'; import { Provider } from 'react-redux'; import D2UIApp from '@dhis2/d2-ui-app'; import { AppContents } from './AppContents.component'; +import { + RulesEngineVerboseInitializer, +} from '../../core_modules/capture-core/components/RulesEngineVerboseInitializer'; + type Props = { store: ReduxStore, @@ -16,7 +20,9 @@ export const App = ({ store }: Props) => ( store={store} > - + + + diff --git a/src/components/App/AppContents.component.js b/src/components/App/AppContents.component.js index 6839da4d41..b476d02f5a 100644 --- a/src/components/App/AppContents.component.js +++ b/src/components/App/AppContents.component.js @@ -1,6 +1,6 @@ // @flow -import React from 'react'; +import React, { memo } from 'react'; import { withStyles } from '@material-ui/core/styles'; import { systemSettingsStore } from 'capture-core/metaDataMemoryStores'; import { FeedbackBar } from 'capture-core/components/FeedbackBar'; @@ -30,4 +30,4 @@ const Index = ({ classes }: Props) => ( ); Index.displayName = 'AppContents'; -export const AppContents = withStyles(getStyles)(Index); +export const AppContents = withStyles(getStyles)(memo(Index)); diff --git a/src/core_modules/capture-core-utils/featuresSupport/support.js b/src/core_modules/capture-core-utils/featuresSupport/support.js index 0b31bf6dd9..207c929c21 100644 --- a/src/core_modules/capture-core-utils/featuresSupport/support.js +++ b/src/core_modules/capture-core-utils/featuresSupport/support.js @@ -2,12 +2,14 @@ export const FEATURES = Object.freeze({ programStageWorkingList: 'programStageWorkingList', storeProgramStageWorkingList: 'storeProgramStageWorkingList', + customIcons: 'customIcons', }); // The first minor version that supports the feature const MINOR_VERSION_SUPPORT = Object.freeze({ [FEATURES.programStageWorkingList]: 39, [FEATURES.storeProgramStageWorkingList]: 40, + [FEATURES.customIcons]: 41, }); export const hasAPISupportForFeature = (minorVersion: string, featureName: string) => diff --git a/src/core_modules/capture-core/components/CardList/CardListItem.component.js b/src/core_modules/capture-core/components/CardList/CardListItem.component.js index d2542d3c1b..328926bdb8 100644 --- a/src/core_modules/capture-core/components/CardList/CardListItem.component.js +++ b/src/core_modules/capture-core/components/CardList/CardListItem.component.js @@ -13,6 +13,7 @@ import { searchScopes } from '../SearchBox'; import { enrollmentTypes } from './CardList.constants'; import { ListEntry } from './ListEntry.component'; import { dataElementTypes, getTrackerProgramThrowIfNotFound } from '../../metaData'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import type { ListItem, RenderCustomCardActions } from './CardList.types'; @@ -96,24 +97,24 @@ const deriveEnrollmentType = return enrollmentTypes.DONT_SHOW_TAG; }; -const deriveEnrollmentOrgUnitAndDate = (enrollments, enrollmentType, currentProgramId): {orgUnitName?: string, enrolledAt?: string} => { +const deriveEnrollmentOrgUnitIdAndDate = (enrollments, enrollmentType, currentProgramId): {orgUnitId?: string, enrolledAt?: string} => { if (!enrollments?.length) { return {}; } if (!currentProgramId && enrollments.length) { - const { orgUnitName, enrolledAt } = enrollments[0]; + const { orgUnit: orgUnitId, enrolledAt } = enrollments[0]; return { - orgUnitName, + orgUnitId, enrolledAt, }; } - const { orgUnitName, enrolledAt } = + const { orgUnit: orgUnitId, enrolledAt } = enrollments .filter(({ program }) => program === currentProgramId) .filter(({ status }) => status === enrollmentType) .sort((a, b) => moment.utc(a.lastUpdated).diff(moment.utc(b.lastUpdated)))[0] || {}; - return { orgUnitName, enrolledAt }; + return { orgUnitId, enrolledAt }; }; const deriveProgramFromEnrollment = (enrollments, currentSearchScopeType) => { @@ -137,7 +138,8 @@ const CardListItemIndex = ({ }: Props) => { const enrollments = item.tei ? item.tei.enrollments : []; const enrollmentType = deriveEnrollmentType(enrollments, currentProgramId); - const { orgUnitName, enrolledAt } = deriveEnrollmentOrgUnitAndDate(enrollments, enrollmentType, currentProgramId); + const { orgUnitId, enrolledAt } = deriveEnrollmentOrgUnitIdAndDate(enrollments, enrollmentType, currentProgramId); + const { displayName: orgUnitName } = useOrgUnitName(orgUnitId); const program = enrollments && enrollments.length ? deriveProgramFromEnrollment(enrollments, currentSearchScopeType) : undefined; diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js index 2507d1c474..8e120d4beb 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/actions/open.actionBatchs.js @@ -71,6 +71,7 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ const formId = getDataEntryKey(dataEntryId, itemId); const addFormDataActions = addFormData(`${dataEntryId}-${itemId}`, formValues); const firstStageDataEntryPropsToInclude = firstStage && getDataEntryPropsToInclude(firstStage.stageForm); + const defaultDataEntryValues = { enrolledAt: convertDateObjectToDateFormatString(new Date()) }; const dataEntryPropsToInclude = [ ...enrollmentDataEntryPropsToInclude, ...extraDataEntryProps, @@ -88,7 +89,7 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ dataEntryId, itemId, dataEntryPropsToInclude, - { enrolledAt: convertDateObjectToDateFormatString(new Date()) }, + defaultDataEntryValues, ); const effects = getApplicableRuleEffectsForTrackerProgram({ @@ -96,6 +97,7 @@ export const openDataEntryForNewEnrollmentBatchAsync = async ({ orgUnit, stage: firstStage, attributeValues: clientValues, + enrollmentData: defaultDataEntryValues, formFoundation, }); diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.js index 97d9a7165c..78e63ddf59 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.component.js @@ -4,7 +4,6 @@ import { Button, spacers } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import { withStyles } from '@material-ui/core'; import { compose } from 'redux'; -import { useHistory } from 'react-router-dom'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { scopeTypes } from '../../../metaData'; import { DiscardDialog } from '../../Dialogs/DiscardDialog.component'; @@ -14,7 +13,6 @@ import { withSaveHandler } from '../../DataEntry'; import { withLoadingIndicator } from '../../../HOC'; import { InfoIconText } from '../../InfoIconText'; import { withErrorMessagePostProcessor } from '../withErrorMessagePostProcessor'; -import { buildUrlQueryString } from '../../../utils/routing'; import { withDuplicateCheckOnSave } from '../common/TEIAndEnrollment/DuplicateCheckOnSave'; import { defaultDialogProps } from '../../Dialogs/DiscardDialog.constants'; @@ -52,6 +50,7 @@ const EnrollmentRegistrationEntryPlain = saveButtonText, classes, onSave, + onCancel, onPostProcessErrorMessage, orgUnitId, orgUnit, @@ -60,26 +59,16 @@ const EnrollmentRegistrationEntryPlain = isSavingInProgress, ...rest }: PlainProps) => { - const { push } = useHistory(); const [showWarning, setShowWarning] = useState(false); const { scopeType, trackedEntityName, programName } = useScopeInfo(selectedScopeId); const handleOnCancel = () => { if (!isUserInteractionInProgress) { - navigateToWorkingListsPage(); + onCancel(); } else { setShowWarning(true); } }; - const navigateToWorkingListsPage = () => { - const url = - scopeType === scopeTypes.TRACKER_PROGRAM - ? - buildUrlQueryString({ programId: selectedScopeId, orgUnitId }) - : - buildUrlQueryString({ orgUnitId }); - return push(`/?${url}`); - }; return ( <> @@ -130,7 +119,7 @@ const EnrollmentRegistrationEntryPlain = } { setShowWarning(false); }} /> diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js index 2da9aba5b1..92ff9612b7 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.container.js @@ -19,6 +19,7 @@ export const EnrollmentRegistrationEntry: ComponentType = ({ orgUnitId, teiId, onSave, + onCancel, ...passOnProps }) => { const { orgUnit, error } = useCoreOrgUnit(orgUnitId); @@ -68,6 +69,7 @@ export const EnrollmentRegistrationEntry: ComponentType = ({ formId={formId} formFoundation={formFoundation} id={id} + onCancel={onCancel} saveButtonText={saveButtonText(trackedEntityTypeNameLC)} ready={ready && !!enrollmentMetadata} teiId={teiId} diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types.js index d58a42aff2..be426afafc 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/EnrollmentRegistrationEntry.types.js @@ -8,11 +8,17 @@ import type { ExistingUniqueValueDialogActionsComponent } from '../withErrorMess import type { InputAttribute } from './hooks/useFormValues'; import { RenderFoundation, ProgramStage } from '../../../metaData'; +type TrackedEntityAttributes = Array<{ + attribute: string, + value: any, +}>; + export type EnrollmentPayload = {| trackedEntity: string, trackedEntityType: string, orgUnit: string, geometry: any, + attributes: TrackedEntityAttributes, enrollments: [ {| occurredAt: string, @@ -23,10 +29,7 @@ export type EnrollmentPayload = {| events: Array<{ orgUnit: string, }>, - attributes: Array<{ - attribute: string, - value: any, - }>, + attributes: TrackedEntityAttributes, |} ] |} @@ -37,6 +40,7 @@ export type OwnProps = $ReadOnly<{| selectedScopeId: string, fieldOptions?: Object, onSave: SaveForDuplicateCheck, + onCancel: () => void, duplicatesReviewPageSize: number, renderDuplicatesCardActions?: RenderCustomCardActions, renderDuplicatesDialogActions?: (onCancel: () => void, onSave: SaveForDuplicateCheck) => Node, @@ -52,6 +56,7 @@ type ContainerProps = {| ready: boolean, orgUnitId: string, orgUnit: ?OrgUnit, + onCancel: () => void, isUserInteractionInProgress: boolean, isSavingInProgress: boolean, enrollmentMetadata: RegistrationFormMetadata, diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js index 95778162b0..d6ae0f8adf 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useBuildEnrollmentPayload.js @@ -156,13 +156,15 @@ export const useBuildEnrollmentPayload = ({ ? [firstStageDuringRegistrationEvent, ...autoGenerateEvents] : autoGenerateEvents; + const attributes = deriveAttributesFromFormValues(formServerValues); + const enrollment = { program: programId, status: 'ACTIVE', orgUnit: orgUnitId, occurredAt, enrolledAt, - attributes: deriveAttributesFromFormValues(formServerValues), + attributes, events: allEventsToBeCreated, }; @@ -177,6 +179,7 @@ export const useBuildEnrollmentPayload = ({ trackedEntity: teiId || generateUID(), orgUnit: orgUnitId, trackedEntityType: trackedEntityTypeId, + attributes, geometry, enrollments: [enrollment], }; diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js index 1431a10e23..679d973ecb 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues.js @@ -8,6 +8,7 @@ import type { RenderFoundation } from '../../../../metaData'; import { convertClientToForm, convertServerToClient } from '../../../../converters'; import { subValueGetterByElementType } from './getSubValueForTei'; import type { QuerySingleResource } from '../../../../utils/api/api.types'; +import { dataElementTypes } from '../../../../metaData'; type InputProgramData = { attributes: Array<{ @@ -32,7 +33,7 @@ export type InputAttribute = { displayName: string, lastUpdated: string, value: string, - valueType: string, + valueType: $Keys, }; type InputForm = { @@ -50,7 +51,7 @@ type StaticPatternValues = { const useClientAttributesWithSubvalues = (program: InputProgramData, attributes: Array) => { const dataEngine = useDataEngine(); - const [listAttributes, setListAttributes] = useState([]); + const [listAttributes, setListAttributes] = useState(null); const getListAttributes = useCallback(async () => { if (program && attributes) { @@ -139,8 +140,6 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni const formValuesReadyRef = useRef(false); const [formValues, setFormValues] = useState({}); const [clientValues, setClientValues] = useState({}); - const areAttributesWithSubvaluesReady = - (teiId && clientAttributesWithSubvalues.length > 0) || (!teiId && clientAttributesWithSubvalues.length === 0); useEffect(() => { formValuesReadyRef.current = false; @@ -152,7 +151,7 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni formFoundation && Object.entries(formFoundation).length > 0 && formValuesReadyRef.current === false && - areAttributesWithSubvaluesReady + !!clientAttributesWithSubvalues ) { const staticPatternValues = { orgUnitCode: orgUnit.code }; const querySingleResource = makeQuerySingleResource(dataEngine.query.bind(dataEngine)); @@ -172,7 +171,6 @@ export const useFormValues = ({ program, trackedEntityInstanceAttributes, orgUni clientAttributesWithSubvalues, formValuesReadyRef, orgUnit, - areAttributesWithSubvaluesReady, searchTerms, dataEngine, ]); diff --git a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js index 4a84d7ccb7..a9b0eda796 100644 --- a/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/DataEntries/EnrollmentRegistrationEntry/hooks/useLifecycle.js @@ -45,7 +45,7 @@ export const useLifecycle = ( }); useEffect(() => { dataEntryReadyRef.current = false; - }, [teiId]); + }, [teiId, selectedScopeId]); useEffect(() => { if ( diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/Assignee/Assignee.component.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/Assignee/Assignee.component.js index 1a81f5f219..44910d1616 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/Assignee/Assignee.component.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/Assignee/Assignee.component.js @@ -7,7 +7,7 @@ import { UserField } from '../../../../../FormFields/UserField/UserField.compone const getStyles = () => ({ container: { display: 'flex', - alignItems: 'center', + alignItems: 'baseline', padding: '8px 8px 8px 12px', }, containerVertical: { diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/withMainButton.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/withMainButton.js index 8b18d2f201..66f2be3f12 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/withMainButton.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/withMainButton.js @@ -3,7 +3,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import i18n from '@dhis2/d2-i18n'; import { Button } from '@dhis2/ui'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { newEventSaveTypes } from './newEventSaveTypes'; import { getDataEntryKey } from '../../../../DataEntry/common/getDataEntryKey'; import { type RenderFoundation } from '../../../../../metaData'; diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js index 1151c1e4ae..d5d7ad6b4a 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js @@ -115,6 +115,7 @@ class NewEventNewRelationshipWrapper extends React.Component { diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js index e32730dd2c..5aa066ac07 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js @@ -24,8 +24,7 @@ const makeMapStateToProps = (): MapStateToProps => { }); }; -const mapDispatchToProps = () => ({ -}); +const mapDispatchToProps = () => ({}); const mergeProps = (stateProps: StateProps): StateProps => (stateProps); diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.types.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.types.js index b9f2259497..50f64543ad 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.types.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.types.js @@ -16,4 +16,8 @@ export type ContainerProps = {| id: string, |}; +export type DispatchProps = {| + onCancel: () => void, +|}; + export type MapStateToProps = (ReduxState, ContainerProps) => StateProps; diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js index 7b61054364..457fe3a112 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.component.js @@ -4,18 +4,16 @@ import { compose } from 'redux'; import { Button, spacers } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import { withStyles } from '@material-ui/core'; -import { useHistory } from 'react-router-dom'; import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { scopeTypes } from '../../../metaData'; import { TrackedEntityInstanceDataEntry } from '../TrackedEntityInstance'; import { useCurrentOrgUnitId } from '../../../hooks/useCurrentOrgUnitId'; -import { useCoreOrgUnit } from '../../../metadataRetrieval/coreOrgUnit'; +import { useOrgUnitName } from '../../../metadataRetrieval/orgUnitName'; import type { Props, PlainProps } from './TeiRegistrationEntry.types'; import { DiscardDialog } from '../../Dialogs/DiscardDialog.component'; import { withSaveHandler } from '../../DataEntry'; import { InfoIconText } from '../../InfoIconText'; import { withErrorMessagePostProcessor } from '../withErrorMessagePostProcessor'; -import { buildUrlQueryString } from '../../../utils/routing'; import { withDuplicateCheckOnSave } from '../common/TEIAndEnrollment/DuplicateCheckOnSave'; import { defaultDialogProps } from '../../Dialogs/DiscardDialog.constants'; import { useMetadataForRegistrationForm } from '../common/TEIAndEnrollment/useMetadataForRegistrationForm'; @@ -49,34 +47,23 @@ const TeiRegistrationEntryPlain = trackedEntityName, isUserInteractionInProgress, isSavingInProgress, + onCancel, ...rest }: PlainProps) => { - const { push } = useHistory(); const [showWarning, setShowWarning] = useState(false); const { scopeType } = useScopeInfo(selectedScopeId); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); const orgUnitId = useCurrentOrgUnitId(); - const { orgUnit } = useCoreOrgUnit(orgUnitId); // Tony: [DHIS2-15814] Change this to new hook - const orgUnitName = orgUnit ? orgUnit.name : ''; + const { displayName: orgUnitName } = useOrgUnitName(orgUnitId); const handleOnCancel = () => { if (!isUserInteractionInProgress) { - navigateToWorkingListsPage(); + onCancel(); } else { setShowWarning(true); } }; - const navigateToWorkingListsPage = () => { - const url = - scopeType === scopeTypes.TRACKER_PROGRAM - ? - buildUrlQueryString({ programId: selectedScopeId, orgUnitId }) - : - buildUrlQueryString({ orgUnitId }); - return push(`/?${url}`); - }; - return ( <> { @@ -121,7 +108,7 @@ const TeiRegistrationEntryPlain = { setShowWarning(false); }} /> diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js index 4c3b8badab..7d017181b9 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.container.js @@ -11,19 +11,37 @@ import { useFormValuesFromSearchTerms } from './hooks/useFormValuesFromSearchTer import { dataEntryHasChanges } from '../../DataEntry/common/dataEntryHasChanges'; import { useMetadataForRegistrationForm } from '../common/TEIAndEnrollment/useMetadataForRegistrationForm'; import { useBuildTeiPayload } from './hooks/useBuildTeiPayload'; +import type { InputAttribute } from '../EnrollmentRegistrationEntry/hooks/useFormValues'; -const useInitialiseTeiRegistration = (selectedScopeId, dataEntryId, orgUnitId) => { +type Props = { + selectedScopeId: string, + dataEntryId: string, + orgUnitId: string, + inheritedAttributes: ?Array, +} +const useInitialiseTeiRegistration = ({ + selectedScopeId, + dataEntryId, + orgUnitId, + inheritedAttributes, +}: Props) => { const dispatch = useDispatch(); const { scopeType, trackedEntityName } = useScopeInfo(selectedScopeId); const { formId, formFoundation } = useMetadataForRegistrationForm({ selectedScopeId }); - const formValues = useFormValuesFromSearchTerms(); + const formValues = useFormValuesFromSearchTerms({ inheritedAttributes }); const registrationFormReady = !!formId; useEffect(() => { if (registrationFormReady && scopeType === scopeTypes.TRACKED_ENTITY_TYPE) { dispatch( startNewTeiDataEntryInitialisation( - { selectedOrgUnitId: orgUnitId, selectedScopeId, dataEntryId, formFoundation, formValues }, + { + selectedOrgUnitId: orgUnitId, + selectedScopeId, + dataEntryId, + formFoundation, + formValues, + }, )); } }, [ @@ -43,8 +61,20 @@ const useInitialiseTeiRegistration = (selectedScopeId, dataEntryId, orgUnitId) = }; -export const TeiRegistrationEntry: ComponentType = ({ selectedScopeId, id, orgUnitId, onSave, ...rest }) => { - const { trackedEntityName } = useInitialiseTeiRegistration(selectedScopeId, id, orgUnitId); +export const TeiRegistrationEntry: ComponentType = ({ + selectedScopeId, + id, + orgUnitId, + onSave, + inheritedAttributes, + ...rest +}) => { + const { trackedEntityName } = useInitialiseTeiRegistration({ + selectedScopeId, + dataEntryId: id, + orgUnitId, + inheritedAttributes, + }); const ready = useSelector(({ dataEntries }) => (!!dataEntries[id])); const dataEntry = useSelector(({ dataEntries }) => (dataEntries[id])); const { diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js index 43afd9adc8..93062803b9 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/TeiRegistrationEntry.types.js @@ -6,6 +6,7 @@ import type { ExistingUniqueValueDialogActionsComponent } from '../withErrorMess import type { TeiPayload, } from '../../Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types'; +import type { InputAttribute } from '../EnrollmentRegistrationEntry/hooks/useFormValues'; export type OwnProps = $ReadOnly<{| id: string, @@ -14,8 +15,10 @@ export type OwnProps = $ReadOnly<{| saveButtonText: string, fieldOptions?: Object, onSave: (TeiPayload) => void, + onCancel: () => void, duplicatesReviewPageSize: number, isSavingInProgress?: boolean, + inheritedAttributes?: Array, renderDuplicatesCardActions?: RenderCustomCardActions, renderDuplicatesDialogActions?: (onCancel: () => void, onSave: (TeiPayload) => void) => Node, ExistingUniqueValueDialogActions: ExistingUniqueValueDialogActionsComponent, diff --git a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js index 87bdf7fa98..42c6fbff54 100644 --- a/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js +++ b/src/core_modules/capture-core/components/DataEntries/TeiRegistrationEntry/hooks/useFormValuesFromSearchTerms.js @@ -1,19 +1,32 @@ // @flow -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { convertClientToForm } from '../../../../converters'; +import type { InputAttribute } from '../../EnrollmentRegistrationEntry/hooks/useFormValues'; +type Props = { + inheritedAttributes: ?Array, +}; -export const useFormValuesFromSearchTerms = () => { +export const useFormValuesFromSearchTerms = ({ inheritedAttributes }: Props) => { const searchTerms = useSelector(({ searchDomain }) => searchDomain.currentSearchInfo.currentSearchTerms); - const [formValues, setFormValues] = useState(); - useEffect(() => { + + return useMemo(() => { + if (inheritedAttributes) { + return inheritedAttributes + ?.reduce((acc, item) => { + acc[item.attribute] = convertClientToForm(item.value, item.valueType); + return acc; + }, {}); + } if (searchTerms) { - const searchFormValues = searchTerms - ?.reduce((acc, item) => ({ ...acc, [item.id]: convertClientToForm(item.value, item.type) }), {}); - setFormValues(searchFormValues); + return searchTerms + ?.reduce((acc, item) => { + acc[item.id] = convertClientToForm(item.value, item.type); + return acc; + }, {}); } - }, [searchTerms]); - return formValues; + return null; + }, [inheritedAttributes, searchTerms]); }; diff --git a/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEIContents.component.js b/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEIContents.component.js index 8d919e73e2..7bfed86fa6 100644 --- a/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEIContents.component.js +++ b/src/core_modules/capture-core/components/DataEntries/withErrorMessagePostProcessor/UniqueTEADuplicate/ExistingTEIContents.component.js @@ -2,10 +2,7 @@ import React, { type ComponentType } from 'react'; import { withStyles } from '@material-ui/core/styles'; import i18n from '@dhis2/d2-i18n'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import DialogActions from '@material-ui/core/DialogActions'; -import { Button } from '@dhis2/ui'; +import { Button, ModalContent, ModalTitle, ModalActions } from '@dhis2/ui'; import { CardList } from '../../../CardList'; import type { Props } from './existingTeiContents.types'; @@ -35,18 +32,18 @@ const ExistingTEIContentsComponentPlain = ({ return ( - - + + {i18n.t('Registered person')} - + - - + + - - - - - ) + renderAskToCreateNewModal = () => { + if (!this.state.isOpen) { + return null; + } + + return ( + + + {i18n.t('Generate new event')} + + + {i18n.t('Do you want to create another event?')} + + + + + + + + + ); + } render() { const { onSave, ...passOnProps } = this.props; diff --git a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorAndWarningDialog.component.js b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorAndWarningDialog.component.js index 203faee0b5..0b92900d0b 100644 --- a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorAndWarningDialog.component.js +++ b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorAndWarningDialog.component.js @@ -1,12 +1,8 @@ // @flow import * as React from 'react'; import { withStyles } from '@material-ui/core'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; import i18n from '@dhis2/d2-i18n'; -import { Button } from '@dhis2/ui'; +import { Button, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; const getStyles = (theme: Theme) => ({ errors: { @@ -134,17 +130,15 @@ class ErrorAndWarningDialogPlain extends React.Component { render() { return ( - + {i18n.t('Validation errors and warnings')} - - - - {this.getContents()} - - - + + + {this.getContents()} + + {this.getButtons()} - + ); } diff --git a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorDialog.component.js b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorDialog.component.js index 8d17307161..25d60ad5f2 100644 --- a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorDialog.component.js +++ b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/ErrorDialog.component.js @@ -1,11 +1,7 @@ // @flow import * as React from 'react'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; import i18n from '@dhis2/d2-i18n'; -import { Button } from '@dhis2/ui'; +import { Button, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; type Props = { @@ -52,7 +48,7 @@ class ErrorDialogPlain extends React.Component { const { onAbort, onSave, saveEnabled, classes } = this.props; return ( -
+
@@ -73,17 +69,15 @@ class ErrorDialogPlain extends React.Component { render() { return ( - + {i18n.t('Validation errors')} - - - - {this.getContents()} - - - + + + {this.getContents()} + + {this.getButtons()} - + ); } diff --git a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/WarningDialog.component.js b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/WarningDialog.component.js index 4510553cca..ff95f00ee1 100644 --- a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/WarningDialog.component.js +++ b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/MessagesDialogContents/WarningDialog.component.js @@ -1,11 +1,7 @@ // @flow import * as React from 'react'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; import i18n from '@dhis2/d2-i18n'; -import { Button } from '@dhis2/ui'; +import { Button, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; type Props = { @@ -51,15 +47,13 @@ class WarningDialogPlain extends React.Component { const { onAbort, onSave, classes } = this.props; return ( - + {i18n.t('Validation warnings')} - - - - {this.getContents()} - - - + + + {this.getContents()} + +
-
+
); } diff --git a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/withSaveHandler.js b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/withSaveHandler.js index d8e90bfc3c..a58549fd2b 100644 --- a/src/core_modules/capture-core/components/DataEntry/withSaveHandler/withSaveHandler.js +++ b/src/core_modules/capture-core/components/DataEntry/withSaveHandler/withSaveHandler.js @@ -1,10 +1,7 @@ // @flow import * as React from 'react'; import log from 'loglevel'; -import Dialog from '@material-ui/core/Dialog'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; +import { Modal, ModalContent, ModalTitle } from '@dhis2/ui'; import { connect } from 'react-redux'; import i18n from '@dhis2/d2-i18n'; import { errorCreator } from 'capture-core-utils'; @@ -248,32 +245,34 @@ const getSaveHandler = ( onSave={this.handleSaveAttempt} {...filteredProps} /> - - - - - - {i18n.t('Operations running')} - - - + {this.state.messagesDialogOpen && ( + + + + )} + {this.state.waitForPromisesDialogOpen && ( + + + {i18n.t('Operations running')} + + {this.getDialogWaitForUploadContents()} - - - + + + )}
); } diff --git a/src/core_modules/capture-core/components/Dialogs/DiscardDialog.component.js b/src/core_modules/capture-core/components/Dialogs/DiscardDialog.component.js index 257852c7d8..926637ec0a 100644 --- a/src/core_modules/capture-core/components/Dialogs/DiscardDialog.component.js +++ b/src/core_modules/capture-core/components/Dialogs/DiscardDialog.component.js @@ -5,24 +5,32 @@ import type { Props } from './discardDialog.types'; export const DiscardDialog = ({ open, header, text, cancelText, onCancel, destructiveText, onDestroy, -}: Props) => ( - - - {header} - - - {text} - - - - - - - - -); +}: Props) => { + if (!open) { + return null; + } + + return ( + + + {header} + + +
+ {text} +
+
+ + + + + + +
+ ); +}; diff --git a/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js b/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js index c6b1fe0a49..f4406742cc 100644 --- a/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js +++ b/src/core_modules/capture-core/components/FeedbackBar/FeedbackBar.component.js @@ -1,13 +1,9 @@ // @flow import * as React from 'react'; import SnackBar from '@material-ui/core/Snackbar'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; import { withStyles } from '@material-ui/core/styles'; import { IconButton } from 'capture-ui'; -import { IconCross24, Button } from '@dhis2/ui'; +import { IconCross24, Button, Modal, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import isDefined from 'd2-utilizr/lib/isDefined'; @@ -100,25 +96,27 @@ class Index extends React.Component { message={{message}} action={this.getAction()} /> - - - { + {isDialogOpen && ( + + + { // $FlowFixMe[prop-missing] automated comment - isDialogOpen ? message && message.title : ''} - - - { + isDialogOpen ? message && message.title : ''} + + + { // $FlowFixMe[prop-missing] automated comment - isDialogOpen ? message && message.content : ''} - - - - - + isDialogOpen ? message && message.content : ''} + + + + + + )} ); } diff --git a/src/core_modules/capture-core/components/FormFields/File/D2File.component.js b/src/core_modules/capture-core/components/FormFields/File/D2File.component.js index a3c449e82a..0b87faba5f 100644 --- a/src/core_modules/capture-core/components/FormFields/File/D2File.component.js +++ b/src/core_modules/capture-core/components/FormFields/File/D2File.component.js @@ -28,6 +28,10 @@ type Props = { mutate: (data: any) => Promise } +type State = { + fileSelectorOpen: boolean, +}; + const styles = theme => ({ horizontalContainer: { display: 'flex', @@ -74,10 +78,18 @@ const styles = theme => ({ }, }); -class D2FilePlain extends Component { +class D2FilePlain extends Component { hiddenFileSelectorRef: any; + fileSelectorOpen: boolean; + constructor(props: Props) { + super(props); + this.state = { + fileSelectorOpen: false, + }; + } handleFileChange = (e: Object) => { + this.setState((state) => { state.fileSelectorOpen = false; }); e.preventDefault(); const file = e.target.files[0]; e.target.value = null; @@ -100,12 +112,23 @@ class D2FilePlain extends Component { } handleButtonClick = () => { this.hiddenFileSelectorRef.click(); + this.setState((state) => { state.fileSelectorOpen = true; }); + } + + handleCancel = () => { + this.setState((state) => { state.fileSelectorOpen = false; }); } handleRemoveClick = () => { this.props.onBlur(null); } + handleBlur = () => { + if (!this.state.fileSelectorOpen) { + this.props.onBlur(this.getFileUrl()); + } + } + getFileUrl = () => { const value = this.props.value; if (value) { @@ -122,7 +145,7 @@ class D2FilePlain extends Component { const containerClass = isVertical ? classes.verticalContainer : classes.horizontalContainer; const selectedFileTextContainerClass = isVertical ? classes.verticalSelectedFileTextContainer : classes.horizontalSelectedFileTextContainer; return ( -
+
{ this.hiddenFileSelectorRef = hiddenFileSelector; }} onChange={e => this.handleFileChange(e)} + onCancel={this.handleCancel} // eslint-disable-line react/no-unknown-property /> { (() => { diff --git a/src/core_modules/capture-core/components/FormFields/Image/D2Image.component.js b/src/core_modules/capture-core/components/FormFields/Image/D2Image.component.js index 5f9458930b..d9682c56b5 100644 --- a/src/core_modules/capture-core/components/FormFields/Image/D2Image.component.js +++ b/src/core_modules/capture-core/components/FormFields/Image/D2Image.component.js @@ -27,6 +27,10 @@ type Props = { mutate: (data: any) => Promise } +type State = { + imageSelectorOpen: boolean, +}; + const styles = theme => ({ horizontalContainer: { display: 'flex', @@ -72,10 +76,18 @@ const styles = theme => ({ }, }); -class D2ImagePlain extends Component { +class D2ImagePlain extends Component { hiddenimageSelectorRef: any; + imageSelectorOpen: boolean; + constructor(props: Props) { + super(props); + this.state = { + imageSelectorOpen: false, + }; + } handleImageChange = (e: Object) => { + this.setState((state) => { state.imageSelectorOpen = false; }); e.preventDefault(); const image = e.target.files[0]; e.target.value = null; @@ -98,13 +110,24 @@ class D2ImagePlain extends Component { } handleButtonClick = () => { this.hiddenimageSelectorRef.click(); + this.setState((state) => { state.imageSelectorOpen = true; }); + } + + handleCancel = () => { + this.setState((state) => { state.imageSelectorOpen = false; }); } handleRemoveClick = () => { this.props.onBlur(null); } - getimageUrl = () => { + handleBlur = () => { + if (!this.state.imageSelectorOpen) { + this.props.onBlur(this.getImageUrl()); + } + } + + getImageUrl = () => { const value = this.props.value; if (value) { return value.url || inMemoryFileStore.get(value.value); @@ -116,13 +139,13 @@ class D2ImagePlain extends Component { const { value, classes, asyncUIState, orientation, disabled } = this.props; const isVertical = orientation === orientations.VERTICAL; const isUploading = asyncUIState && asyncUIState.loading; - const imageUrl = this.getimageUrl(); + const imageUrl = this.getImageUrl(); // $FlowFixMe[prop-missing] automated comment const containerClass = isVertical ? classes.verticalContainer : classes.horizontalContainer; // $FlowFixMe[prop-missing] automated comment const selectedImageTextContainerClass = isVertical ? classes.verticalSelectedImageTextContainer : classes.horizontalSelectedImageTextContainer; return ( -
+
{ this.hiddenimageSelectorRef = hiddenimageSelector; }} onChange={e => this.handleImageChange(e)} + onCancel={this.handleCancel} // eslint-disable-line react/no-unknown-property /> { (() => { diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js index 9ac8d6931c..992b6b069d 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/CoordinateField/CoordinateField.component.js @@ -2,8 +2,7 @@ import * as React from 'react'; import withStyles from '@material-ui/core/styles/withStyles'; import { CoordinateField as UICoordinateField } from 'capture-ui'; -import Dialog from '@material-ui/core/Dialog'; -import DialogTitle from '@material-ui/core/DialogTitle'; +import { Modal, ModalTitle } from '@dhis2/ui'; import { typeof orientations } from '../../../New'; const getStyles = (theme: Theme) => ({ @@ -80,11 +79,12 @@ class CoordinateFieldPlain extends React.Component { // $FlowFixMe[cannot-spread-inexact] automated comment - {dialogLabel} - + {dialogLabel} + } {...passOnProps} classes={this.passOnClasses} diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.component.js index 1057e0ee76..6cb7c2db71 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.component.js @@ -39,22 +39,27 @@ const MultiSelectFieldComponentPlain = (props: Props) => { onSelect(multiSelectSelected.join(MULTI_TEXT_SEPARATOR)); }; + const handleBlur = () => { + onBlur(selected ? selected.join(MULTI_TEXT_SEPARATOR) : null); + }; + return ( - - {options.map(option => ( - - ))} - +
+ + {options.map(option => ( + + ))} + +
); }; diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.types.js b/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.types.js index 286daf7be7..c708a8b3a7 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.types.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/MultiSelectField.types.js @@ -11,7 +11,7 @@ type MultiSelectOptionConfig = { export type Props = { onSelect: (value?: string) => void, onFocus: () => void, - onBlur: () => void, + onBlur: (value: ?string) => void, options: Array, value?: string, translations: { diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/withFocusHandler.js b/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/withFocusHandler.js index 378623435f..cdbf756da5 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/withFocusHandler.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/MultiSelectField/withFocusHandler.js @@ -7,7 +7,7 @@ type Props = { onSetFocus: () => void, onRemoveFocus: () => void, inFocus: boolean, - onBlur?: ?(event: SyntheticEvent) => void, + onBlur?: (value: ?string) => void, onFocus: () => void, classes: { inputWrapperFocused: string, @@ -15,10 +15,11 @@ type Props = { } }; -export const withFocusHandler = () => (InnerCompnent: React.ComponentType) => +export const withFocusHandler = () => (InnerComponent: React.ComponentType) => class FocusHandlerHOC extends React.Component { - handleRemoveFocus = () => { + handleRemoveFocus = (value: string) => { this.props.onRemoveFocus(); + this.props.onBlur && this.props.onBlur(value); } handleFocus = () => { @@ -40,7 +41,7 @@ export const withFocusHandler = () => (InnerCompnent: React.ComponentType) className={classNames(defaultClasses.inputWrapper, inputWrapper)} > {/* $FlowFixMe[cannot-spread-inexact] automated comment */} - ({ type Props = { onSelectClick: (selectedOrgUnit: Object) => void, + onBlur: (selectedOrgUnit: Object) => void, selected?: ?string, maxTreeHeight?: ?number, disabled?: ?boolean, @@ -56,6 +57,7 @@ type Props = { const OrgUnitFieldPlain = (props: Props) => { const { onSelectClick, + onBlur, classes, selected, maxTreeHeight, @@ -136,10 +138,15 @@ const OrgUnitFieldPlain = (props: Props) => { setSearchText(event.currentTarget.value); }; + const handleBlur = () => { + onBlur && onBlur(null); + }; + const styles = maxTreeHeight ? { maxHeight: maxTreeHeight, overflowY: 'auto' } : null; return (
({ type OrgUnitValue = { id: string, - name: string, + displayName: string, path: string, } @@ -44,7 +44,7 @@ class SingleOrgUnitSelectFieldPlain extends React.Component { const { classes } = this.props; return (
- {selectedOrgUnit.name} + {selectedOrgUnit.displayName}
); } @@ -52,7 +52,7 @@ class SingleOrgUnitSelectFieldPlain extends React.Component { onSelectOrgUnit = (orgUnit: Object) => { this.props.onBlur({ id: orgUnit.id, - name: orgUnit.displayName, + displayName: orgUnit.displayName, path: orgUnit.path, }); } diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js index 058d82c9c9..47831a9a47 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/PolygonField/PolygonField.component.js @@ -2,7 +2,7 @@ import * as React from 'react'; import withStyles from '@material-ui/core/styles/withStyles'; import { PolygonField as UIPolygonField } from 'capture-ui'; -import { Dialog, DialogTitle } from '@material-ui/core'; +import { Modal, ModalTitle } from '@dhis2/ui'; import { typeof orientations } from '../../../New'; const getStyles = () => ({ @@ -44,11 +44,12 @@ class PolygonFieldPlain extends React.Component { // $FlowFixMe[cannot-spread-inexact] automated comment - {dialogLabel} - + {dialogLabel} + } {...passOnProps} /> diff --git a/src/core_modules/capture-core/components/FormFields/Options/SelectVirtualizedV2/OptionsSelectVirtualized.component.js b/src/core_modules/capture-core/components/FormFields/Options/SelectVirtualizedV2/OptionsSelectVirtualized.component.js index f9a32a1403..e1af06802f 100644 --- a/src/core_modules/capture-core/components/FormFields/Options/SelectVirtualizedV2/OptionsSelectVirtualized.component.js +++ b/src/core_modules/capture-core/components/FormFields/Options/SelectVirtualizedV2/OptionsSelectVirtualized.component.js @@ -193,6 +193,10 @@ class OptionsSelectVirtualizedPlain extends React.Component { ); } + onBlur = () => { + this.props.onSelect(this.props.value); + } + render() { const { options, @@ -220,6 +224,7 @@ class OptionsSelectVirtualizedPlain extends React.Component { >
{/* $FlowFixMe[cannot-spread-inexact] automated comment */} { focusSelectedInput.current = true; }; + const handleBlur = () => { + // $FlowExpectedError + onSet(value); + }; + if (value) { return ( { } return ( -
+
({ + noMatchFound: { + color: colors.red600, + fontSize: theme.typography.pxToRem(14), + }, +}); + type Props = { onSet: (user: User) => void, inputWrapperClasses: Object, @@ -18,6 +27,7 @@ type Props = { inputPlaceholderText?: ?string, useUpwardList?: ?boolean, querySingleResource: QuerySingleResource, + ...CssClasses, }; type State = { @@ -26,6 +36,7 @@ type State = { suggestionsError?: ?string, highlightedSuggestion?: ?User, inputKey: number, + noMatch: boolean, }; const exitBehaviours = { @@ -46,6 +57,7 @@ class UserSearchPlain extends React.Component { suggestions: [], searchValue: '', inputKey: 0, + noMatch: false, }; this.domNames = { @@ -82,6 +94,7 @@ class UserSearchPlain extends React.Component { suggestions, highlightedSuggestion: undefined, searchValue, + noMatch: suggestions.length === 0, }); } @@ -90,6 +103,7 @@ class UserSearchPlain extends React.Component { suggestions: [], highlightedSuggestion: undefined, searchValue: '', + noMatch: false, }); } @@ -133,11 +147,14 @@ class UserSearchPlain extends React.Component { id: au.id, name: au.displayName, username: au.username, + firstName: au.firstName, + surname: au.surname, })); }); handleInputChange = (value: string) => { this.cancelablePromise && this.cancelablePromise.cancel(); + this.setState({ noMatch: false }); if (value.length > 1) { const searchPromise = this.search(value); @@ -294,6 +311,15 @@ class UserSearchPlain extends React.Component { ); } + renderNoMatchFound() { + const { noMatch } = this.state; + const { classes } = this.props; + + return noMatch ? ( + {i18n.t('No results found')} + ) : null; + } + render() { return (
@@ -301,6 +327,7 @@ class UserSearchPlain extends React.Component { value={this.domNames} > {this.renderInput()} + {this.renderNoMatchFound()} {this.renderSuggestions()}
@@ -308,4 +335,4 @@ class UserSearchPlain extends React.Component { } } -export const UserSearch = withApiUtils(UserSearchPlain); +export const UserSearch = withStyles(getStyles)(withApiUtils(UserSearchPlain)); diff --git a/src/core_modules/capture-core/components/FormFields/UserField/index.js b/src/core_modules/capture-core/components/FormFields/UserField/index.js index 3e88ffe311..e534c4387a 100644 --- a/src/core_modules/capture-core/components/FormFields/UserField/index.js +++ b/src/core_modules/capture-core/components/FormFields/UserField/index.js @@ -1,2 +1,3 @@ // @flow export { UserField } from './UserField.component'; +export type { User as UserFormField } from './types'; diff --git a/src/core_modules/capture-core/components/FormFields/UserField/types.js b/src/core_modules/capture-core/components/FormFields/UserField/types.js index 0695b283f1..c8d34e711a 100644 --- a/src/core_modules/capture-core/components/FormFields/UserField/types.js +++ b/src/core_modules/capture-core/components/FormFields/UserField/types.js @@ -3,4 +3,6 @@ export type User = { id: string, username: string, name: string, + firstName: string, + surname: string, }; diff --git a/src/core_modules/capture-core/components/ListView/ColumnSelector/ColumnSelectorDialog.component.js b/src/core_modules/capture-core/components/ListView/ColumnSelector/ColumnSelectorDialog.component.js index 6cbe5aa034..ff0bad9c19 100644 --- a/src/core_modules/capture-core/components/ListView/ColumnSelector/ColumnSelectorDialog.component.js +++ b/src/core_modules/capture-core/components/ListView/ColumnSelector/ColumnSelectorDialog.component.js @@ -1,11 +1,7 @@ // @flow import React, { useState, useEffect } from 'react'; import { isEqual } from 'lodash'; -import { Button } from '@dhis2/ui'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; +import { Modal, ModalTitle, ModalContent, ModalActions, Button } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import { DragDropList } from './DragDropList'; @@ -40,23 +36,31 @@ export const ColumnSelectorDialog = ({ columns, open, onClose, onSave }: Props) setColumnList(sortedList); }; + if (!open) { + return null; + } + return ( - - {i18n.t('Columns to show in table')} - + + {i18n.t('Columns to show in table')} + - - + + - - + + ); }; diff --git a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js index 6541747f96..8a1c83ddd1 100644 --- a/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js +++ b/src/core_modules/capture-core/components/ListView/Filters/FilterButton/FilterButtonMain.component.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { withStyles } from '@material-ui/core/styles'; import Popover from '@material-ui/core/Popover'; import { IconChevronDown16, IconChevronUp16, Button } from '@dhis2/ui'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { ActiveFilterButton } from './ActiveFilterButton.component'; import { FilterSelectorContents } from '../Contents'; import type { UpdateFilter, ClearFilter, RemoveFilter } from '../../types'; diff --git a/src/core_modules/capture-core/components/LoadingMasks/DialogLoadingMask.component.js b/src/core_modules/capture-core/components/LoadingMasks/DialogLoadingMask.component.js index 0366b2b095..1acc9e6a1c 100644 --- a/src/core_modules/capture-core/components/LoadingMasks/DialogLoadingMask.component.js +++ b/src/core_modules/capture-core/components/LoadingMasks/DialogLoadingMask.component.js @@ -1,8 +1,6 @@ // @flow import React, { Component } from 'react'; -import { CircularLoader } from '@dhis2/ui'; -import Dialog from '@material-ui/core/Dialog'; -import DialogContent from '@material-ui/core/DialogContent'; +import { CircularLoader, Modal, ModalContent } from '@dhis2/ui'; type Props = { }; @@ -10,13 +8,13 @@ type Props = { export class DialogLoadingMask extends Component { render() { return ( - - + - - + + ); } } diff --git a/src/core_modules/capture-core/components/NonBundledDhis2Icon/NonBundledDhis2Icon.component.js b/src/core_modules/capture-core/components/NonBundledDhis2Icon/NonBundledDhis2Icon.component.js index b16ac14c1e..c6dc0002eb 100644 --- a/src/core_modules/capture-core/components/NonBundledDhis2Icon/NonBundledDhis2Icon.component.js +++ b/src/core_modules/capture-core/components/NonBundledDhis2Icon/NonBundledDhis2Icon.component.js @@ -1,13 +1,20 @@ // @flow import React from 'react'; import { useConfig } from '@dhis2/app-runtime'; -import { buildUrl } from 'capture-core-utils'; +import { buildUrl, FEATURES, useFeature } from 'capture-core-utils'; import { NonBundledIcon } from 'capture-ui'; import type { Props } from './nonBundledDhis2Icon.types'; export const NonBundledDhis2Icon = ({ name, alternativeText = name, ...passOnProps }: Props) => { + const supportCustomIcons = useFeature(FEATURES.customIcons); const { baseUrl, apiVersion } = useConfig(); - const source = name && buildUrl(baseUrl, `api/${apiVersion}/icons/${name}/icon.svg`); + let source; + + if (name) { + source = buildUrl(baseUrl, `api/${apiVersion}/icons/${name}/icon`); + // Append .svg to source if supportCustomIcons is false (feature flag v41) + source = supportCustomIcons ? source : `${source}.svg`; + } return ( { const [mainContentVisible, setMainContentVisibility] = useState(true); const [addRelationShipContainerElement, setAddRelationshipContainerElement] = - useState(undefined); + useState(undefined); const toggleVisibility = useCallback(() => setMainContentVisibility(current => !current), []); diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/QuickActionButton/QuickActionButton.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/QuickActionButton/QuickActionButton.js index 7727a05019..ae60c51371 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/QuickActionButton/QuickActionButton.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/EnrollmentQuickActions/QuickActionButton/QuickActionButton.js @@ -3,7 +3,7 @@ import React from 'react'; import i18n from '@dhis2/d2-i18n'; import { Button, spacers } from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import type { QuickActionButtonTypes } from './QuickActionButton.types'; const styles = { diff --git a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js index 3c8b50285a..7d692e9867 100644 --- a/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js +++ b/src/core_modules/capture-core/components/Pages/Enrollment/EnrollmentPageDefault/hooks/useProgramStages.js @@ -1,11 +1,12 @@ // @flow +import { useMemo } from 'react'; import log from 'loglevel'; import { errorCreator } from 'capture-core-utils'; import i18n from '@dhis2/d2-i18n'; import type { apiProgramStage } from 'capture-core/metaDataStoreLoaders/programs/quickStoreOperations/types'; import { Program } from '../../../../../metaData'; -export const useProgramStages = (program: Program, programStages?: Array) => { +export const useProgramStages = (program: Program, programStages?: Array) => useMemo(() => { const stages = []; if (program && programStages) { program.stages.forEach((item) => { @@ -48,4 +49,4 @@ export const useProgramStages = (program: Program, programStages?: Array @@ -10,7 +11,7 @@ type InputAttribute = { displayName: string, lastUpdated: string, value: string, - valueType: string, + valueType: $Keys, }; export type ContainerProps = $ReadOnly<{| diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.component.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.component.js index af18875d9d..308671c147 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.component.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.component.js @@ -1,6 +1,7 @@ // @flow import React, { type ComponentType, useContext, useCallback } from 'react'; import { useDispatch } from 'react-redux'; +import { useHistory } from 'react-router'; import i18n from '@dhis2/d2-i18n'; import { Button, colors, spacers } from '@dhis2/ui'; import { Grid, withStyles } from '@material-ui/core'; @@ -13,7 +14,7 @@ import { TrackedEntityTypeSelector } from '../../../TrackedEntityTypeSelector'; import { DataEntryWidgetOutput } from '../../../DataEntryWidgetOutput/DataEntryWidgetOutput.container'; import { ResultsPageSizeContext } from '../../shared-contexts'; import { navigateToEnrollmentOverview } from '../../../../actions/navigateToEnrollmentOverview/navigateToEnrollmentOverview.actions'; -import { useLocationQuery } from '../../../../utils/routing'; +import { buildUrlQueryString, useLocationQuery } from '../../../../utils/routing'; import { EnrollmentRegistrationEntryWrapper } from '../EnrollmentRegistrationEntryWrapper.component'; import { useCurrentOrgUnitId } from '../../../../hooks/useCurrentOrgUnitId'; @@ -94,18 +95,29 @@ const RegistrationDataEntryPlain = ({ teiId, trackedEntityInstanceAttributes, }: Props) => { + const { push } = useHistory(); const { resultsPageSize } = useContext(ResultsPageSizeContext); const { scopeType, programName, trackedEntityName } = useScopeInfo(selectedScopeId); const titleText = useScopeTitleText(selectedScopeId); const currentOrgUnitId = useCurrentOrgUnitId(); + const onCancel = useCallback(() => { + let url; + if (scopeType === scopeTypes.TRACKER_PROGRAM) { + url = buildUrlQueryString({ programId: selectedScopeId, orgUnitId: currentOrgUnitId }); + } else { + url = buildUrlQueryString({ orgUnitId: currentOrgUnitId }); + } + return push(`/?${url}`); + }, [currentOrgUnitId, push, scopeType, selectedScopeId]); + const handleRegistrationScopeSelection = (id) => { setScopeId(id); }; - const renderDuplicatesDialogActions = useCallback((onCancel, onSave) => ( + const renderDuplicatesDialogActions = useCallback((callbackOnCancel, onSave) => ( ), []); @@ -180,6 +192,7 @@ const RegistrationDataEntryPlain = ({ teiId={teiId} selectedScopeId={selectedScopeId} onSave={onSaveWithEnrollment} + onCancel={onCancel} saveButtonText={(trackedEntityTypeNameLC: string) => i18n.t('Save {{trackedEntityTypeName}}', { trackedEntityTypeName: trackedEntityTypeNameLC, interpolation: { escapeValue: false }, @@ -232,6 +245,7 @@ const RegistrationDataEntryPlain = ({ id={dataEntryId} selectedScopeId={selectedScopeId} orgUnitId={currentOrgUnitId} + onCancel={onCancel} saveButtonText={i18n.t('Save {{trackedEntityName}}', { trackedEntityName, interpolation: { escapeValue: false }, diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js index 79a07ea96e..41330409c8 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.types.js @@ -1,5 +1,7 @@ // @flow +import { dataElementTypes } from '../../../../metaData'; + type InputAttribute = { attribute: string, code: string, @@ -7,7 +9,7 @@ type InputAttribute = { displayName: string, lastUpdated: string, value: string, - valueType: string, + valueType: $Keys, }; export type OwnProps = $ReadOnly<{| @@ -15,7 +17,7 @@ export type OwnProps = $ReadOnly<{| selectedScopeId: string, dataEntryId: string, teiId?: ?string, - trackedEntityInstanceAttributes?: Array + trackedEntityInstanceAttributes?: Array, |}>; type ContainerProps = {| diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/NewRelationship.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/NewRelationship.component.js index 1ebcfad048..6ff7297604 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/NewRelationship.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/NewRelationship.component.js @@ -9,6 +9,7 @@ import { RelationshipNavigation } from './RelationshipNavigation/RelationshipNav type Props = { onAddRelationship: (relationshipType: { id: string, name: string }, entity: Object, entityType: string) => void, + onCancel: () => {}, classes: { container: string, }, diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js index 19d78a8c31..42dc5b3760 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js @@ -12,6 +12,7 @@ const NewEnrollmentRelationshipPlain = ({ theme, onSave, + onCancel, programId, duplicatesReviewPageSize, renderDuplicatesDialogActions, @@ -33,6 +34,7 @@ const NewEnrollmentRelationshipPlain = interpolation: { escapeValue: false }, })} onSave={onSave} + onCancel={onCancel} duplicatesReviewPageSize={duplicatesReviewPageSize} renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js index 452fdf271f..38612d6fda 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js @@ -12,6 +12,7 @@ export type Props = {| programId: string, enrollmentMetadata?: Enrollment, onSave: SaveForEnrollmentAndTeiRegistration, + onCancel: () => void, duplicatesReviewPageSize: number, renderDuplicatesCardActions?: RenderCustomCardActions, renderDuplicatesDialogActions?: (onCancel: () => void, onSave: SaveForEnrollmentAndTeiRegistration) => Node, diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.component.js index 2f04955d2a..baf4bd3e05 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.component.js @@ -12,6 +12,7 @@ const RelationshipTrackedEntityInstancePlain = ({ theme, onSave, + onCancel, teiRegistrationMetadata = {}, duplicatesReviewPageSize, renderDuplicatesDialogActions, @@ -35,6 +36,7 @@ const RelationshipTrackedEntityInstancePlain = })} fieldOptions={fieldOptions} onSave={onSave} + onCancel={onCancel} duplicatesReviewPageSize={duplicatesReviewPageSize} renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js index 20f5463e75..0a49a28dc5 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js @@ -12,6 +12,7 @@ import type { export type Props = {| theme: Theme, onSave: (TeiPayload) => void, + onCancel: () => void, teiRegistrationMetadata?: TeiRegistration, duplicatesReviewPageSize: number, renderDuplicatesCardActions?: RenderCustomCardActions, diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js index ee4e752c8e..6269d5ec2a 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.component.js @@ -60,6 +60,7 @@ const RegisterTeiPlain = ({ itemId, onLink, onSave, + onCancel, onGetUnsavedAttributeValues, trackedEntityName, newRelationshipProgramId, @@ -75,9 +76,9 @@ const RegisterTeiPlain = ({ /> ), [onLink]); - const renderDuplicatesDialogActions = useCallback((onCancel, onSaveArgument) => ( + const renderDuplicatesDialogActions = useCallback((callbackOnCancel, onSaveArgument) => ( @@ -104,6 +105,7 @@ const RegisterTeiPlain = ({ }) => (programId || trackedEntityTypeId), ); -export const RegisterTei = ({ onLink, onSave, onGetUnsavedAttributeValues }: OwnProps) => { +export const RegisterTei = ({ onLink, onSave, onCancel, onGetUnsavedAttributeValues }: OwnProps) => { const dataEntryId = 'relationship'; const itemId = useSelector(({ dataEntries }) => dataEntries[dataEntryId]?.itemId); const error = useSelector(({ newRelationshipRegisterTei }) => (newRelationshipRegisterTei.error)); @@ -30,6 +30,7 @@ export const RegisterTei = ({ onLink, onSave, onGetUnsavedAttributeValues }: Own itemId={itemId} onLink={onLink} onSave={onSave} + onCancel={onCancel} onGetUnsavedAttributeValues={onGetUnsavedAttributeValues} trackedEntityName={trackedEntityName} newRelationshipProgramId={newRelationshipProgramId} diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js index ab47e2ca52..d1dc483194 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/RegisterTei/RegisterTei.types.js @@ -9,6 +9,7 @@ type PropsFromRedux = {| export type OwnProps = {| onLink: (teiId: string, values: Object) => void, + onCancel: () => void, onGetUnsavedAttributeValues?: ?Function, onSave: (itemId: string, dataEntryId: string) => void, |}; diff --git a/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js b/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js index 976dcbf749..863327b37f 100644 --- a/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js +++ b/src/core_modules/capture-core/components/Pages/NewRelationship/TeiRelationship/TeiRelationship.component.js @@ -20,6 +20,7 @@ type Props = { onOpenSearch: (trackedEntityTypeId: string, programId: ?string) => void, onSelectFindMode: (findMode: $Values) => void, onAddRelationship: (entity: Object) => void, + onCancel: () => void, selectedRelationshipType: SelectedRelationshipType, classes: { container: string, @@ -143,6 +144,7 @@ class TeiRelationshipPlain extends React.Component { diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js index 31da7bdbd4..7f6a2a440f 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js @@ -5,7 +5,7 @@ import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; import { withStyles } from '@material-ui/core/'; import { spacers, IconFileDocument24, Button } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { ViewEventSection } from '../Section/ViewEventSection.component'; import { ViewEventSectionHeader } from '../Section/ViewEventSectionHeader.component'; import { EditEventDataEntry } from '../../../WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container'; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventNewRelationshipWrapper.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventNewRelationshipWrapper.component.js index 998b86f7a7..d3cf907c06 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventNewRelationshipWrapper.component.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/Relationship/ViewEventNewRelationshipWrapper.component.js @@ -102,6 +102,7 @@ class ViewEventNewRelationshipWrapperPlain extends React.Component {/* $FlowFixMe[cannot-spread-inexact] automated comment */} @@ -110,7 +111,7 @@ class ViewEventNewRelationshipWrapperPlain extends React.Component text={i18n.t('Leaving this page will discard any selections you made for a new relationship')} destructiveText={i18n.t('Yes, discard changes')} cancelText={i18n.t('No, cancel')} - onDestroy={this.props.onCancel} + onDestroy={onCancel} open={!!this.state.discardDialogOpen} onCancel={this.handleCancelDiscard} /> diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js index 6ee03b475f..688e293955 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js @@ -14,7 +14,6 @@ export type Event = {| occurredAt: string, updatedAt: string, orgUnit: string, - orgUnitName: string, program: string, programStage: string, status: 'ACTIVE' | 'VISITED' | 'COMPLETED' | 'SCHEDULE' | 'OVERDUE' | 'SKIPPED', @@ -34,13 +33,13 @@ export type EnrollmentData = {| updatedAt: string, updatedAtClient: string, orgUnit: string, - orgUnitName: string, program: string, status: string, storedBy: string, scheduledAt: string, trackedEntity: string, trackedEntityType: string, + geometry?: ?{ type: string, coordinates: [number, number] | Array<[number, number]>} |}; export type AttributeValue = {| diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js index 74f6a96735..d8ff56e1b7 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js @@ -33,7 +33,6 @@ const useEventsData = (enrollment, program) => { programId: event.program, programStageId: event.programStage, orgUnitId: event.orgUnit, - orgUnitName: event.orgUnitName, trackedEntityInstanceId: event.trackedEntityInstance, enrollmentId: event.enrollment, enrollmentStatus: event.enrollmentStatus, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js index 2dbf0c7a20..cea2f79f8f 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/DataEntryEnrollment.component.js @@ -11,7 +11,9 @@ const NewEnrollmentRelationshipPlain = ({ theme, onSave, + onCancel, programId, + inheritedAttributes, orgUnitId, duplicatesReviewPageSize, renderDuplicatesDialogActions, @@ -31,10 +33,12 @@ const NewEnrollmentRelationshipPlain = interpolation: { escapeValue: false }, })} onSave={onSave} + onCancel={onCancel} duplicatesReviewPageSize={duplicatesReviewPageSize} renderDuplicatesDialogActions={renderDuplicatesDialogActions} renderDuplicatesCardActions={renderDuplicatesCardActions} ExistingUniqueValueDialogActions={ExistingUniqueValueDialogActions} + trackedEntityInstanceAttributes={inheritedAttributes} /> ); }; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js index de3f6cee6f..4ab96c8c52 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/Enrollment/dataEntryEnrollment.types.js @@ -6,13 +6,16 @@ import type { SaveForEnrollmentAndTeiRegistration, ExistingUniqueValueDialogActionsComponent, } from '../../../../../../DataEntries'; +import type { InputAttribute } from '../../../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; export type Props = {| theme: Theme, programId: string, orgUnitId: string, + inheritedAttributes: Array, enrollmentMetadata?: Enrollment, onSave: SaveForEnrollmentAndTeiRegistration, + onCancel: () => void, duplicatesReviewPageSize: number, renderDuplicatesCardActions?: RenderCustomCardActions, renderDuplicatesDialogActions?: (onCancel: () => void, onSave: SaveForEnrollmentAndTeiRegistration) => Node, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js index 292a1d1530..940aa904fd 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/RegisterTeiDataEntry.component.js @@ -12,7 +12,13 @@ type Props = { export class RegisterTeiDataEntryComponent extends React.Component { render() { - const { showDataEntry, programId, onSaveWithoutEnrollment, onSaveWithEnrollment, ...passOnProps } = this.props; + const { + showDataEntry, + programId, + onSaveWithoutEnrollment, + onSaveWithEnrollment, + ...passOnProps + } = this.props; if (!showDataEntry) { return null; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js index c455e3616d..0680da0fa1 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/DataEntryTrackedEntityInstance.js @@ -13,7 +13,9 @@ const RelationshipTrackedEntityInstancePlain = ({ theme, onSave, + onCancel, trackedEntityTypeId, + inheritedAttributes, duplicatesReviewPageSize, renderDuplicatesDialogActions, renderDuplicatesCardActions, @@ -36,6 +38,8 @@ const RelationshipTrackedEntityInstancePlain = orgUnitId={orgUnitId} teiRegistrationMetadata={teiRegistrationMetadata} selectedScopeId={teiRegistrationMetadata.form.id} + inheritedAttributes={inheritedAttributes} + onCancel={onCancel} saveButtonText={i18n.t('Save new {{trackedEntityTypeName}} and link', { trackedEntityTypeName: trackedEntityTypeNameLC, interpolation: { escapeValue: false }, })} diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js index b4f9617052..e473cb96f8 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/DataEntry/TrackedEntityInstance/dataEntryTrackedEntityInstance.types.js @@ -5,6 +5,7 @@ import type { RenderCustomCardActions } from '../../../../../../CardList'; import type { ExistingUniqueValueDialogActionsComponent, } from '../../../../../../DataEntries'; +import type { InputAttribute } from '../../../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; export type TeiPayload = {| trackedEntity: string, @@ -22,7 +23,9 @@ export type Props = {| theme: Theme, trackedEntityTypeId: string, onSave: TeiPayload => void, + onCancel: () => void, teiRegistrationMetadata?: TeiRegistration, + inheritedAttributes: Array, duplicatesReviewPageSize: number, renderDuplicatesCardActions?: RenderCustomCardActions, renderDuplicatesDialogActions?: (onCancel: () => void, onSave: (TeiPayload) => void) => Node, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js index 9a9a7070af..c22f4bc5cf 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.component.js @@ -58,12 +58,14 @@ const DialogButtons = ({ onCancel, onSave, trackedEntityName }) => ( const RegisterTeiPlain = ({ dataEntryId, onLink, + onCancel, onSaveWithoutEnrollment, onSaveWithEnrollment, onGetUnsavedAttributeValues, trackedEntityName, trackedEntityTypeId, selectedScopeId, + inheritedAttributes, classes, }: ComponentProps) => { const { resultsPageSize } = useContext(ResultsPageSizeContext); @@ -76,9 +78,9 @@ const RegisterTeiPlain = ({ /> ), [onLink]); - const renderDuplicatesDialogActions = useCallback((onCancel, onSaveArgument) => ( + const renderDuplicatesDialogActions = useCallback((callbackOnCancel, onSaveArgument) => ( @@ -102,6 +104,7 @@ const RegisterTeiPlain = ({ />
{ @@ -16,11 +19,21 @@ export const RegisterTei = ({ const error = useSelector(({ newRelationshipRegisterTei }) => (newRelationshipRegisterTei.error)); const selectedScopeId = suggestedProgramId || trackedEntityTypeId; const { trackedEntityName } = useScopeInfo(selectedScopeId); + const { inheritedAttributes, isLoading: isLoadingAttributes } = useInheritedAttributeValues({ + teiId, + trackedEntityTypeId, + programId: suggestedProgramId, + }); + + if (isLoadingAttributes) { + return null; + } return ( ); }; diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js index 0de2b570ba..70fbd7c041 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/RegisterTei/RegisterTei.types.js @@ -1,12 +1,16 @@ // @flow +import type { InputAttribute } from '../../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; + export type SharedProps = {| onLink: (teiId: string, values: Object) => void, onGetUnsavedAttributeValues?: ?Function, trackedEntityTypeId: string, + onCancel: () => void, |}; export type ContainerProps = {| suggestedProgramId: string, + teiId: string, onSave: (teiPayload: Object) => void, ...SharedProps, |}; @@ -16,6 +20,7 @@ export type ComponentProps = {| error: string, dataEntryId: string, trackedEntityName: ?string, + inheritedAttributes: Array, onSaveWithEnrollment: () => void, onSaveWithoutEnrollment: () => void, ...SharedProps, diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js index df27fb1125..7be6a4a07b 100644 --- a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/TrackedEntityRelationshipsWrapper/TrackedEntityRelationshipsWrapper.component.js @@ -47,7 +47,7 @@ export const TrackedEntityRelationshipsWrapper = ({ ); } - if (!relationshipTypes || !addRelationshipRenderElement) { + if (!relationshipTypes?.length || !addRelationshipRenderElement) { return null; } @@ -70,14 +70,17 @@ export const TrackedEntityRelationshipsWrapper = ({ suggestedProgramId, onLinkToTrackedEntityFromRegistration, onLinkToTrackedEntityFromSearch, + onCancel, ) => ( console.log('get unsaved')} trackedEntityTypeId={selectedTrackedEntityTypeId} + onCancel={onCancel} /> )} diff --git a/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js new file mode 100644 index 0000000000..8c526525dd --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/common/TEIRelationshipsWidget/useInheritedAttributeValues.js @@ -0,0 +1,70 @@ +// @flow +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import type { InputAttribute } from '../../../DataEntries/EnrollmentRegistrationEntry/hooks/useFormValues'; +import { useApiDataQuery } from '../../../../utils/reactQueryHelpers'; +import { + getProgramFromProgramIdThrowIfNotFound, + getTrackedEntityTypeThrowIfNotFound, + TrackerProgram, +} from '../../../../metaData'; + +type Props = { + teiId: string, + trackedEntityTypeId: string, +}; + +type Return = { + inheritedAttributes: Array, + isLoading: boolean, +}; +export const useInheritedAttributeValues = ({ teiId, trackedEntityTypeId }: Props): Return => { + const programId = useSelector(({ newRelationshipRegisterTei }) => newRelationshipRegisterTei.programId); + const inheritedAttributeIds = useMemo(() => { + const attributeIds = new Set(); + + if (programId) { + const program = getProgramFromProgramIdThrowIfNotFound(programId); + if (program instanceof TrackerProgram) { + program.attributes.forEach((attribute) => { + if (attribute.inherit) { + attributeIds.add(attribute.id); + } + }); + } + return attributeIds; + } + + const trackedEntityType = getTrackedEntityTypeThrowIfNotFound(trackedEntityTypeId); + trackedEntityType.attributes.forEach((attribute) => { + if (attribute.inherit) { + attributeIds.add(attribute.id); + } + }); + return attributeIds; + }, [programId, trackedEntityTypeId]); + + + const { data, isLoading } = useApiDataQuery( + ['inheritedAttributeValues', teiId, programId], + { + resource: 'tracker/trackedEntities', + id: teiId, + params: { + fields: ['attributes'], + program: programId, + }, + }, { + enabled: !!teiId, + select: (response) => { + const attributes = response.attributes || []; + return attributes + .filter(attribute => inheritedAttributeIds.has(attribute.attribute)); + }, + }); + + return { + inheritedAttributes: data ?? [], + isLoading, + }; +}; diff --git a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/PossibleDuplicatesDialog.component.js b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/PossibleDuplicatesDialog.component.js index 5a35b3ff34..85a1fccc19 100644 --- a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/PossibleDuplicatesDialog.component.js +++ b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/PossibleDuplicatesDialog.component.js @@ -1,8 +1,6 @@ // @flow import * as React from 'react'; -import { withStyles } from '@material-ui/core'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; +import { Modal, ModalActions } from '@dhis2/ui'; import { ReviewDialogContents } from './ReviewDialogContents/ReviewDialogContents.container'; import type { RenderCustomCardActions } from '../CardList/CardList.types'; @@ -15,37 +13,29 @@ type Props = {| selectedScopeId: string |}; -const StyledDialogActions = withStyles({ - root: { margin: 24 }, -})(DialogActions); - class ReviewDialogClass extends React.Component { - static paperProps = { - style: { - maxHeight: 'calc(100% - 100px)', - }, - }; - render() { const { open, onCancel, extraActions, selectedScopeId, dataEntryId, renderCardActions } = this.props; + if (!open) { + return null; + } + return ( - - + {extraActions} - - + + ); } } diff --git a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/ReviewDialogContents/ReviewDialogContents.component.js b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/ReviewDialogContents/ReviewDialogContents.component.js index 99c01c200c..d5146481cb 100644 --- a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/ReviewDialogContents/ReviewDialogContents.component.js +++ b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/ReviewDialogContents/ReviewDialogContents.component.js @@ -1,8 +1,7 @@ // @flow import React, { type ComponentType, useContext } from 'react'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; import { withStyles } from '@material-ui/core/styles'; +import { ModalTitle, ModalContent } from '@dhis2/ui'; import i18n from '@dhis2/d2-i18n'; import { CardList } from '../../CardList'; import { ReviewDialogContentsPager } from './ReviewDialogContentsPager.container'; @@ -30,10 +29,10 @@ const ReviewDialogContentsPlain = ({ const { resultsPageSize } = useContext(ResultsPageSizeContext); return ( - - + + {i18n.t('Possible duplicates found')} - + - + ); }; diff --git a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/possibleDuplicatesDialog.epics.js b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/possibleDuplicatesDialog.epics.js index 515eee121d..78098fa714 100644 --- a/src/core_modules/capture-core/components/PossibleDuplicatesDialog/possibleDuplicatesDialog.epics.js +++ b/src/core_modules/capture-core/components/PossibleDuplicatesDialog/possibleDuplicatesDialog.epics.js @@ -47,7 +47,6 @@ export const loadSearchGroupDuplicatesForReviewEpic = ( payload: { page, pageSize, - orgUnitId, selectedScopeId, scopeType, dataEntryId, @@ -73,7 +72,6 @@ export const loadSearchGroupDuplicatesForReviewEpic = ( const contextParam = scopeType === scopeTypes.TRACKER_PROGRAM ? { program: selectedScopeId } : { trackedEntityType: selectedScopeId }; const queryArgs = { - ou: orgUnitId, ouMode: 'ACCESSIBLE', pageSize, page, diff --git a/src/core_modules/capture-core/components/Relationships/Relationships.component.js b/src/core_modules/capture-core/components/Relationships/Relationships.component.js index 1ee7f8af6c..be9fb6c569 100644 --- a/src/core_modules/capture-core/components/Relationships/Relationships.component.js +++ b/src/core_modules/capture-core/components/Relationships/Relationships.component.js @@ -5,7 +5,7 @@ import classNames from 'classnames'; import i18n from '@dhis2/d2-i18n'; import { IconButton, withStyles } from '@material-ui/core'; import { IconArrowRight16, IconCross24, Button } from '@dhis2/ui'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import type { RelationshipType } from '../../metaData'; import type { Relationship, Entity } from './relationships.types'; diff --git a/src/core_modules/capture-core/components/RulesEngineVerboseInitializer/RulesEngineVerboseInitializer.js b/src/core_modules/capture-core/components/RulesEngineVerboseInitializer/RulesEngineVerboseInitializer.js new file mode 100644 index 0000000000..6ed402f545 --- /dev/null +++ b/src/core_modules/capture-core/components/RulesEngineVerboseInitializer/RulesEngineVerboseInitializer.js @@ -0,0 +1,11 @@ +// @flow +import { useRuleEngineFlags } from '../../rules/useRuleEngineFlags'; + +type Props = {| + children: React$Node, +|}; +export const RulesEngineVerboseInitializer = ({ children }: Props) => { + useRuleEngineFlags(); + + return children; +}; diff --git a/src/core_modules/capture-core/components/RulesEngineVerboseInitializer/index.js b/src/core_modules/capture-core/components/RulesEngineVerboseInitializer/index.js new file mode 100644 index 0000000000..5043a09470 --- /dev/null +++ b/src/core_modules/capture-core/components/RulesEngineVerboseInitializer/index.js @@ -0,0 +1,2 @@ +// @flow +export { RulesEngineVerboseInitializer } from './RulesEngineVerboseInitializer'; diff --git a/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js b/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js index 18557db76b..0aba4ccd71 100644 --- a/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js +++ b/src/core_modules/capture-core/components/ScopeSelector/ScopeSelector.container.js @@ -3,15 +3,15 @@ import React, { type ComponentType, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { ScopeSelectorComponent } from './ScopeSelector.component'; import type { OwnProps } from './ScopeSelector.types'; -import { useOrganizationUnit } from './hooks'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { resetOrgUnitIdFromScopeSelector } from './ScopeSelector.actions'; -const deriveReadiness = (lockedSelectorLoads, selectedOrgUnitId, selectedOrgUnitName) => { +const deriveReadiness = (lockedSelectorLoads, selectedOrgUnitId, selectedOrgUnitName, displayName) => { // because we want the orgUnit to be fetched and stored // before allowing the user to view the locked selector - if (selectedOrgUnitId && selectedOrgUnitName) { - return true; + if (selectedOrgUnitId && (!selectedOrgUnitName || selectedOrgUnitName !== displayName)) { + return false; } return !lockedSelectorLoads; }; @@ -32,21 +32,20 @@ export const ScopeSelector: ComponentType = ({ children, }) => { const dispatch = useDispatch(); - const { refetch: refetchOrganisationUnit, displayName } = useOrganizationUnit(); - const [selectedOrgUnit, setSelectedOrgUnit] = useState({ name: displayName, id: selectedOrgUnitId }); + const [selectedOrgUnit, setSelectedOrgUnit] = useState({ name: undefined, id: selectedOrgUnitId }); + const { displayName } = useOrgUnitName(selectedOrgUnit.id); useEffect(() => { - const missName = !selectedOrgUnit.name; - const hasDifferentId = selectedOrgUnit.id !== selectedOrgUnitId; - - selectedOrgUnitId && - (hasDifferentId || missName) && - refetchOrganisationUnit({ variables: { selectedOrgUnitId } }); - }, [selectedOrgUnitId]); // eslint-disable-line react-hooks/exhaustive-deps + if (displayName && selectedOrgUnit.name !== displayName) { + setSelectedOrgUnit(prevSelectedOrgUnit => ({ ...prevSelectedOrgUnit, name: displayName })); + } + }, [displayName, selectedOrgUnit, setSelectedOrgUnit]); useEffect(() => { - displayName && setSelectedOrgUnit(prevSelectedOrgUnit => ({ ...prevSelectedOrgUnit, name: displayName })); - }, [displayName, setSelectedOrgUnit]); + if (selectedOrgUnitId && !selectedOrgUnit.id) { + selectedOrgUnitId && setSelectedOrgUnit(prevSelectedOrgUnit => ({ ...prevSelectedOrgUnit, id: selectedOrgUnitId })); + } + }, [selectedOrgUnitId, selectedOrgUnit, setSelectedOrgUnit]); const handleSetOrgUnit = (orgUnitId, orgUnitObject) => { setSelectedOrgUnit(orgUnitObject); @@ -59,7 +58,7 @@ export const ScopeSelector: ComponentType = ({ previousOrgUnitId: app.previousOrgUnitId, } )); - const ready = deriveReadiness(lockedSelectorLoads, selectedOrgUnitId, selectedOrgUnit.name); + const ready = deriveReadiness(lockedSelectorLoads, selectedOrgUnit.id, selectedOrgUnit.name, displayName); return ( { - const { data, refetch } = useDataQuery( - useMemo( - () => ({ - organisationUnits: { - resource: 'organisationUnits', - id: ({ variables: { selectedOrgUnitId: id } }) => id, - params: { - fields: ['displayName'], - }, - }, - }), - [], - ), - { - lazy: true, - }, - ); - - return { - displayName: data?.organisationUnits?.displayName, - refetch, - }; -}; diff --git a/src/core_modules/capture-core/components/TeiSearch/epics/teiSearch.epics.js b/src/core_modules/capture-core/components/TeiSearch/epics/teiSearch.epics.js index 4b0fd58a51..c0b3e7321c 100644 --- a/src/core_modules/capture-core/components/TeiSearch/epics/teiSearch.epics.js +++ b/src/core_modules/capture-core/components/TeiSearch/epics/teiSearch.epics.js @@ -34,9 +34,9 @@ import { getSearchFormId } from '../getSearchFormId'; import type { QuerySingleResource } from '../../../utils/api/api.types'; const getOuQueryArgs = (orgUnit: ?Object, orgUnitScope: string) => - (orgUnitScope !== 'ACCESSIBLE' ? - { orgUnit: orgUnit && orgUnit.id, ouMode: orgUnitScope } : - { ouMode: orgUnitScope }); + (['ACCESSIBLE', 'CAPTURE', 'ALL'].includes(orgUnitScope) ? + { ouMode: orgUnitScope } : + { orgUnit: orgUnit && orgUnit.id, ouMode: orgUnitScope }); const getContextQueryArgs = (programId: ?string, trackedEntityTypeId: string) => (programId ? { program: programId } : { trackedEntityType: trackedEntityTypeId }); diff --git a/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.component.js b/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.component.js index 5ffa6dd1a8..43a48240be 100644 --- a/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.component.js +++ b/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.component.js @@ -18,7 +18,6 @@ const getStyles = () => ({ padding: 0, gap: '4px', marginBottom: spacers.dp8, - overflow: 'hidden', }, chipContainer: { padding: 0, @@ -80,7 +79,10 @@ const TemplateSelectorPlain = (props: Props) => { const { id } = customTemplate; return (
- +
); }); diff --git a/src/core_modules/capture-core/components/TemplateSelector/TemplateSelectorChip.component.js b/src/core_modules/capture-core/components/TemplateSelector/TemplateSelectorChip.component.js index 24e3ddc79f..5b9b05cfb0 100644 --- a/src/core_modules/capture-core/components/TemplateSelector/TemplateSelectorChip.component.js +++ b/src/core_modules/capture-core/components/TemplateSelector/TemplateSelectorChip.component.js @@ -2,6 +2,7 @@ import React, { useCallback } from 'react'; import { Chip } from '@dhis2/ui'; import type { WorkingListTemplate } from './workingListsBase.types'; +import { TooltipForChip } from '../Tooltips/TooltipForChip'; type Props = { template: WorkingListTemplate, @@ -19,8 +20,23 @@ export const TemplateSelectorChip = (props: Props) => { const text = displayName.length > 30 ? `${displayName.substring(0, 27)}...` : displayName; return ( - - {text} - + 30} + onClick={selectTemplateHandler} + > + + {text} + + + ); }; diff --git a/src/core_modules/capture-core/components/ConditionalTooltip/ConditionalTooltip.component.js b/src/core_modules/capture-core/components/Tooltips/ConditionalTooltip/ConditionalTooltip.component.js similarity index 100% rename from src/core_modules/capture-core/components/ConditionalTooltip/ConditionalTooltip.component.js rename to src/core_modules/capture-core/components/Tooltips/ConditionalTooltip/ConditionalTooltip.component.js diff --git a/src/core_modules/capture-core/components/ConditionalTooltip/index.js b/src/core_modules/capture-core/components/Tooltips/ConditionalTooltip/index.js similarity index 100% rename from src/core_modules/capture-core/components/ConditionalTooltip/index.js rename to src/core_modules/capture-core/components/Tooltips/ConditionalTooltip/index.js diff --git a/src/core_modules/capture-core/components/Tooltips/TooltipForChip/TooltipForChip.component.js b/src/core_modules/capture-core/components/Tooltips/TooltipForChip/TooltipForChip.component.js new file mode 100644 index 0000000000..01c539b3af --- /dev/null +++ b/src/core_modules/capture-core/components/Tooltips/TooltipForChip/TooltipForChip.component.js @@ -0,0 +1,60 @@ +// @flow +import React from 'react'; +import { Tooltip } from '@dhis2/ui'; +import { withStyles } from '@material-ui/core/'; + +type Props = { + enabled: boolean, + onClick: Function, + children: any, + ...CssClasses, +}; + +const styles = { + // button style reset + button: { + border: 'none', + backgroundColor: 'transparent', + borderRadius: '16px', + padding: 0, + margin: 0, + minWidth: 0, + minHeight: 0, + '&:hover': { + backgroundColor: 'transparent', + }, + }, +}; +const TooltipForChipPlain = (props: Props) => { + const { enabled, children, classes, onClick, ...passOnProps } = props; + + return enabled ? + ( + + {({ ref, onMouseOver, onMouseOut }) => ( + + )} + + ) : ( + + ); +}; + +export const TooltipForChip = withStyles(styles)(TooltipForChipPlain); diff --git a/src/core_modules/capture-core/components/Tooltips/TooltipForChip/index.js b/src/core_modules/capture-core/components/Tooltips/TooltipForChip/index.js new file mode 100644 index 0000000000..8894bd8888 --- /dev/null +++ b/src/core_modules/capture-core/components/Tooltips/TooltipForChip/index.js @@ -0,0 +1,2 @@ +// @flow +export { TooltipForChip } from './TooltipForChip.component'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js index 4d07c56a61..0c56de8cb1 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.component.js @@ -8,8 +8,10 @@ import { Complete } from './Complete'; import { Delete } from './Delete'; import { Followup } from './Followup'; import { AddNew } from './AddNew'; +import { AddLocation } from './AddLocation'; import type { PlainProps } from './actions.types'; import { LoadingMaskForButton } from '../../LoadingMasks'; +import { MapModal } from '../MapModal'; const styles = { actions: { @@ -35,13 +37,14 @@ export const ActionsPlain = ({ onlyEnrollOnce, classes, }: PlainProps) => { - const [open, setOpen] = useState(false); + const [isOpenActions, setOpenActions] = useState(false); + const [isOpenMap, setOpenMap] = useState(false); const handleOnUpdate = (arg) => { - setOpen(prev => !prev); + setOpenActions(false); onUpdate(arg); }; const handleOnDelete = (arg) => { - setOpen(prev => !prev); + setOpenActions(false); onDelete(arg); }; @@ -53,8 +56,8 @@ export const ActionsPlain = ({ small disabled={loading} className={classes.actions} - open={open} - onClick={() => setOpen(prev => !prev)} + open={isOpenActions} + onClick={() => setOpenActions(prev => !prev)} component={ loading ? null : ( @@ -72,6 +75,13 @@ export const ActionsPlain = ({ enrollment={enrollment} onUpdate={handleOnUpdate} /> + { + setOpenMap(true); + setOpenActions(false); + }} + /> + ) } @@ -94,6 +105,11 @@ export const ActionsPlain = ({ {i18n.t('We are processing your request.')}
)} + {isOpenMap && } ); }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js index 2ce98fcb38..1e3bab189e 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.js @@ -1,24 +1,8 @@ // @flow -import { useDataMutation } from '@dhis2/app-runtime'; import React from 'react'; import { ActionsComponent } from './Actions.component'; import type { Props } from './actions.types'; -import { processErrorReports } from '../processErrorReports'; - -const enrollmentUpdate = { - resource: 'tracker?async=false&importStrategy=UPDATE', - type: 'create', - data: enrollment => ({ - enrollments: [enrollment], - }), -}; -const enrollmentDelete = { - resource: 'tracker?async=false&importStrategy=DELETE', - type: 'create', - data: enrollment => ({ - enrollments: [enrollment], - }), -}; +import { useUpdateEnrollment, useDeleteEnrollment } from '../dataMutation/dataMutation'; export const Actions = ({ enrollment = {}, @@ -29,31 +13,8 @@ export const Actions = ({ onSuccess, ...passOnProps }: Props) => { - const [updateMutation, { loading: updateLoading }] = useDataMutation( - enrollmentUpdate, - { - onComplete: () => { - refetchEnrollment(); - refetchTEI(); - onSuccess && onSuccess(); - }, - onError: (e) => { - onError && onError(processErrorReports(e)); - }, - }, - ); - const [deleteMutation, { loading: deleteLoading }] = useDataMutation( - enrollmentDelete, - { - onComplete: () => { - onDelete(); - onSuccess && onSuccess(); - }, - onError: (e) => { - onError && onError(processErrorReports(e)); - }, - }, - ); + const { updateMutation, updateLoading } = useUpdateEnrollment(refetchEnrollment, refetchTEI, onError); + const { deleteMutation, deleteLoading } = useDeleteEnrollment(onDelete, onError); return ( { + const label = useGeometryLabel(enrollment); + + if (!label) { + return null; + } + + return ( + } + label={label} + onClick={() => setOpenMap(true)} + /> + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/addLocation.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/addLocation.types.js new file mode 100644 index 0000000000..d004713319 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/addLocation.types.js @@ -0,0 +1,6 @@ +// @flow + +export type Props = {| + enrollment: Object, + setOpenMap: (toogle: boolean) => void, +|}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/index.js new file mode 100644 index 0000000000..bdf4bdca7c --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddLocation/index.js @@ -0,0 +1,2 @@ +// @flow +export { AddLocation } from './AddLocation.component'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddNew/AddNew.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddNew/AddNew.component.js index e4ec61be1b..ce6e30b06b 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddNew/AddNew.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/AddNew/AddNew.component.js @@ -2,7 +2,7 @@ import { IconAdd16, MenuItem } from '@dhis2/ui'; import React from 'react'; import i18n from '@dhis2/d2-i18n'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import type { Props } from './addNew.types'; export const AddNew = ({ canAddNew, onlyEnrollOnce, tetName, onAddNew }: Props) => { diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Date/Date.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/Date/Date.component.js index f3c7f36617..0fdba91aea 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/Date/Date.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/Date/Date.component.js @@ -17,6 +17,7 @@ import { dataElementTypes } from '../../../metaData'; type Props = { date: string, dateLabel: string, + locale: string, editEnabled: boolean, displayAutoGeneratedEventWarning: boolean, onSave: (string) => void, @@ -66,6 +67,7 @@ const styles = { const DateComponentPlain = ({ date, dateLabel, + locale, editEnabled, displayAutoGeneratedEventWarning, onSave, @@ -103,6 +105,7 @@ const DateComponentPlain = ({ className={classes.calendar} label={dateLabel} date={selectedDate} + locale={locale} onDateSelect={dateChangeHandler} />
diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/Coordinates.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/Coordinates.component.js new file mode 100644 index 0000000000..cd9940c11a --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/Coordinates.component.js @@ -0,0 +1,256 @@ +// @flow +import React, { useState, useMemo } from 'react'; +import classNames from 'classnames'; +import i18n from '@dhis2/d2-i18n'; +import { IconCross24, spacers, Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; +import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; +import { Map, TileLayer, Marker, withLeaflet } from 'react-leaflet'; +import { withStyles } from '@material-ui/core'; +import type { CoordinatesProps } from './Coordinates.types'; +import { CoordinateInput } from '../../../../../capture-ui/internal/CoordinateInput/CoordinateInput.component'; +import { isEqual } from '../../../../utils/valueEqualityChecker'; +import { isValidCoordinate } from './coordinate.validator'; +import { convertCoordinatesToServer } from './converters'; + +const styles = (theme: Theme) => ({ + modalContent: { + width: '100%', + }, + map: { + width: '100%', + height: 'calc(100vh - 380px)', + }, + inputWrapper: { + paddingTop: spacers.dp8, + display: 'flex', + }, + inputContent: { + flexGrow: 1, + }, + fieldButton: { + height: '42px !important', + width: 42, + borderRadius: '0 !important', + }, + errorContainer: { + backgroundColor: theme.palette.error.lighter, + color: theme.palette.error.main, + }, +}); + +const WrappedLeafletSearch = withLeaflet(ReactLeafletSearch); + +const CoordinatesPlain = ({ + classes, + center: initialCenter, + setOpen, + defaultValues, + onSetCoordinates, +}: CoordinatesProps) => { + const [position, setPosition] = useState(defaultValues); + const [center, setCenter] = useState(initialCenter); + const [tempLatitude, setTempLatitude] = useState(position?.[0]); + const [tempLongitude, setTempLongitude] = useState(position?.[1]); + const [isEditing, setEditing] = useState(!defaultValues); + const [isValid, setValid] = useState(true); + const hasErrors = useMemo(() => { + const changed = !isEqual(position, defaultValues); + return changed && !isValid; + }, [position, defaultValues, isValid]); + + const resetToDefaultValues = () => { + setCenter(initialCenter); + setPosition(defaultValues); + if (defaultValues) { + setTempLatitude(defaultValues[0]); + setTempLongitude(defaultValues[1]); + setEditing(false); + } else { + setTempLatitude(null); + setTempLongitude(null); + } + }; + + const onHandleMapClicked = (mapCoordinates) => { + if (isEditing) { + const { lat, lng } = mapCoordinates.latlng; + const newPosition: [number, number] = [lat, lng]; + setValid(true); + setPosition(newPosition); + setTempLatitude(lat); + setTempLongitude(lng); + } + }; + + const onSearch = (searchPosition: any) => { + setCenter(searchPosition); + setValid(true); + setTempLatitude(searchPosition[0]); + setTempLongitude(searchPosition[1]); + setPosition(searchPosition); + }; + + const renderMap = () => ( + { + if (ref?.leafletElement) { + ref.leafletElement.invalidateSize(); + } + }} + className={classes.map} + onClick={onHandleMapClicked} + > + + + {position && } + + ); + + const renderLatitude = () => ( + { + if (!latitude) { + return; + } + const longitude = tempLongitude || (position?.[1] ? position[1] : undefined); + if (!longitude) { + return; + } + if (!isValidCoordinate({ longitude: Number(longitude), latitude: Number(latitude) })) { + setPosition(null); + setValid(false); + return; + } + setValid(true); + const newPosition = [Number(latitude), longitude]; + setPosition(newPosition); + setCenter(newPosition); + }} + onChange={(latitude) => { + setTempLatitude(latitude); + }} + /> + ); + + const renderLongitude = () => ( + { + if (!longitude) { + return; + } + const latitude = tempLatitude || (position?.[1] ? position[0] : undefined); + if (!latitude) { + return; + } + if (!isValidCoordinate({ longitude: Number(longitude), latitude: Number(latitude) })) { + setPosition(null); + setValid(false); + return; + } + setValid(true); + const newPosition = [latitude, Number(longitude)]; + setPosition(newPosition); + setCenter(newPosition); + }} + onChange={(longitude) => { + setTempLongitude(longitude); + }} + /> + ); + + const renderFieldButton = () => ( +
+ {!isEditing ? ( + + ) : ( +
+ ); + + const renderActions = () => ( + + + + + ); + + return ( + + {i18n.t('Coordinates')} + + {renderMap()} +
+
+
{renderLatitude()}
+
{renderLongitude()}
+ {renderFieldButton()} +
+ {hasErrors && ( +
{i18n.t('Please provide valid coordinates')}
+ )} +
+
+ {renderActions()} +
+ ); +}; +export const Coordinates = withStyles(styles)(CoordinatesPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/Coordinates.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/Coordinates.types.js new file mode 100644 index 0000000000..56e3c5f689 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/Coordinates.types.js @@ -0,0 +1,10 @@ +// @flow + +export type CoordinatesProps = { + center: ?[number, number], + setOpen: (open: boolean) => void, + onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, + defaultValues?: ?[number, number], + ...CssClasses, +} + diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/converters.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/converters.js new file mode 100644 index 0000000000..357a034be3 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/converters.js @@ -0,0 +1,11 @@ +// @flow + +export const convertCoordinatesToServer = (coordinates?: Array | null): ?[number, number] => { + if (!coordinates || !coordinates[0]) { + return null; + } + + const lng: number = coordinates[0][1]; + const lat: number = coordinates[0][0]; + return [lng, lat]; +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/coordinate.validator.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/coordinate.validator.js new file mode 100644 index 0000000000..1d37df3233 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/coordinate.validator.js @@ -0,0 +1,32 @@ +// @flow + +type Location = { + longitude: number, + latitude: number, +}; + +function isNumValid(num) { + if (typeof num === 'number') { + return true; + } else if (typeof num === 'string') { + return num.match(/[^0-9.,-]+/) === null; + } + + return false; +} + +export const isValidCoordinate = (value: Location) => { + if (!value) { + return false; + } + + const { longitude, latitude } = value; + if (!isNumValid(latitude) || !isNumValid(longitude)) { + return false; + } + + const ld = parseInt(longitude, 10); + const lt = parseInt(latitude, 10); + + return ld >= -180 && ld <= 180 && lt >= -90 && lt <= 90; +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/index.js new file mode 100644 index 0000000000..8d4c2efc14 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Coordinates/index.js @@ -0,0 +1,3 @@ +// @flow +export { Coordinates } from './Coordinates.component'; + diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js new file mode 100644 index 0000000000..355490f756 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.component.js @@ -0,0 +1,27 @@ +// @flow +import React from 'react'; +import { dataElementTypes } from '../../../metaData'; +import type { MapModalComponentProps } from './MapModal.types'; +import { Coordinates } from './Coordinates'; +import { Polygon } from './Polygon'; + +export const MapModal = ({ type, center, setOpen, onSetCoordinates, defaultValues }: MapModalComponentProps) => ( + <> + {type === dataElementTypes.COORDINATE && ( + + )} + {type === dataElementTypes.POLYGON && ( + + )} + +); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js new file mode 100644 index 0000000000..b39d141b1d --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.container.js @@ -0,0 +1,32 @@ +// @flow +import React, { useCallback } from 'react'; +import { useGeometry } from '../hooks/useGeometry'; +import type { MapModalProps } from './MapModal.types'; +import { MapModal as MapModalComponent } from './MapModal.component'; + +const DEFAULT_CENTER = [51.505, -0.09]; + +export const MapModal = ({ + enrollment, + onUpdate, + setOpenMap, + defaultValues, + center, +}: MapModalProps) => { + const { geometryType, dataElementType } = useGeometry(enrollment); + + const onSetCoordinates = useCallback((coordinates) => { + const geometry = coordinates ? { type: geometryType, coordinates } : null; + onUpdate({ ...enrollment, geometry }); + }, [enrollment, geometryType, onUpdate]); + + return ( + + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.types.js new file mode 100644 index 0000000000..80a273420b --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/MapModal.types.js @@ -0,0 +1,18 @@ +// @flow +import { dataElementTypes } from '../../../metaData'; + +export type MapModalComponentProps = { + center: ?[number, number], + type: typeof dataElementTypes.COORDINATE | typeof dataElementTypes.POLYGON, + defaultValues?: ?Array> | ?[number, number], + setOpen: (open: boolean) => void, + onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, +} + +export type MapModalProps = {| + center?: ?[number, number], + enrollment: Object, + onUpdate: (arg: Object) => void, + setOpenMap: (toggle: boolean) => void, + defaultValues?: ?Array> | ?[number, number], +|}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/ConditionalTooltip.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/ConditionalTooltip.component.js new file mode 100644 index 0000000000..1250a792d2 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/ConditionalTooltip.component.js @@ -0,0 +1,29 @@ +// @flow +import React from 'react'; +import { Tooltip } from '@dhis2/ui'; + +type Props = { + enabled: boolean, + children: any, +}; + +export const ConditionalTooltip = (props: Props) => { + const { enabled, children, ...passOnProps } = props; + + return enabled ? + ( + { ({ onMouseOver, onMouseOut, ref }) => ( + { + if (btnRef) { + btnRef.onpointerenter = onMouseOver; + btnRef.onpointerleave = onMouseOut; + ref.current = btnRef; + } + }} + > + {children} + + )} + ) : children; +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/DeleteControl.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/DeleteControl.component.js new file mode 100644 index 0000000000..3dd18b0e96 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/DeleteControl.component.js @@ -0,0 +1,52 @@ +// @flow +import React, { useEffect, useState, useCallback } from 'react'; +import ReactDOM from 'react-dom'; +import i18n from '@dhis2/d2-i18n'; +import classNames from 'classnames'; +import L, { Control } from 'leaflet'; +import { withLeaflet } from 'react-leaflet'; + +type Props = { + onClick: () => void, + disabled?: ?boolean, + leaflet: typeof Control, +}; + +const DeleteControlPlain = ({ onClick, disabled, leaflet }: Props) => { + const [leafletElement, setLeafletElement] = useState(); + const onHandleClick = useCallback(() => !disabled && onClick(), [disabled, onClick]); + + useEffect(() => { + const deleteControl = L.control({ position: 'topright' }); + const text = i18n.t('Delete polygon'); + const jsx = ( +
+ {/* eslint-disable-next-line */} + +
+ ); + + deleteControl.onAdd = () => { + const div = L.DomUtil.create('div', ''); + ReactDOM.render(jsx, div); + return div; + }; + setLeafletElement(deleteControl); + }, [onHandleClick, disabled]); + + useEffect(() => { + leafletElement && leafletElement.addTo(leaflet.map); + }, [leafletElement, leaflet.map]); + + useEffect(() => () => leafletElement && leafletElement.remove(), [leafletElement]); + + return null; +}; + +export const DeleteControl = withLeaflet(DeleteControlPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.component.js new file mode 100644 index 0000000000..d1c2eeefe1 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.component.js @@ -0,0 +1,225 @@ +// @flow +import React, { useState, useRef } from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { Modal, ModalTitle, ModalContent, ModalActions, Button, ButtonStrip } from '@dhis2/ui'; +import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; +import { Map, TileLayer, FeatureGroup, withLeaflet } from 'react-leaflet'; +import { EditControl } from 'react-leaflet-draw'; +import L from 'leaflet'; +import { withStyles } from '@material-ui/core'; +import type { PolygonProps, FeatureCollection } from './Polygon.types'; +import { convertPolygonToServer } from './converters'; +import { DeleteControl } from './DeleteControl.component'; +import { ConditionalTooltip } from './ConditionalTooltip.component'; + +const styles = () => ({ + modalContent: { + width: '100%', + }, + map: { + width: '100%', + height: 'calc(100vh - 380px)', + }, + setAreaButton: { + marginLeft: '5px', + }, +}); + +const coordsToFeatureCollection = (inputCoordinates: any): ?FeatureCollection => { + if (!inputCoordinates) { + return null; + } + const list = inputCoordinates[0].length > 2 ? inputCoordinates[0] : inputCoordinates.map(c => [c[1], c[0]]); + + return { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [list], + }, + }, + ], + }; +}; + +const drawing = { + STARTED: 'STARTED', + FINISHED: 'FINISHED', +}; + +const WrappedLeafletSearch = withLeaflet(ReactLeafletSearch); + +const PolygonPlain = ({ + classes, + center: initialCenter, + setOpen, + defaultValues, + onSetCoordinates, +}: PolygonProps) => { + const [polygonArea, setPolygonArea] = useState(defaultValues); + const [center, setCenter] = useState(initialCenter); + const [drawingState, setDrawingState] = useState(undefined); + const prevDrawingState = useRef(undefined); + + const resetToDefaultValues = () => { + setCenter(initialCenter); + setPolygonArea(defaultValues); + }; + + const onMapPolygonCreated = (e: any) => { + const polygonCoordinates = e.layer.toGeoJSON().geometry.coordinates[0].map(c => [c[1], c[0]]); + setPolygonArea(polygonCoordinates); + setDrawingState(drawing.FINISHED); + prevDrawingState.current = drawing.FINISHED; + }; + + const onMapPolygonDelete = () => { + setPolygonArea(null); + setDrawingState(drawing.FINISHED); + prevDrawingState.current = drawing.FINISHED; + }; + + const onSearch = (searchPosition: any) => { + setCenter(searchPosition); + }; + + const getFeatureCollection = () => (Array.isArray(polygonArea) ? coordsToFeatureCollection(polygonArea) : null); + + const renderMap = () => ( + { + if (ref?.leafletElement) { + ref.leafletElement.invalidateSize(); + if (ref.contextValue && polygonArea) { + const { map } = ref.contextValue; + map?.fitBounds(polygonArea); + } + } + }} + className={classes.map} + > + + + { + onFeatureGroupReady(reactFGref, getFeatureCollection()); + }} + > + setDrawingState(drawing.STARTED)} + onDrawStop={() => setDrawingState(prevDrawingState.current)} + draw={{ + rectangle: false, + polyline: false, + circle: false, + marker: false, + circlemarker: false, + }} + edit={{ + remove: false, + edit: false, + }} + /> + + + + ); + + const onFeatureGroupReady = (reactFGref: any, featureCollection: ?FeatureCollection) => { + if (!reactFGref) { + return; + } + if (featureCollection) { + const leafletGeoJSON = new L.GeoJSON(featureCollection); + const leafletFG = reactFGref.leafletElement; + leafletFG.clearLayers(); + + leafletGeoJSON.eachLayer((layer) => { + leafletFG.addLayer(layer); + }); + } else { + const leafletFG = reactFGref.leafletElement; + leafletFG.clearLayers(); + } + }; + + const renderActions = () => ( + + {!drawingState && ( + + )} + {drawingState && ( + <> + + + + + + )} + + ); + + return ( + + {i18n.t('Area')} + {renderMap()} + {renderActions()} + + ); +}; + +export const Polygon = withStyles(styles)(PolygonPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.types.js new file mode 100644 index 0000000000..024be1b432 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/Polygon.types.js @@ -0,0 +1,24 @@ +// @flow + +type Feature = { + type: string, + properties: Object, + geometry: { + type: string, + coordinates: Array | number>>, + }, +} + +export type FeatureCollection = { + type: string, + features: Array, +}; + +export type PolygonProps = { + center: ?[number, number], + setOpen: (open: boolean) => void, + onSetCoordinates: (coordinates: ?[number, number] | ?Array<[number, number]>) => void, + defaultValues?: ?Array>, + ...CssClasses, +} + diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/converters.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/converters.js new file mode 100644 index 0000000000..15556ee8c2 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/converters.js @@ -0,0 +1,8 @@ +// @flow + +export const convertPolygonToServer = (coordinates?: Array> | null): ?Array<[number, number]> => { + if (!coordinates) { + return null; + } + return Array<[number, number]>(coordinates.map(c => (c ? [c[1], c[0]] : null))); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/index.js new file mode 100644 index 0000000000..04659c427b --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/Polygon/index.js @@ -0,0 +1,3 @@ +// @flow +export { Polygon } from './Polygon.component'; + diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/index.js new file mode 100644 index 0000000000..41266d1ca4 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MapModal/index.js @@ -0,0 +1,3 @@ +// @flow +export { MapModal } from './MapModal.container'; + diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.component.js new file mode 100644 index 0000000000..a8d3304847 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.component.js @@ -0,0 +1,80 @@ +// @flow +import React, { useState } from 'react'; +import { Map, TileLayer, Marker, Polygon } from 'react-leaflet'; +import { withStyles } from '@material-ui/core'; +import { dataElementTypes } from '../../../metaData'; +import { MapModal } from '../MapModal'; +import type { MiniMapProps } from './MiniMap.types'; +import { convertToClientCoordinates } from './converters'; +import { useUpdateEnrollment } from '../dataMutation/dataMutation'; + +const styles = () => ({ + mapContainer: { + width: 150, + height: 120, + }, + map: { + width: '100%', + height: '100%', + }, +}); + +const MiniMapPlain = ({ + coordinates, + geometryType, + enrollment, + refetchEnrollment, + refetchTEI, + onError, + classes, +}: MiniMapProps) => { + const [isOpenMap, setOpenMap] = useState(false); + const { updateMutation } = useUpdateEnrollment(refetchEnrollment, refetchTEI, onError); + const clientValues = convertToClientCoordinates(coordinates, geometryType); + const center = geometryType === dataElementTypes.COORDINATE ? clientValues : clientValues[0]; + const onMapReady = (mapRef) => { + if (mapRef?.contextValue && geometryType === dataElementTypes.POLYGON) { + const { map } = mapRef.contextValue; + map?.fitBounds(clientValues); + } + }; + + return ( + <> +
+ { + onMapReady(mapRef); + }} + center={center} + className={classes.map} + zoom={11} + zoomControl={false} + attributionControl={false} + key="minimap" + onClick={() => { + setOpenMap(true); + }} + > + + {geometryType === dataElementTypes.COORDINATE && } + {geometryType === dataElementTypes.POLYGON && } + +
+ {isOpenMap && ( + + )} + + ); +}; + +export const MiniMap = withStyles(styles)(MiniMapPlain); diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.types.js new file mode 100644 index 0000000000..b905c28783 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/MiniMap.types.js @@ -0,0 +1,13 @@ +// @flow +import type { QueryRefetchFunction } from '@dhis2/app-runtime'; +import { dataElementTypes } from '../../../metaData'; + +export type MiniMapProps = { + coordinates: Array>, + enrollment: any, + refetchEnrollment: QueryRefetchFunction, + refetchTEI: QueryRefetchFunction, + onError?: (message: string) => void, + geometryType: typeof dataElementTypes.COORDINATE | typeof dataElementTypes.POLYGON, + ...CssClasses +} diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/converters.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/converters.js new file mode 100644 index 0000000000..498738ef13 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/converters.js @@ -0,0 +1,10 @@ +// @flow +import { dataElementTypes } from '../../../metaData'; + +export const convertToClientCoordinates = (coordinates: any[], type: $Values) => { + if (type === dataElementTypes.COORDINATE) { + return [coordinates[1], coordinates[0]]; + } + + return coordinates[0].map(coord => [coord[1], coord[0]]); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/index.js b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/index.js new file mode 100644 index 0000000000..c1e7fa0e6f --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/MiniMap/index.js @@ -0,0 +1,2 @@ +// @flow +export { MiniMap } from './MiniMap.component'; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js index 15ebdb7c41..018a6e7798 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/WidgetEnrollment.component.js @@ -4,7 +4,6 @@ import moment from 'moment'; import { IconClock16, IconDimensionOrgUnit16, - IconLocation16, colors, Tag, spacersNum, @@ -16,11 +15,11 @@ import { LoadingMaskElementCenter } from '../LoadingMasks'; import { Widget } from '../Widget'; import type { PlainProps } from './enrollment.types'; import { Status } from './Status'; -import { convertValue as convertValueServerToClient } from '../../converters/serverToClient'; -import { convertValue as convertValueClientToView } from '../../converters/clientToView'; import { dataElementTypes } from '../../metaData'; +import { useOrgUnitName } from '../../metadataRetrieval/orgUnitName'; import { Date } from './Date'; import { Actions } from './Actions'; +import { MiniMap } from './MiniMap'; const styles = { enrollment: { @@ -42,14 +41,15 @@ const styles = { const getGeometryType = geometryType => (geometryType === 'Point' ? dataElementTypes.COORDINATE : dataElementTypes.POLYGON); -const getEnrollmentDateLabel = program => program.enrollmentDateLabel || i18n.t('Enrollment date'); -const getIncidentDateLabel = program => program.incidentDateLabel || i18n.t('Incident date'); +const getEnrollmentDateLabel = program => program.displayEnrollmentDateLabel || i18n.t('Enrollment date'); +const getIncidentDateLabel = program => program.displayIncidentDateLabel || i18n.t('Incident date'); export const WidgetEnrollmentPlain = ({ classes, enrollment = {}, program = {}, ownerOrgUnit = {}, + locale, refetchEnrollment, refetchTEI, initError, @@ -67,6 +67,7 @@ export const WidgetEnrollmentPlain = ({ const [open, setOpenStatus] = useState(true); const { fromServerDate } = useTimeZoneConversion(); const geometryType = getGeometryType(enrollment?.geometry?.type); + const { displayName: orgUnitName } = useOrgUnitName(enrollment.orgUnit); return (
@@ -83,7 +84,7 @@ export const WidgetEnrollmentPlain = ({ )} {loading && } {!initError && !loading && ( -
+
{enrollment.followUp && ( @@ -97,6 +98,7 @@ export const WidgetEnrollmentPlain = ({ {i18n.t('Started at {{orgUnitName}}', { - orgUnitName: enrollment.orgUnitName, + orgUnitName, interpolation: { escapeValue: false }, })}
@@ -147,13 +150,14 @@ export const WidgetEnrollmentPlain = ({ {enrollment.geometry && (
- - - - {convertValueClientToView( - convertValueServerToClient(enrollment.geometry.coordinates, geometryType), - geometryType, - )} +
)} item.program === programId) .every(item => item.status !== plainStatus.ACTIVE); const containsAutoGeneratedEvent = program && program.programStages.some(({ autoGenerateEvent }) => autoGenerateEvent); - const error = errorEnrollment || errorProgram || errorOwnerOrgUnit || errorOrgUnit; + const error = errorEnrollment || errorProgram || errorOwnerOrgUnit || errorOrgUnit || errorLocale; if (error) { log.error(errorCreator('Enrollment widget could not be loaded')({ error })); @@ -62,7 +64,8 @@ export const WidgetEnrollment = ({ refetchEnrollment={refetchEnrollment} refetchTEI={refetchTEI} ownerOrgUnit={{ id: ownerOrgUnit, displayName }} - loading={!(enrollment && program && displayName)} + locale={locale} + loading={!(enrollment && program && displayName && locale)} onDelete={onDelete} onAddNew={onAddNew} updateEnrollmentDate={updateEnrollmentDate} diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/dataMutation/dataMutation.js b/src/core_modules/capture-core/components/WidgetEnrollment/dataMutation/dataMutation.js new file mode 100644 index 0000000000..b43c8f318f --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/dataMutation/dataMutation.js @@ -0,0 +1,67 @@ +// @flow +import { useDataMutation, type QueryRefetchFunction } from '@dhis2/app-runtime'; + + +const enrollmentUpdate = { + resource: 'tracker?async=false&importStrategy=UPDATE', + type: 'create', + data: enrollment => ({ + enrollments: [enrollment], + }), +}; + +const enrollmentDelete = { + resource: 'tracker?async=false&importStrategy=DELETE', + type: 'create', + data: enrollment => ({ + enrollments: [enrollment], + }), +}; + +const processErrorReports = (error) => { + // $FlowFixMe[prop-missing] + const errorReports = error?.details?.validationReport?.errorReports; + return errorReports?.length > 0 + ? errorReports.reduce((acc, errorReport) => `${acc} ${errorReport.message}`, '') + : error.message; +}; + + +export const useUpdateEnrollment = ( + refetchEnrollment: QueryRefetchFunction, + refetchTEI: QueryRefetchFunction, + onError?: ?(message: string) => void, +) => { + const [updateMutation, { loading: updateLoading }] = useDataMutation( + enrollmentUpdate, + { + onComplete: () => { + refetchEnrollment(); + refetchTEI(); + }, + onError: (e) => { + onError && onError(processErrorReports(e)); + }, + }, + ); + return { + updateMutation, updateLoading, + }; +}; + +export const useDeleteEnrollment = ( + onDelete: () => void, + onError?: ?(message: string) => void, +) => { + const [deleteMutation, { loading: deleteLoading }] = useDataMutation( + enrollmentDelete, + { + onComplete: onDelete, + onError: (e) => { + onError && onError(processErrorReports(e)); + }, + }, + ); + return { deleteMutation, deleteLoading }; +}; + diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/enrollment.types.js b/src/core_modules/capture-core/components/WidgetEnrollment/enrollment.types.js index 4d4cb57a48..91b4227bc8 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/enrollment.types.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/enrollment.types.js @@ -24,6 +24,7 @@ export type PlainProps = {| enrollment: Object, program: Object, ownerOrgUnit: Object, + locale: string, refetchEnrollment: QueryRefetchFunction, refetchTEI: QueryRefetchFunction, initError?: FetchError, diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useGeometry.js b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useGeometry.js new file mode 100644 index 0000000000..3569c211db --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useGeometry.js @@ -0,0 +1,39 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { dataElementTypes } from '../../../metaData'; +import { useProgram } from './useProgram'; + +export const useGeometry = (enrollment: { program: string }) => { + const { + program: { featureType }, + } = useProgram(enrollment.program); + + if (featureType === 'POINT') { + return { + geometryType: 'Point', + dataElementType: dataElementTypes.COORDINATE, + }; + } + + return { + geometryType: 'Polygon', + dataElementType: dataElementTypes.POLYGON, + }; +}; + +export const useGeometryLabel = (enrollment: { program: string, geometry: { type: string } }) => { + const { + program: { featureType }, + error, + } = useProgram(enrollment.program); + + if (error || !featureType || !['POINT', 'POLYGON'].includes(featureType) || enrollment.geometry?.type) { + return undefined; + } + + if (featureType === 'POINT') { + return i18n.t('Add coordinates'); + } + + return i18n.t('Add area'); +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useOrganizationUnit.js b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useOrganizationUnit.js deleted file mode 100644 index 875e86796b..0000000000 --- a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useOrganizationUnit.js +++ /dev/null @@ -1,32 +0,0 @@ -// @flow -import { useMemo } from 'react'; -import { useDataQuery } from '@dhis2/app-runtime'; - -export const useOrganizationUnit = (ownerOrgUnit: string | boolean) => { - const { error, loading, data, refetch, called } = useDataQuery( - useMemo( - () => ({ - organisationUnits: { - resource: 'organisationUnits', - id: ({ variables: { ownerOrgUnit: id } }) => id, - params: { - fields: ['displayName'], - }, - }, - }), - [], - ), - { - lazy: true, - }, - ); - - if (ownerOrgUnit && !called) { - refetch({ variables: { ownerOrgUnit } }); - } - - return { - error, - displayName: !loading && data?.organisationUnits?.displayName, - }; -}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js index fec7939f6d..de41c3692e 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js +++ b/src/core_modules/capture-core/components/WidgetEnrollment/hooks/useProgram.js @@ -10,7 +10,7 @@ export const useProgram = (programId: string) => { resource: `programs/${programId}`, params: { fields: [ - 'displayIncidentDate,incidentDateLabel,enrollmentDateLabel,onlyEnrollOnce,trackedEntityType[displayName],programStages[autoGenerateEvent],access', + 'displayIncidentDate,displayIncidentDateLabel,displayEnrollmentDateLabel,onlyEnrollOnce,trackedEntityType[displayName],programStages[autoGenerateEvent],access,featureType', ], }, }, @@ -18,5 +18,5 @@ export const useProgram = (programId: string) => { [programId], ), ); - return { error, program: !loading && data?.program }; + return { error, loading, program: data?.program }; }; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/Assignee/Assignee.component.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/Assignee/Assignee.component.js index 165082a0c0..cae71ab09f 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/Assignee/Assignee.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/DataEntry/Assignee/Assignee.component.js @@ -7,7 +7,7 @@ import { UserField } from '../../../FormFields/UserField/UserField.component'; const getStyles = () => ({ container: { display: 'flex', - alignItems: 'center', + alignItems: 'baseline', padding: '8px 8px 8px 12px', }, containerVertical: { diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js index ccdcea6c45..791ff8fa85 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/common.types.js @@ -25,7 +25,6 @@ export type EnrollmentEvent = {| programId: string, programStageId: string, orgUnitId: string, - orgUnitName: string, trackedEntityInstanceId: string, enrollmentId: string, enrollmentStatus: string, diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withDeleteButton.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withDeleteButton.js index 14efa04273..1c55b9ed1b 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withDeleteButton.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withDeleteButton.js @@ -39,33 +39,40 @@ const getDeleteButton = (InnerComponent: React.ComponentType) => {i18n.t('Delete')} - - - {i18n.t('Delete event')} - - - {i18n.t('Deleting an event is permanent and cannot be undone.' + ' ' + - 'Are you sure you want to delete this event? ')} - - - - - - - - + {this.state.isOpen && ( + + + {i18n.t('Delete event')} + + + {i18n.t('Deleting an event is permanent and cannot be undone.' + ' ' + + 'Are you sure you want to delete this event? ')} + + + + + + + + + )}
) : null ) diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withMainButton.js b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withMainButton.js index 42491ef4ab..ba6088af20 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withMainButton.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/DataEntry/withMainButton.js @@ -3,7 +3,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import i18n from '@dhis2/d2-i18n'; import { Button } from '@dhis2/ui'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { type RenderFoundation } from '../../../metaData'; type Props = { diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index e2096656ab..58c4645463 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -5,7 +5,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { spacersNum, Button, colors, IconEdit24, IconArrowLeft24 } from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; import i18n from '@dhis2/d2-i18n'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { useEnrollmentEditEventPageMode, useAvailableProgramStages } from 'capture-core/hooks'; import { useCoreOrgUnit } from 'capture-core/metadataRetrieval/coreOrgUnit'; import type { Props } from './widgetEventEdit.types'; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.component.js b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.component.js new file mode 100644 index 0000000000..012cd0f72a --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.component.js @@ -0,0 +1,38 @@ +// @flow +import React from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { withStyles } from '@material-ui/core/styles'; +import { UserField } from '../../FormFields/UserField/UserField.component'; +import type { Props } from './Assignee.types'; + +const getStyles = () => ({ + container: { + display: 'flex', + alignItems: 'baseline', + padding: '8px 8px 8px 12px', + }, + label: { + fontSize: 14, + flexBasis: 200, + color: '#212934', + }, + field: { + flexBasis: 150, + flexGrow: 1, + }, +}); + +const AssigneePlain = (props: Props) => { + const { classes, assignee, ...passOnProps } = props; + return ( +
+
{i18n.t('Assignee')}
+
+ {/* $FlowFixMe[cannot-spread-inexact] automated comment */} + +
+
+ ); +}; + +export const Assignee = withStyles(getStyles)(AssigneePlain); diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.types.js b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.types.js new file mode 100644 index 0000000000..fdbbccd9f2 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.types.js @@ -0,0 +1,7 @@ +// @flow +import type { UserFormField } from '../../FormFields/UserField'; + +export type Props = { + ...CssClasses, + assignee?: UserFormField, +}; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/index.js b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/index.js new file mode 100644 index 0000000000..0af6b7501f --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/index.js @@ -0,0 +1,2 @@ +// @flow +export { Assignee } from './Assignee.component'; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js b/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js index 84d5f037e3..e181165e6f 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/ScheduleDate/scheduleDate.types.js @@ -12,6 +12,6 @@ export type Props = {| eventCountInOrgUnit: number, suggestedScheduleDate?: ?string, hideDueDate?: boolean, - orgUnit: Object, + orgUnit: { id: string, name: string }, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js index b1e092d37f..6dc9c0a5bc 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js @@ -21,6 +21,7 @@ export const requestScheduleEvent = ({ onSaveExternal, onSaveSuccessActionType, onSaveErrorActionType, + assignedUser, }: { scheduleDate: string, comments: Array<{value: string}>, @@ -34,6 +35,7 @@ export const requestScheduleEvent = ({ onSaveExternal: (eventServerValues: Object, uid: string) => void, onSaveSuccessActionType?: string, onSaveErrorActionType?: string, + assignedUser?: {uid: string}, }) => actionCreator(scheduleEventWidgetActionTypes.EVENT_SCHEDULE_REQUEST)({ scheduleDate, @@ -48,6 +50,7 @@ export const requestScheduleEvent = ({ onSaveExternal, onSaveSuccessActionType, onSaveErrorActionType, + assignedUser, }); export const scheduleEvent = ( diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js index e68f737994..ca2a1f7fbb 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.component.js @@ -10,7 +10,7 @@ import { ScheduleText } from './ScheduleText'; import { CommentSection } from '../WidgetComment'; import type { Props } from './widgetEventSchedule.types'; import { CategoryOptions } from './CategoryOptions/CategoryOptions.component'; - +import { Assignee } from './Assignee'; const styles = () => ({ wrapper: { @@ -66,9 +66,12 @@ const WidgetEventSchedulePlain = ({ suggestedScheduleDate, comments, programCategory, + enableUserAssignment, selectedCategories, onClickCategoryOption, onResetCategoryOption, + onSetAssignee, + assignee, categoryOptionsError, ...passOnProps }: Props) => ( @@ -119,6 +122,11 @@ const WidgetEventSchedulePlain = ({ handleAddComment={onAddComment} /> + {enableUserAssignment && ( + + + + )} { const { program, stage } = useMemo(() => getProgramAndStageForProgram(programId, stageId), [programId, stageId]); const dispatch = useDispatch(); - const { orgUnit } = useOrganisationUnit(orgUnitId, 'displayName'); + const orgUnit = { id: orgUnitId, name: useOrgUnitName(orgUnitId).displayName }; const { programStageScheduleConfig } = useScheduleConfigFromProgramStage(stageId); const { programConfig } = useScheduleConfigFromProgram(programId); const suggestedScheduleDate = useDetermineSuggestedScheduleDate({ @@ -43,6 +44,7 @@ export const WidgetEventSchedule = ({ const { currentUser, noteId } = useCommentDetails(); const [scheduleDate, setScheduleDate] = useState(''); const [comments, setComments] = useState([]); + const [assignee, setAssignee] = useState(); const { events } = useEventsInOrgUnit(orgUnitId, scheduleDate); const { eventId } = useLocationQuery(); const eventCountInOrgUnit = events @@ -80,6 +82,7 @@ export const WidgetEventSchedule = ({ onSaveExternal: onSave, onSaveSuccessActionType, onSaveErrorActionType, + ...(assignee && { assignedUser: convertAssigneeToServer(assignee) }), })); }, [ dispatch, @@ -96,6 +99,7 @@ export const WidgetEventSchedule = ({ onSaveSuccessActionType, onSaveErrorActionType, programCategory, + assignee, ]); React.useEffect(() => { @@ -119,6 +123,7 @@ export const WidgetEventSchedule = ({ setComments([...comments, newComment]); }; + const onSetAssignee = useCallback(user => setAssignee(user), []); const onClickCategoryOption = useCallback((optionId: string, categoryId: string) => { setSelectedCategories(prevCategoryOptions => ({ ...prevCategoryOptions, @@ -159,11 +164,13 @@ export const WidgetEventSchedule = ({ return ( diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.epics.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.epics.js index edd627702f..677bde57d1 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.epics.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.epics.js @@ -29,6 +29,7 @@ export const scheduleEnrollmentEventEpic = (action$: InputObservable, store: Red onSaveExternal, onSaveSuccessActionType, onSaveErrorActionType, + assignedUser, } = action.payload; const { events } = store.value; @@ -47,6 +48,7 @@ export const scheduleEnrollmentEventEpic = (action$: InputObservable, store: Red programStage: stageId, status: 'SCHEDULE', notes: comments ?? [], + assignedUser, }] }; if (attributeCategoryOptions) { diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js index 85ef3325bb..f42a7e8f87 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js @@ -1,5 +1,6 @@ // @flow import type { ProgramCategory, CategoryOption } from './CategoryOptions/CategoryOptions.types'; +import type { UserFormField } from '../FormFields/UserField'; export type ContainerProps = {| programId: string, @@ -38,7 +39,10 @@ export type Props = {| selectedCategories?: ?{ [categoryId: string]: CategoryOption }, programCategory?: ProgramCategory, categoryOptionsError?: ?{[categoryId: string]: { touched: boolean, valid: boolean} }, + enableUserAssignment?: boolean, onSchedule: () => void, + onSetAssignee: (user: UserFormField) => void, + assignee?: UserFormField, onCancel: () => void, setScheduleDate: (date: string) => void, onAddComment: (comment: string) => void, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.js index bb75e2a61d..554648322a 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/ProgramRules/rulesContainer.js @@ -1,12 +1,13 @@ // @flow import type { ProgramRulesContainer } from '@dhis2/rules-engine-javascript'; -import { getTrackedEntityAttributeId, getProgramId, getProgramRuleActions, getProgramStageId } from '../helpers'; +import { getTrackedEntityAttributeId, getDataElementId, getProgramId, getProgramRuleActions, getProgramStageId } from '../helpers'; import { getRulesAndVariablesFromProgramIndicators } from '../../../../metaDataMemoryStoreBuilders/programs/getRulesAndVariablesFromIndicators'; const addProgramVariables = (program, programRuleVariables) => { program.programRuleVariables = programRuleVariables.map(programRulesVariable => ({ ...programRulesVariable, programId: getProgramId(programRulesVariable), + dataElementId: getDataElementId(programRulesVariable), trackedEntityAttributeId: getTrackedEntityAttributeId(programRulesVariable), })); }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js index 868e1c5889..6e606a08d4 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { convertValue } from '../../../../converters/serverToClient'; import { dataElementTypes } from '../../../../metaData'; +import { useOrgUnitNames } from '../../../../metadataRetrieval/orgUnitName'; const convertDate = date => convertValue(date, dataElementTypes.DATE); @@ -9,28 +10,39 @@ const getClientFormattedDataValuesAsObject = (dataValues, elementsById) => dataValues.reduce((acc, { dataElement: id, value }) => { const dataElement = elementsById[id]; if (dataElement) { - acc[id] = convertValue(value, elementsById[id].type); + acc[id] = convertValue(value, dataElement.valueType); } return acc; }, {}); -export const useEvents = (enrollment: any, elementsById: Array) => - useMemo( +const getOrgUnitIds = (enrollment: any): Array => + (enrollment ? enrollment.events.reduce((acc, event) => { + if (event.orgUnit) { + acc.push(event.orgUnit); + } + return acc; + }, []) : []); + +export const useEvents = (enrollment: any, elementsById: Array) => { + const orgUnitIds = useMemo(() => getOrgUnitIds(enrollment), [enrollment]); + const { orgUnitNames } = useOrgUnitNames(orgUnitIds); + return useMemo( () => - enrollment && + enrollment && orgUnitNames && enrollment.events.map(event => ({ eventId: event.event, programId: event.program, programStageId: event.programStage, orgUnitId: event.orgUnit, - orgUnitName: event.orgUnitName, + orgUnitName: orgUnitNames[event.orgUnit], trackedEntityInstanceId: event.trackedEntityInstance, enrollmentId: event.enrollment, enrollmentStatus: event.enrollmentStatus, status: event.status, - eventDate: convertDate(event.eventDate), - dueDate: convertDate(event.dueDate), + occurredAt: convertDate(event.occurredAt), + scheduledAt: convertDate(event.scheduledAt), ...getClientFormattedDataValuesAsObject(event.dataValues, elementsById), })), - [elementsById, enrollment], + [elementsById, enrollment, orgUnitNames], ); +}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js index ce75b815f8..c60464a921 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useLifecycle.js @@ -49,7 +49,7 @@ export const useLifecycle = ({ const state = useSelector(stateArg => stateArg); const enrollment = useSelector(({ enrollmentDomain }) => enrollmentDomain?.enrollment); const dataElements: DataElements = useDataElements(programAPI); - const otherEvents = useEvents(enrollment, programAPI); + const otherEvents = useEvents(enrollment, dataElements); const orgUnit: ?OrgUnit = useOrganisationUnit(orgUnitId).orgUnit; const rulesContainer: ProgramRulesContainer = useRulesContainer(programAPI); const formFoundation: RenderFoundation = useFormFoundation(programAPI); diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.js b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.js index f8a8908b20..77728e6dc8 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.js +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/Stage.component.js @@ -4,7 +4,7 @@ import cx from 'classnames'; import i18n from '@dhis2/d2-i18n'; import { withStyles } from '@material-ui/core'; import { spacersNum, colors, IconAdd16, Button } from '@dhis2/ui'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { StageOverview } from './StageOverview'; import type { Props } from './stage.types'; import { Widget } from '../../../Widget'; diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js index 6d5da181fa..86a4cad8bb 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/Stages/Stage/StageDetail/StageDetail.component.js @@ -16,7 +16,7 @@ import { colors, IconAdd16, Tooltip, } from '@dhis2/ui'; -import { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; +import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { sortDataFromEvent } from './hooks/sortFuntions'; import { useComputeDataFromEvent, useComputeHeaderColumn, formatRowForView } from './hooks/useEventList'; import { DEFAULT_NUMBER_OF_ROW, SORT_DIRECTION } from './hooks/constants'; @@ -103,6 +103,11 @@ const StageDetailPlain = (props: Props) => { onCreateNew(stageId); }, [onCreateNew, stageId]); + const handleShowMore = useCallback(() => { + const nextRowIndex = Math.min(events.length, displayedRowNumber + DEFAULT_NUMBER_OF_ROW); + setDisplayedRowNumber(nextRowIndex); + }, [events, displayedRowNumber, setDisplayedRowNumber]); + function renderHeader() { const headerCells = headerColumns .map(column => ( @@ -181,16 +186,14 @@ const StageDetailPlain = (props: Props) => { } function renderFooter() { - const renderShowMoreButton = () => (events.length > DEFAULT_NUMBER_OF_ROW + const renderShowMoreButton = () => (dataSource && !loading + && events.length > DEFAULT_NUMBER_OF_ROW && displayedRowNumber < events.length ?
); diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/relationshipsWidget.types.js b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/relationshipsWidget.types.js index e7e8d306c3..7dce149334 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/relationshipsWidget.types.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/relationshipsWidget.types.js @@ -6,11 +6,11 @@ import type { LinkedRecordClick } from './types'; export type Props = $ReadOnly<{| title: string, relationships?: Array, - cachedRelationshipTypes?: RelationshipTypes, + relationshipTypes: RelationshipTypes, isLoading: boolean, sourceId: string, onLinkedRecordClick: LinkedRecordClick, - children: (relationshipTypes: RelationshipTypes) => Node, + children: Node, |}>; export type StyledProps = $ReadOnly<{| diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useGroupedLinkedEntities.js b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useGroupedLinkedEntities.js index d339202c7b..095f8e5c8a 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useGroupedLinkedEntities.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/common/RelationshipsWidget/useGroupedLinkedEntities.js @@ -8,7 +8,7 @@ import { dataElementTypes } from '../../../../metaData'; import { RELATIONSHIP_ENTITIES } from '../constants'; import { convertClientToList, convertServerToClient } from '../../../../converters'; import type { GroupedLinkedEntities, LinkedEntityData } from './types'; -import type { InputRelationshipData, RelationshipTypes } from '../Types'; +import type { ApiLinkedEntity, InputRelationshipData, RelationshipTypes } from '../Types'; const getFallbackFieldsByRelationshipEntity = { @@ -137,18 +137,19 @@ const getLinkedEntityData = (apiLinkedEntity, relationshipCreatedAt, pendingApiR return null; }; -const determineLinkedEntity = (fromEntity, toEntity, sourceId) => { - if (fromEntity.trackedEntity?.trackedEntity === sourceId || fromEntity.event?.event === sourceId) { - return toEntity; - } +export const determineLinkedEntity = + (fromEntity: ApiLinkedEntity, toEntity: ApiLinkedEntity, sourceId: string): ApiLinkedEntity | null => { + if (fromEntity.trackedEntity?.trackedEntity === sourceId || fromEntity.event?.event === sourceId) { + return toEntity; + } - if (toEntity.trackedEntity?.trackedEntity === sourceId || toEntity.event?.event === sourceId) { - return fromEntity; - } + if (toEntity.trackedEntity?.trackedEntity === sourceId || toEntity.event?.event === sourceId) { + return fromEntity; + } - log.error(errorCreator('Could not determine linked entity')({ fromEntity, toEntity, sourceId })); - return null; -}; + log.error(errorCreator('Could not determine linked entity')({ fromEntity, toEntity, sourceId })); + return null; + }; export const useGroupedLinkedEntities = ( sourceId: string, @@ -184,6 +185,10 @@ export const useGroupedLinkedEntities = ( return accGroupedLinkedEntities; } + if (!relationshipType.bidirectional && apiLinkedEntity === fromEntity) { + return accGroupedLinkedEntities; + } + const linkedEntityData = getLinkedEntityData(apiLinkedEntity, relationshipCreatedAt, pendingApiResponse); if (!linkedEntityData) { return accGroupedLinkedEntities; diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/common/useRelationships/useRelationships.js b/src/core_modules/capture-core/components/WidgetsRelationship/common/useRelationships/useRelationships.js index 9c5b323232..7f626a84c1 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/common/useRelationships/useRelationships.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/common/useRelationships/useRelationships.js @@ -1,7 +1,8 @@ // @flow import { useMemo } from 'react'; import { useApiDataQuery } from '../../../../utils/reactQueryHelpers'; -import type { InputRelationshipData } from '../Types'; +import type { InputRelationshipData, RelationshipTypes } from '../Types'; +import { determineLinkedEntity } from '../RelationshipsWidget/useGroupedLinkedEntities'; export const RelationshipSearchEntities = Object.freeze({ TRACKED_ENTITY: 'trackedEntity', @@ -9,12 +10,19 @@ export const RelationshipSearchEntities = Object.freeze({ EVENT: 'event', }); +type Props = {| + entityId: string, + searchMode: $Values, + relationshipTypes: ?RelationshipTypes, +|} + type ReturnData = Array; -export const useRelationships = (entityId: string, searchMode: string) => { +export const useRelationships = ({ entityId, searchMode, relationshipTypes }: Props) => { const query = useMemo(() => ({ resource: 'tracker/relationships', params: { + // $FlowFixMe - searchMode should be a valid key of RelationshipSearchEntities [searchMode]: entityId, fields: 'relationshipType,createdAt,from[trackedEntity[trackedEntity,attributes,program,orgUnit,trackedEntityType],event[event,dataValues,program,orgUnit,orgUnitName,status,createdAt]],to[trackedEntity[trackedEntity,attributes,program,orgUnit,trackedEntityType],event[event,dataValues,program,orgUnit,orgUnitName,status,createdAt]]', }, @@ -25,7 +33,32 @@ export const useRelationships = (entityId: string, searchMode: string) => { query, { enabled: !!entityId, - select: ({ instances }: any) => instances, + select: ({ instances }: any) => { + if (!relationshipTypes?.length || !instances?.length) { + return []; + } + + return instances.reduce((acc, relationship) => { + const relationshipType = relationshipTypes + .find(relType => relType.id === relationship.relationshipType); + if (!relationshipType) { + return acc; + } + const { from, to } = relationship; + const apiLinkedEntity = determineLinkedEntity(from, to, entityId); + + if (!apiLinkedEntity) { + return acc; + } + + if (!relationshipType.bidirectional && apiLinkedEntity === from) { + return acc; + } + + acc.push(relationship); + return acc; + }, []); + }, }, ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ViewMenuSetup/DownloadDialog/DownloadDialog.component.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ViewMenuSetup/DownloadDialog/DownloadDialog.component.js index fc79445ce2..0ed117534b 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ViewMenuSetup/DownloadDialog/DownloadDialog.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ViewMenuSetup/DownloadDialog/DownloadDialog.component.js @@ -3,8 +3,7 @@ import React, { PureComponent, type ComponentType } from 'react'; import i18n from '@dhis2/d2-i18n'; import { withStyles } from '@material-ui/core/styles'; -import { Dialog, DialogTitle, DialogContent, DialogActions } from '@material-ui/core'; -import { Button } from '@dhis2/ui'; +import { Button, Modal, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; import type { Props } from './downloadDialog.types'; const getStyles = () => ({ @@ -80,23 +79,28 @@ class DownloadDialogPlain extends PureComponent { } render() { const { open, onClose } = this.props; + + if (!open) { + return null; + } + return ( - - {i18n.t('Download with current filters')} - + {i18n.t('Download with current filters')} + {this.renderButtons()} - - + + - - + + ); } diff --git a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/helpers/getListDataCommon/getSubvalues.js b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/helpers/getListDataCommon/getSubvalues.js index a147346cbb..e8ccd5dd9b 100644 --- a/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/helpers/getListDataCommon/getSubvalues.js +++ b/src/core_modules/capture-core/components/WorkingLists/TeiWorkingLists/epics/teiViewEpics/helpers/getListDataCommon/getSubvalues.js @@ -2,6 +2,7 @@ import log from 'loglevel'; import { createSelector } from 'reselect'; import { errorCreator } from 'capture-core-utils'; +import { getOrgUnitNames } from '../../../../../../../metadataRetrieval/orgUnitName'; import { dataElementTypes } from '../../../../../../../metaData'; import type { SubvalueKeysByType, @@ -30,29 +31,20 @@ const getSubvaluesPlain = (querySingleResource: QuerySingleResource, absoluteApi }, {}); }; - const getOrganisationUnitSubvalue = async (keys: Array) => { - const ids = keys - .join(','); + const getOrganisationUnitSubvalue = async (keys: Array) => + getOrgUnitNames(keys, querySingleResource); - const { organisationUnits = [] } = await querySingleResource({ resource: 'organisationUnits', params: { filter: `id:in:[${ids}]` } }); - - return organisationUnits - .reduce((acc, { id, displayName: name }) => { - acc[id] = { - id, - name, - }; - return acc; - }, {}); - }; - - const subvalueGetterByType: {|[string]: any |} = { + const subvalueGetterByType: {| + [string]: any, + |} = { [dataElementTypes.ORGANISATION_UNIT]: getOrganisationUnitSubvalue, [dataElementTypes.IMAGE]: getImageOrFileResourceSubvalue, [dataElementTypes.FILE_RESOURCE]: getImageOrFileResourceSubvalue, }; - const subvaluePostProcessorByType: {|[string]: any |} = { + const subvaluePostProcessorByType: {| + [string]: any, + |} = { [dataElementTypes.IMAGE]: ({ subvalueKey: value, subvalue: name, diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/DeleteConfirmationDialog.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/DeleteConfirmationDialog.component.js index 137dab9b63..635bac6d3e 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/DeleteConfirmationDialog.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/DeleteConfirmationDialog.component.js @@ -2,8 +2,7 @@ import * as React from 'react'; import { withStyles } from '@material-ui/core/styles'; import i18n from '@dhis2/d2-i18n'; -import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@material-ui/core'; -import { Button } from '@dhis2/ui'; +import { Button, Modal, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; const getStyles = () => ({ buttonContainer: { @@ -29,18 +28,20 @@ const DeleteConfirmationDialogPlain = (props: Props) => { classes, } = props; + if (!open) { + return null; + } + return ( - - {i18n.t('Delete view')} - - - {i18n.t('Do you really want to delete the \'{{templateName}}\' view?', { templateName })} - - - {i18n.t('Delete view')} + + {i18n.t('Do you really want to delete the \'{{templateName}}\' view?', { templateName })} + + - - + + ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateContents.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateContents.component.js index 28b526c2cb..0efef529f0 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateContents.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateContents.component.js @@ -2,8 +2,7 @@ import * as React from 'react'; import { withStyles } from '@material-ui/core/styles'; import i18n from '@dhis2/d2-i18n'; -import { DialogTitle, DialogContent, DialogActions } from '@material-ui/core'; -import { Button } from '@dhis2/ui'; +import { Button, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; const getStyles = () => ({ buttonContainer: { @@ -22,9 +21,9 @@ const ExistingTemplateContentsPlain = (props: Props) => { const { onSaveTemplate, onClose, classes } = props; return ( - {i18n.t('Save')} - - {i18n.t('Save')} + + - + ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateDialog.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateDialog.component.js index e8e1af626e..da394131b6 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateDialog.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/ExistingTemplateDialog.component.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { Dialog } from '@material-ui/core'; +import { Modal } from '@dhis2/ui'; import { ExistingTemplateContents } from './ExistingTemplateContents.component'; type Props = { @@ -16,16 +16,21 @@ export const ExistingTemplateDialog = (props: Props) => { onSaveTemplate, } = props; + if (!open) { + return null; + } + return ( - - + ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateContents.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateContents.component.js index 460069e359..c064f74893 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateContents.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateContents.component.js @@ -1,9 +1,8 @@ // @flow import * as React from 'react'; -import { colors, Button } from '@dhis2/ui'; +import { colors, Button, ModalTitle, ModalContent, ModalActions } from '@dhis2/ui'; import { withStyles } from '@material-ui/core/styles'; import i18n from '@dhis2/d2-i18n'; -import { DialogTitle, DialogContent, DialogActions } from '@material-ui/core'; import { NewTemplateTextField } from './NewTemplateTextField.component'; const getStyles = (theme: Theme) => ({ @@ -46,8 +45,8 @@ const NewTemplateContentsPlain = (props: Props) => { return ( - {i18n.t('Save As view')} - + {i18n.t('Save As view')} + { > {error}
- - + - + ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateDialog.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateDialog.component.js index d10a394168..60859c6f5a 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateDialog.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/NewTemplateDialog.component.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react'; -import { Dialog } from '@material-ui/core'; +import { Modal } from '@dhis2/ui'; import { NewTemplateContents } from './NewTemplateContents.component'; type Props = { @@ -16,16 +16,21 @@ export const NewTemplateDialog = (props: Props) => { onSaveTemplate, } = props; + if (!open) { + return null; + } + return ( - - + ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/SharingDialog.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/SharingDialog.component.js index 173f3ebfd2..9434601bea 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/SharingDialog.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/SharingDialog.component.js @@ -12,7 +12,7 @@ const styles = { }, }; -const SharingDialogPlain = ({ onClose, open, templateId, classes, templateSharingType }: Props) => { +const SharingDialogPlain = ({ onClose, open, templateId, classes, templateSharingType, dataTest }: Props) => { const { refetch } = useDataQuery( useMemo( () => ({ @@ -61,6 +61,7 @@ const SharingDialogPlain = ({ onClose, open, templateId, classes, templateSharin id={templateId} onClose={handleClose} className={classes.dialog} + dataTest={dataTest} /> : null ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/TemplateMaintenance.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/TemplateMaintenance.component.js index 6167966094..714ff7177a 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/TemplateMaintenance.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/TemplateMaintenance.component.js @@ -65,6 +65,7 @@ const TemplateMaintenancePlain = (props: Props, ref) => { templateId={currentTemplate.id} onClose={handleSetSharingSettings} templateSharingType={templateSharingType} + dataTest={'sharing-dialog'} /> ); diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/sharingDialog.types.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/sharingDialog.types.js index d2abcf26b2..9eed85d346 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/sharingDialog.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateMaintenance/sharingDialog.types.js @@ -6,5 +6,6 @@ export type Props = {| open: boolean, templateId: string, templateSharingType: string, + dataTest: string, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChip.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChip.component.js index 5ce7331edf..238423ef2c 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChip.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChip.component.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { Chip } from '@dhis2/ui'; import { TemplateSelectorChipContent } from './TemplateSelectorChipContent.component'; import type { WorkingListTemplate } from './workingListsBase.types'; +import { TooltipForChip } from '../../Tooltips/TooltipForChip'; type PassOnProps = { currentListIsModified: boolean, @@ -13,10 +14,17 @@ type Props = { template: WorkingListTemplate, currentTemplateId: string, onSelectTemplate: Function, + maxCharacters?: number, }; export const TemplateSelectorChip = (props: Props) => { - const { template, currentTemplateId, onSelectTemplate, ...passOnProps } = props; + const { + template, + currentTemplateId, + onSelectTemplate, + maxCharacters = 30, + ...passOnProps + } = props; const { name, id } = template; const selectTemplateHandler = React.useCallback(() => { @@ -27,16 +35,29 @@ export const TemplateSelectorChip = (props: Props) => { ]); return ( - maxCharacters} onClick={selectTemplateHandler} > - - + + + + ); }; + diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChipContent.component.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChipContent.component.js index 57d0c53205..d9efe28079 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChipContent.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/TemplateSelectorChipContent.component.js @@ -2,6 +2,7 @@ type Props = { currentListIsModified: boolean, + maxCharacters: number, text: string, isSelectedTemplate: boolean, }; @@ -10,10 +11,11 @@ export const TemplateSelectorChipContent = (props: Props) => { const { currentListIsModified, text, + maxCharacters, isSelectedTemplate, } = props; - const truncatedText = text.length > 30 ? `${text.substring(0, 27)}...` : text; + const truncatedText = text.length > maxCharacters ? `${text.substring(0, maxCharacters - 3)}...` : text; const content = isSelectedTemplate && currentListIsModified ? `${truncatedText} *` : truncatedText; return content; diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js index fc9148b995..f6b8f82ef9 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js @@ -75,7 +75,12 @@ export type CancelUpdateList = () => void; export type DeleteTemplate = (template: WorkingListTemplate) => void; export type LoadView = ( template: WorkingListTemplate, - meta: {| programId: string, orgUnitId: string, categories?: Categories, programStageId?: string |}) => void; + meta: {| + programId: string, + orgUnitId: string, + categories?: Categories, + programStageId?: string, + |}) => void; export type LoadTemplates = (programId: string) => void; export type SelectTemplate = (templateId: string, programStageId?: ?string) => void; export type UnloadingContext = () => void; diff --git a/src/core_modules/capture-core/converters/clientToServer.js b/src/core_modules/capture-core/converters/clientToServer.js index 7477352af6..49b4af1021 100644 --- a/src/core_modules/capture-core/converters/clientToServer.js +++ b/src/core_modules/capture-core/converters/clientToServer.js @@ -8,6 +8,13 @@ type RangeValue = { to: number, } +type Assignee = { + id: string, + username: string, + name: string, + firstName: string, + surname: string, +} function convertDate(rawValue: string): string { const editedDate = rawValue; @@ -66,3 +73,13 @@ export function convertCategoryOptionsToServer(value: {[categoryId: string]: str } return value; } + +export function convertAssigneeToServer(assignee: Assignee): ApiAssignedUser { + return { + uid: assignee.id, + displayName: assignee.name, + username: assignee.username, + firstName: assignee.firstName, + surname: assignee.surname, + }; +} diff --git a/src/core_modules/capture-core/converters/index.js b/src/core_modules/capture-core/converters/index.js index 74b4d0e019..82eb3936a6 100644 --- a/src/core_modules/capture-core/converters/index.js +++ b/src/core_modules/capture-core/converters/index.js @@ -2,7 +2,7 @@ export { convertValue as convertClientToForm } from './clientToForm'; export { convertValue as convertClientToList } from './clientToList'; export { convertValue as convertClientToView, convertDateWithTimeForView } from './clientToView'; -export { convertValue as convertClientToServer } from './clientToServer'; +export { convertValue as convertClientToServer, convertAssigneeToServer } from './clientToServer'; export { convertValue as convertFormToClient } from './formToClient'; export { convertValue as convertServerToClient, diff --git a/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js b/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js index 5472a1ccfa..45760736e7 100644 --- a/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js +++ b/src/core_modules/capture-core/dataQueries/useOrganisationUnit.js @@ -5,9 +5,14 @@ import log from 'loglevel'; import { errorCreator } from '../../capture-core-utils'; // Skips fetching if orgUnitId is falsy -export const useOrganisationUnit = (orgUnitId: string, fields?: string) => { +export const useOrganisationUnit = (orgUnitId: string, fields?: string): { + orgUnit: any, + error: any, +} => { const [orgUnit, setOrgUnit] = useState(); - const { error, loading, data, refetch, called } = useDataQuery( + const [requestedOrgUnitId, setRequestedOrgUnitId] = useState(); + const [fetchingInProgress, setFetchingInProgress] = useState(false); + const { error, data, loading, refetch } = useDataQuery( useMemo( () => ({ organisationUnits: { @@ -25,8 +30,13 @@ export const useOrganisationUnit = (orgUnitId: string, fields?: string) => { ); useEffect(() => { - orgUnitId && refetch({ variables: { orgUnitId } }); - }, [refetch, orgUnitId]); + if (!fetchingInProgress && orgUnitId && orgUnitId !== requestedOrgUnitId) { + setFetchingInProgress(true); + setRequestedOrgUnitId(orgUnitId); + setOrgUnit(undefined); + refetch({ variables: { orgUnitId } }); + } + }, [fetchingInProgress, orgUnitId, requestedOrgUnitId, setFetchingInProgress, setRequestedOrgUnitId, refetch]); useEffect(() => { if (error) { @@ -35,20 +45,19 @@ export const useOrganisationUnit = (orgUnitId: string, fields?: string) => { }, [error]); useEffect(() => { - const organisationUnit = data?.organisationUnits; - orgUnitId && setOrgUnit( - (loading || !called || error) ? - undefined : { + if (fetchingInProgress && !loading) { + setFetchingInProgress(false); + if (orgUnitId === requestedOrgUnitId && !error) { + setOrgUnit({ id: orgUnitId, - name: organisationUnit?.displayName, - code: organisationUnit?.code, - ...organisationUnit, - }, - ); - }, [orgUnitId, data, loading, called, error]); + ...data.organisationUnits, + }); + } + } + }, [data, loading, error, fetchingInProgress, setFetchingInProgress, orgUnitId, requestedOrgUnitId]); - return { + return (orgUnitId && orgUnitId === requestedOrgUnitId) ? { error, orgUnit, - }; + } : {}; }; diff --git a/src/core_modules/capture-core/events/eventRequests.js b/src/core_modules/capture-core/events/eventRequests.js index 4c966899ce..e598b93a16 100644 --- a/src/core_modules/capture-core/events/eventRequests.js +++ b/src/core_modules/capture-core/events/eventRequests.js @@ -17,7 +17,6 @@ type ApiTEIEvent = { program: string, programStage: string, orgUnit: string, - orgUnitName: string, trackedEntityInstance?: string, enrollment?: string, enrollmentStatus?: string, diff --git a/src/core_modules/capture-core/flow/apiTypes.js b/src/core_modules/capture-core/flow/apiTypes.js index b11decd46e..23766ea4a4 100644 --- a/src/core_modules/capture-core/flow/apiTypes.js +++ b/src/core_modules/capture-core/flow/apiTypes.js @@ -1,5 +1,13 @@ // @flow +declare type ApiAssignedUser = {| + uid: string, + username: string, + displayName: string, + firstName: string, + surname: string, +|}; + declare type ApiDataValue = { dataElement: string, value: string, @@ -10,7 +18,6 @@ declare type ApiEnrollmentEvent = {| program: string, programStage: string, orgUnit: string, - orgUnitName: string, trackedEntityInstance: string, enrollment: string, enrollmentStatus: string, diff --git a/src/core_modules/capture-core/flow/typeDeclarations.js b/src/core_modules/capture-core/flow/typeDeclarations.js index 15bdc712cd..4f245e5882 100644 --- a/src/core_modules/capture-core/flow/typeDeclarations.js +++ b/src/core_modules/capture-core/flow/typeDeclarations.js @@ -45,7 +45,6 @@ declare type CaptureClientEvent = { programId: string, programStageId: string, orgUnitId: string, - orgUnitName: string, trackedEntityInstanceId?: string, enrollmentId?: string, enrollmentStatus?: string, diff --git a/src/core_modules/capture-core/metaData/DataElement/DataElement.js b/src/core_modules/capture-core/metaData/DataElement/DataElement.js index 5555d4842e..78fe1d959a 100644 --- a/src/core_modules/capture-core/metaData/DataElement/DataElement.js +++ b/src/core_modules/capture-core/metaData/DataElement/DataElement.js @@ -35,6 +35,7 @@ export class DataElement { _displayInReports: boolean; _icon: Icon | void; _unique: ?Unique; + _inherit: boolean; _searchable: ?boolean; _url: ?string; _attributeValues: Array @@ -157,6 +158,14 @@ export class DataElement { return this._unique; } + get inherit(): boolean { + return this._inherit; + } + + set inherit(value: boolean) { + this._inherit = value; + } + set searchable(searchable: boolean) { this._searchable = searchable; } diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js index 18e56e1869..27d1222735 100644 --- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js +++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/programs/factory/enrollment/DataElementFactory.js @@ -164,6 +164,7 @@ export class DataElementFactory { dataElement.compulsory = cachedProgramTrackedEntityAttribute.mandatory; dataElement.code = cachedTrackedEntityAttribute.code; dataElement.attributeValues = cachedTrackedEntityAttribute.attributeValues; + dataElement.inherit = cachedTrackedEntityAttribute.inherit; dataElement.name = this._getAttributeTranslation( cachedTrackedEntityAttribute.translations, diff --git a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js index bc27da80ed..723d148d3c 100644 --- a/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js +++ b/src/core_modules/capture-core/metaDataMemoryStoreBuilders/trackedEntityTypes/factory/TrackedEntityType/DataElementFactory.js @@ -112,6 +112,7 @@ export class DataElementFactory { cachedAttribute.translations, DataElementFactory.translationPropertyNames.DESCRIPTION) || cachedAttribute.description; o.displayInForms = true; + o.inherit = cachedAttribute.inherit; o.displayInReports = cachedTrackedEntityTypeAttribute.displayInList; o.disabled = false; o.type = cachedAttribute.valueType; diff --git a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js index 8624bb577e..7658ff4a8b 100644 --- a/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js +++ b/src/core_modules/capture-core/metaDataStoreLoaders/trackedEntityAttributes/quickStoreOperations/storeTrackedEntityAttributes.js @@ -7,7 +7,7 @@ export const storeTrackedEntityAttributes = (ids: Array) => { resource: 'trackedEntityAttributes', params: { fields: 'id,displayName,displayShortName,displayFormName,description,valueType,optionSetValue,unique,orgunitScope,' + - 'pattern,code,attributeValues,translations[property,locale,value],optionSet[id]', + 'pattern,code,attributeValues,inherit,translations[property,locale,value],optionSet[id]', filter: `id:in:[${ids.join(',')}]`, pageSize: ids.length, }, diff --git a/src/core_modules/capture-core/metadataRetrieval/orgUnitName/index.js b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/index.js new file mode 100644 index 0000000000..f254f83a12 --- /dev/null +++ b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/index.js @@ -0,0 +1,7 @@ +// @flow +export { + useOrgUnitName, + useOrgUnitNames, + getOrgUnitNames, + getCachedOrgUnitName, +} from './orgUnitName'; diff --git a/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.js b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.js new file mode 100644 index 0000000000..ca45d9de40 --- /dev/null +++ b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.js @@ -0,0 +1,173 @@ +// @flow +import { useState, useMemo, useCallback, useEffect } from 'react'; +import { useDataQuery } from '@dhis2/app-runtime'; +import { useOrganisationUnit } from '../../dataQueries'; +import type { OrgUnitNames } from './orgUnitName.types'; +import type { QuerySingleResource } from '../../utils/api'; + +// Avoid exporting displayNameCache to keep it truly private. +// As a consequence all functions using it must be in this file. +const displayNameCache = {}; +const maxBatchSize = 50; + +const displayNamesQuery = { + organisationUnits: { + resource: 'organisationUnits', + params: ({ filter }) => ({ + fields: 'id,displayName', + filter: `id:in:[${filter}]`, + pageSize: maxBatchSize, + }), + }, +}; + +const createBatches = (orgUnitIds: Array): Array> => { + const reducedOrgUnitIds = Array.from(orgUnitIds + .filter(id => id) + .reduce((acc, id) => { + if (!displayNameCache[id]) { + acc.add(id); + } + return acc; + }, new Set())); + + const batches = []; + for (let i = 0; i < reducedOrgUnitIds.length; i += maxBatchSize) { + batches.push(reducedOrgUnitIds.slice(i, i + maxBatchSize)); + } + + return batches; +}; + +// Works best with memoized input arrays. +export const useOrgUnitNames = (orgUnitIds: Array): { + loading: boolean, + orgUnitNames: ?OrgUnitNames, + error: any, +} => { + const [fetching, setFetching] = useState(false); + const [fetchNextBatch, setFetchNextBatch] = useState(false); + const [requestedArray, setRequestedArray] = useState(); + const [currentBatches, setCurrentBatches] = useState([]); + const [completedBatches, setCompletedBatches] = useState(0); + const [error, setError] = useState(); + + const ready = !fetching && orgUnitIds === requestedArray; + + const batches = useMemo( + () => createBatches(orgUnitIds), + [orgUnitIds], + ); + const filter = useMemo( + () => (fetching ? currentBatches[completedBatches].join(',') : ''), + [fetching, currentBatches, completedBatches], + ); + const result = useMemo( + () => (ready ? orgUnitIds.reduce((acc, id) => { + acc[id] = displayNameCache[id]; + return acc; + }, {}) : null), + [ready, orgUnitIds], + ); + + const onComplete = useCallback(({ organisationUnits }) => { + for (const { id, displayName } of organisationUnits.organisationUnits) { + displayNameCache[id] = displayName; + } + const completeCount = completedBatches + 1; + setCompletedBatches(completeCount); + if (completeCount === currentBatches.length) { + setFetching(false); + } else { + setFetchNextBatch(true); + } + }, [completedBatches, setCompletedBatches, currentBatches, setFetching, setFetchNextBatch]); + + const onError = useCallback( + (fetchError) => { + setFetching(false); + setError(fetchError); + }, + [setFetching, setError], + ); + + const { refetch } = useDataQuery( + displayNamesQuery, { + variables: { filter }, + onComplete, + onError, + lazy: true, + }, + ); + + useEffect(() => { + if (!fetching && orgUnitIds !== requestedArray) { + setRequestedArray(orgUnitIds); + if (batches.length > 0) { + setFetching(true); + setError(undefined); + setFetchNextBatch(true); + setCurrentBatches(batches); + setCompletedBatches(0); + } + } + }, [fetching, orgUnitIds, requestedArray, batches, setRequestedArray, setCurrentBatches, setCompletedBatches, setFetching, setError]); + + useEffect(() => { + if (fetchNextBatch) { + setFetchNextBatch(false); + if (completedBatches < currentBatches.length) { + refetch({ filter }); + } + } + }, [fetchNextBatch, setFetchNextBatch, completedBatches, currentBatches, refetch, filter]); + + return { + loading: !ready && !error, + orgUnitNames: ready && !error ? result : undefined, + error, + }; +}; + +export async function getOrgUnitNames(orgUnitIds: Array, querySingleResource: QuerySingleResource): Promise<{| + [orgUnitId: string]: {| + id: string, + displayName: string, + |} +|}> { + await Promise.all(createBatches(orgUnitIds) + .map(batch => querySingleResource(displayNamesQuery.organisationUnits, { filter: batch.join(',') }) + .then(({ organisationUnits }) => { + for (const { id, displayName } of organisationUnits) { + displayNameCache[id] = displayName; + } + }))); + + return orgUnitIds.reduce((acc, orgUnitId) => { + acc[orgUnitId] = { + id: orgUnitId, + name: displayNameCache[orgUnitId], + }; + return acc; + }, {}); +} + +export const useOrgUnitName = (orgUnitId: ?string): { + displayName?: string, + error?: any, +} => { + const cachedOrgUnitName = orgUnitId && displayNameCache[orgUnitId]; + const fetchId = cachedOrgUnitName ? undefined : orgUnitId; + const { orgUnit, error } = useOrganisationUnit(fetchId, 'displayName'); + if (cachedOrgUnitName) { + return { displayName: cachedOrgUnitName }; + } else if (orgUnit && fetchId) { + displayNameCache[orgUnit.id] = orgUnit.displayName; + if (orgUnit.id === fetchId) { + return { displayName: orgUnit.displayName, error }; + } + } + return { error }; +}; + +export const getCachedOrgUnitName = (orgUnitId: string): ?string => displayNameCache[orgUnitId]; diff --git a/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.types.js b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.types.js new file mode 100644 index 0000000000..97a8f48167 --- /dev/null +++ b/src/core_modules/capture-core/metadataRetrieval/orgUnitName/orgUnitName.types.js @@ -0,0 +1,4 @@ +// @flow +export type OrgUnitNames = {| + [orgUnitId: string]: string, +|}; diff --git a/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap b/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap index 2c73fac45b..f2fb347ece 100644 --- a/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap +++ b/src/core_modules/capture-core/rules/__tests__/__snapshots__/getApplicableRuleEffectsForTrackerProgram.test.js.snap @@ -125,7 +125,7 @@ Object { "trackedEntityAttributes": Object { "lZGmxYbs96q": Object { "id": "lZGmxYbs96q", - "optionSetId": undefined, + "optionSetId": "optionSet", "valueType": "DATE", }, "lZGmxYbs97q": Object { diff --git a/src/core_modules/capture-core/rules/__tests__/getApplicableRuleEffectsForTrackerProgram.test.js b/src/core_modules/capture-core/rules/__tests__/getApplicableRuleEffectsForTrackerProgram.test.js index 0ad48833c4..23e05892ba 100644 --- a/src/core_modules/capture-core/rules/__tests__/getApplicableRuleEffectsForTrackerProgram.test.js +++ b/src/core_modules/capture-core/rules/__tests__/getApplicableRuleEffectsForTrackerProgram.test.js @@ -1,3 +1,4 @@ +import { variableSourceTypes } from '@dhis2/rules-engine-javascript'; import { TrackerProgram, ProgramStage, @@ -10,24 +11,27 @@ import { } from '../../metaData'; import { getApplicableRuleEffectsForTrackerProgram } from '..'; -const mockGetProgramRuleEffects = jest.fn().mockImplementation(() => [{ - id: 'effectId', - type: 'DISPLAYTEXT', - message: 'display effect', -}]); +const mockGetProgramRuleEffects = jest.fn().mockImplementation(() => [ + { + id: 'effectId', + type: 'DISPLAYTEXT', + message: 'display effect', + }, +]); const mockOptionSet = new OptionSet('optionSet1', [new Option('option1', 'opt1')]); jest.mock('@dhis2/rules-engine-javascript/build/cjs/RulesEngine', () => ({ - RulesEngine: jest.fn().mockImplementation(() => - ({ getProgramRuleEffects: (...args) => mockGetProgramRuleEffects(...args) })), + RulesEngine: jest + .fn() + .mockImplementation(() => ({ getProgramRuleEffects: (...args) => mockGetProgramRuleEffects(...args) })), })); jest.mock('../../metaDataMemoryStores/constants/constants.store', () => ({ - constantsStore: ({ get: () => [{ id: 'constantId1', value: '1' }] }), + constantsStore: { get: () => [{ id: 'constantId1', value: '1' }] }, })); jest.mock('../../metaDataMemoryStores/optionSets/optionSets.store', () => ({ - optionSetStore: ({ get: () => [mockOptionSet] }), + optionSetStore: { get: () => [mockOptionSet] }, })); describe('getApplicableRuleEffectsForTrackerProgram', () => { @@ -46,20 +50,22 @@ describe('getApplicableRuleEffectsForTrackerProgram', () => { trackedEntityInstanceId: 'vCGpQAWG17I', }; - const otherEvents = [{ - da1Id: 'otherEventText', - dueDate: '2021-05-31T09:51:38.134', - enrollmentId: 'vVtmDlsu3me', - enrollmentStatus: 'ACTIVE', - eventDate: '2021-05-31T00:00:00.000', - eventId: 'BxGzDJK3JqN', - orgUnitId: 'DiszpKrYNg8', - orgUnitName: 'Ngelehun CHC', - programId: 'IpHINAT79UW', - programStageId: 'A03MvHHogjR', - status: 'ACTIVE', - trackedEntityInstanceId: 'vCGpQAWG17I', - }]; + const otherEvents = [ + { + da1Id: 'otherEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + }, + ]; const orgUnit = { id: 'DiszpKrYNg8', code: 'Ngelehun CHC' }; @@ -72,16 +78,18 @@ describe('getApplicableRuleEffectsForTrackerProgram', () => { const programStage = new ProgramStage((stage) => { stage.id = 'st1Id'; stage.name = 'stage1'; - stage.programRules = [{ - id: 'rule1Id', - name: 'rule1', - displayName: 'rule1', - priority: 1, - condition: 'true', - programId: 'IpHINAT79UW', - programStageId: 'st1Id', - programRuleActions: [], - }]; + stage.programRules = [ + { + id: 'rule1Id', + name: 'rule1', + displayName: 'rule1', + priority: 1, + condition: 'true', + programId: 'IpHINAT79UW', + programStageId: 'st1Id', + programRuleActions: [], + }, + ]; stage.stageForm = new RenderFoundation((foundation) => { const section = new Section((initSection) => { @@ -115,6 +123,7 @@ describe('getApplicableRuleEffectsForTrackerProgram', () => { element3.id = 'lZGmxYbs96q'; element3.name = 'SomeDate'; element3.type = dataElementTypes.DATE; + element3.optionSet = { id: 'optionSet', name: 'optionSet', code: 'optionSet' }; }), new DataElement((element4) => { element4.id = 'w75KJ2mc4zz'; @@ -129,7 +138,7 @@ describe('getApplicableRuleEffectsForTrackerProgram', () => { displayName: 'Test', id: 'PUQZWgmQ0jx', programId: 'IpHINAT79UW', - programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, useNameForOptionSet: true, }, { @@ -137,7 +146,7 @@ describe('getApplicableRuleEffectsForTrackerProgram', () => { displayName: 'apgarcomment', id: 'aKpfPKSRQnv', programId: 'IpHINAT79UW', - programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, useNameForOptionSet: true, }, { @@ -145,23 +154,27 @@ describe('getApplicableRuleEffectsForTrackerProgram', () => { displayName: 'apgarscore', id: 'g2GooOydipB', programId: 'IpHINAT79UW', - programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, useNameForOptionSet: true, }, ]; - initProgram.programRules = [{ - condition: 'true', - displayName: 'TestRule', - id: 'JJDQxgHuuL2', - programId: 'IpHINAT79UW', - programRuleActions: [{ - data: '#{Test}', - id: 'CQaifjkoFEU', - location: 'feedback', - programRuleActionType: 'DISPLAYTEXT', - }], - }]; + initProgram.programRules = [ + { + condition: 'true', + displayName: 'TestRule', + id: 'JJDQxgHuuL2', + programId: 'IpHINAT79UW', + programRuleActions: [ + { + data: '#{Test}', + id: 'CQaifjkoFEU', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + ], + }, + ]; }); const attributeValues = { @@ -210,16 +223,51 @@ describe('getApplicableRuleEffectsForTrackerProgram', () => { }); test('Flat result', () => { + const effects = getApplicableRuleEffectsForTrackerProgram( + { + program, + stage: programStage, + orgUnit, + currentEvent, + otherEvents, + attributeValues, + enrollmentData, + }, + true, + ); + + expect(Array.isArray(effects)).toBe(true); + }); + + test('RulesEngine called without programRules', () => { + const effects = getApplicableRuleEffectsForTrackerProgram({ + program: new TrackerProgram((initProgram) => { + initProgram.programRules = []; + }), + stage: new ProgramStage((stage) => { + stage.programRules = []; + }), + orgUnit, + currentEvent, + otherEvents, + attributeValues, + enrollmentData, + }); + + expect(effects).toStrictEqual([]); + }); + + test('currentEvent without a programStageId', () => { const effects = getApplicableRuleEffectsForTrackerProgram({ program, stage: programStage, orgUnit, - currentEvent, + currentEvent: {}, otherEvents, attributeValues, enrollmentData, - }, true); + }); - expect(Array.isArray(effects)).toBe(true); + expect(effects.DISPLAYTEXT).toBeDefined(); }); }); diff --git a/src/core_modules/capture-core/rules/__tests__/getApplicableRulesEffectsForEventProgram.test.js b/src/core_modules/capture-core/rules/__tests__/getApplicableRulesEffectsForEventProgram.test.js index 0fd9ac1510..7efadd5453 100644 --- a/src/core_modules/capture-core/rules/__tests__/getApplicableRulesEffectsForEventProgram.test.js +++ b/src/core_modules/capture-core/rules/__tests__/getApplicableRulesEffectsForEventProgram.test.js @@ -1,3 +1,4 @@ +import { variableSourceTypes } from '@dhis2/rules-engine-javascript'; import { EventProgram, ProgramStage, @@ -10,24 +11,27 @@ import { } from '../../metaData'; import { getApplicableRuleEffectsForEventProgram } from '..'; -const mockGetProgramRuleEffects = jest.fn().mockImplementation(() => [{ - id: 'effectId', - type: 'DISPLAYTEXT', - message: 'display effect', -}]); +const mockGetProgramRuleEffects = jest.fn().mockImplementation(() => [ + { + id: 'effectId', + type: 'DISPLAYTEXT', + message: 'display effect', + }, +]); const mockOptionSet = new OptionSet('optionSet1', [new Option('option1', 'opt1')]); jest.mock('@dhis2/rules-engine-javascript/build/cjs/RulesEngine', () => ({ - RulesEngine: jest.fn().mockImplementation(() => - ({ getProgramRuleEffects: (...args) => mockGetProgramRuleEffects(...args) })), + RulesEngine: jest + .fn() + .mockImplementation(() => ({ getProgramRuleEffects: (...args) => mockGetProgramRuleEffects(...args) })), })); jest.mock('../../metaDataMemoryStores/constants/constants.store', () => ({ - constantsStore: ({ get: () => [{ id: 'constantId1', value: '1' }] }), + constantsStore: { get: () => [{ id: 'constantId1', value: '1' }] }, })); jest.mock('../../metaDataMemoryStores/optionSets/optionSets.store', () => ({ - optionSetStore: ({ get: () => [mockOptionSet] }), + optionSetStore: { get: () => [mockOptionSet] }, })); describe('getApplicableRuleEffectsForEventProgram', () => { @@ -75,7 +79,7 @@ describe('getApplicableRuleEffectsForEventProgram', () => { displayName: 'Test', id: 'PUQZWgmQ0jx', programId: 'IpHINAT79UW', - programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, useNameForOptionSet: true, }, { @@ -83,7 +87,7 @@ describe('getApplicableRuleEffectsForEventProgram', () => { displayName: 'apgarcomment', id: 'aKpfPKSRQnv', programId: 'IpHINAT79UW', - programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, useNameForOptionSet: true, }, { @@ -91,23 +95,27 @@ describe('getApplicableRuleEffectsForEventProgram', () => { displayName: 'apgarscore', id: 'g2GooOydipB', programId: 'IpHINAT79UW', - programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, useNameForOptionSet: true, }, ]; - initProgram.programRules = [{ - condition: 'true', - displayName: 'TestRule', - id: 'JJDQxgHuuL2', - programId: 'IpHINAT79UW', - programRuleActions: [{ - data: '#{Test}', - id: 'CQaifjkoFEU', - location: 'feedback', - programRuleActionType: 'DISPLAYTEXT', - }], - }]; + initProgram.programRules = [ + { + condition: 'true', + displayName: 'TestRule', + id: 'JJDQxgHuuL2', + programId: 'IpHINAT79UW', + programRuleActions: [ + { + data: '#{Test}', + id: 'CQaifjkoFEU', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + ], + }, + ]; }); test('RulesEngine called with computed arguments from getApplicableRuleEffectsForEventProgram', () => { @@ -139,4 +147,16 @@ describe('getApplicableRuleEffectsForEventProgram', () => { expect(effects.DISPLAYTEXT).toBeDefined(); }); + + test('RulesEngine called without programRules', () => { + const effects = getApplicableRuleEffectsForEventProgram({ + program: new EventProgram((initProgram) => { + initProgram.programRules = []; + }), + orgUnit, + currentEvent, + }); + + expect(effects).toStrictEqual([]); + }); }); diff --git a/src/core_modules/capture-core/rules/__tests__/postProcessRulesEffects.test.js b/src/core_modules/capture-core/rules/__tests__/postProcessRulesEffects.test.js new file mode 100644 index 0000000000..6792dc777f --- /dev/null +++ b/src/core_modules/capture-core/rules/__tests__/postProcessRulesEffects.test.js @@ -0,0 +1,211 @@ +import { postProcessRulesEffects } from '../index'; +import { RenderFoundation, Section, DataElement, dataElementTypes } from '../../metaData'; + +test('Post process rules effects', () => { + // given + const rulesEffects = [ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pv6', message: 'd2:floor((5+5) / 2) = 5' }, + }, + { type: 'HIDEPROGRAMSTAGE', id: 'PUZaKR0Jh2k' }, + { id: 'SWfdBhglX0fk', type: 'HIDESECTION' }, + { id: 'w75KJ2mc4zz', type: 'ASSIGN', value: 'true' }, + { id: 'wasdJ2mc4zz', type: 'ASSIGN', value: 'true' }, + { + id: 'w75KJ2mc4zz', + message: ' true', + type: 'ERRORONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + message: ' true', + type: 'WARNINGONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + type: 'SETMANDATORYFIELD', + }, + { + content: undefined, + id: 'zDhUuAYrxNC', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + hadValue: true, + name: undefined, + }, + { + content: undefined, + id: 'unknown', + type: 'HIDEFIELD', + }, + { + displayKeyValuePair: { + id: 'khy8GmlwpgZ', + key: "d2:weeksBetween('2020-01-28', V{unknown} ) = ", + value: '', + }, + id: 'feedback', + type: 'DISPLAYKEYVALUEPAIR', + }, + ]; + + const foundation = new RenderFoundation(() => {}); + const section1 = new Section((initSection) => { + initSection.id = 's1Id'; + initSection.name = 'section1'; + const dataElement1 = new DataElement((o) => { + o.id = 'w75KJ2mc4zz'; + o.name = 'dataElement1'; + o.type = dataElementTypes.TEXT; + o.optionSet = { + id: 'optionSet', + name: 'optionSet', + code: 'optionSet', + options: [{ option: { value: false } }], + }; + }); + + const dataElement2 = new DataElement((o) => { + o.id = 'wasdJ2mc4zz'; + o.name = 'dataElement1'; + o.type = dataElementTypes.NUMBER; + o.compulsory = true; + }); + initSection.addElement(dataElement1); + initSection.addElement(dataElement2); + }); + + const section2 = new Section((initSection) => { + initSection.id = 'SWfdBhglX0fk'; + initSection.name = 'section2'; + const dataElement = new DataElement((o) => { + o.id = 'da1Id'; + o.name = 'dataElement1'; + o.type = dataElementTypes.TEXT; + }); + + initSection.addElement(dataElement); + }); + + foundation.addSection(section1); + foundation.addSection(section2); + + // when + const processedRulesEffects = postProcessRulesEffects(rulesEffects, foundation); + + // then + expect(processedRulesEffects).toEqual([ + { + displayText: { + id: 'Eeb7Ixr4Pv6', + message: 'd2:floor((5+5) / 2) = 5', + }, + id: 'feedback', + type: 'DISPLAYTEXT', + }, + { + id: 'PUZaKR0Jh2k', + type: 'HIDEPROGRAMSTAGE', + }, + { + id: 'w75KJ2mc4zz', + message: ' true', + type: 'ERRORONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + message: ' true', + type: 'WARNINGONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + type: 'SETMANDATORYFIELD', + }, + { + displayKeyValuePair: { + id: 'khy8GmlwpgZ', + key: "d2:weeksBetween('2020-01-28', V{unknown} ) = ", + value: '', + }, + id: 'feedback', + type: 'DISPLAYKEYVALUEPAIR', + }, + { + content: undefined, + id: 'unknown', + type: 'HIDEFIELD', + }, + { + id: 'da1Id', + type: 'HIDEFIELD', + }, + { + id: 'unknown', + type: 'ASSIGN', + value: null, + }, + { + id: 'w75KJ2mc4zz', + type: 'ASSIGN', + value: null, + }, + { + id: 'wasdJ2mc4zz', + type: 'ASSIGN', + value: 'true', + }, + ]); +}); + +test('the rules effects are not defined', () => { + // given + + const foundation = new RenderFoundation(() => {}); + const section1 = new Section((initSection) => { + initSection.id = 's1Id'; + initSection.name = 'section1'; + const dataElement1 = new DataElement((o) => { + o.id = 'da1Id'; + o.name = 'dataElement1'; + o.type = dataElementTypes.TEXT; + }); + + initSection.addElement(dataElement1); + }); + + foundation.addSection(section1); + + // when + const processedRulesEffects = postProcessRulesEffects(undefined, foundation); + + // then + expect(processedRulesEffects).toEqual([]); +}); + +test('HIDESECTION effect sectionId do not match the formFoundation sectionId', () => { + // given + const rulesEffects = [{ id: 'SWfdBhglX0fk', type: 'HIDESECTION' }]; + + const foundation = new RenderFoundation(() => {}); + const section1 = new Section((initSection) => { + initSection.id = 's1Id'; + initSection.name = 'section1'; + const dataElement1 = new DataElement((o) => { + o.id = 'da1Id'; + o.name = 'dataElement1'; + o.type = dataElementTypes.TEXT; + }); + + initSection.addElement(dataElement1); + }); + + foundation.addSection(section1); + + // when + const processedRulesEffects = postProcessRulesEffects(rulesEffects, foundation); + + // then + expect(processedRulesEffects).toEqual([]); +}); diff --git a/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js b/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js index 8fd6054d3c..215b2a8ea0 100644 --- a/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js +++ b/src/core_modules/capture-core/rules/__tests__/ruleEffectsForEventProgram.test.js @@ -1,19 +1,95 @@ -import { rulesEngineEffectTargetDataTypes } from '@dhis2/rules-engine-javascript'; +import { rulesEngineEffectTargetDataTypes, variableSourceTypes } from '@dhis2/rules-engine-javascript'; import { rulesEngine } from '../rulesEngine'; +import { systemSettingsStore } from '../../metaDataMemoryStores'; describe('Event Event rules engine', () => { // these variables are shared between each test - const constants = [{ id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }]; - const dataElementsInProgram = { sWoqcoByYmD: { id: 'sWoqcoByYmD', valueType: 'BOOLEAN' }, Ok9OQpitjQr: { id: 'Ok9OQpitjQr', valueType: 'BOOLEAN' }, vANAXwtLwcT: { id: 'vANAXwtLwcT', valueType: 'NUMBER' } }; - const programRules = [{ id: 'GC4gpdoSD4r', condition: '#{hemoglobin} < 9', description: 'Show warning if hemoglobin is dangerously low', displayName: 'Hemoglobin warning', programId: 'lxAQ7Zs9VYR', programRuleActions: [{ id: 'suS9GnraCx1', content: 'Hemoglobin value lower than normal', displayContent: 'Hemoglobin value lower than normal', dataElementId: 'vANAXwtLwcT', programRuleActionType: 'SHOWWARNING' }] }, { id: 'dahuKlP7jR2', condition: '#{hemoglobin} > 99', description: 'Show error for hemoglobin value higher than 99', displayName: 'Show error for high hemoglobin value', programId: 'lxAQ7Zs9VYR', programRuleActions: [{ id: 'UUwZWS8uirn', content: 'The hemoglobin value cannot be above 99', displayContent: 'The hemoglobin value cannot be above 99', dataElementId: 'vANAXwtLwcT', programRuleActionType: 'SHOWERROR' }] }, { id: 'xOe5qCzRS0Y', condition: '!#{womanSmoking} ', description: 'Hide smoking cessation councelling dataelement unless patient is smoking', displayName: 'Hide smoking cessation councelling', programId: 'lxAQ7Zs9VYR', programRuleActions: [{ id: 'hwgyO59SSxu', dataElementId: 'Ok9OQpitjQr', programRuleActionType: 'HIDEFIELD' }] }]; - const programRuleVariables = [{ id: 'Z92dJO9gIje', dataElementId: 'sWoqcoByYmD', displayName: 'womanSmoking', programId: 'lxAQ7Zs9VYR', programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', useNameForOptionSet: true }, { id: 'omrL0gtPpDL', dataElementId: 'vANAXwtLwcT', displayName: 'hemoglobin', programId: 'lxAQ7Zs9VYR', programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', useNameForOptionSet: true }]; + const constants = [ + { id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, + { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }, + ]; + const dataElementsInProgram = { + sWoqcoByYmD: { id: 'sWoqcoByYmD', valueType: 'BOOLEAN' }, + Ok9OQpitjQr: { id: 'Ok9OQpitjQr', valueType: 'BOOLEAN' }, + vANAXwtLwcT: { id: 'vANAXwtLwcT', valueType: 'NUMBER' }, + }; + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: '#{hemoglobin} < 9', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'suS9GnraCx1', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + dataElementId: 'vANAXwtLwcT', + programRuleActionType: 'SHOWWARNING', + }, + ], + }, + { + id: 'dahuKlP7jR2', + condition: '#{hemoglobin} > 99', + description: 'Show error for hemoglobin value higher than 99', + displayName: 'Show error for high hemoglobin value', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'UUwZWS8uirn', + content: 'The hemoglobin value cannot be above 99', + displayContent: 'The hemoglobin value cannot be above 99', + dataElementId: 'vANAXwtLwcT', + programRuleActionType: 'SHOWERROR', + }, + ], + }, + { + id: 'xOe5qCzRS0Y', + condition: '!#{womanSmoking} ', + description: 'Hide smoking cessation councelling dataelement unless patient is smoking', + displayName: 'Hide smoking cessation councelling', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { id: 'hwgyO59SSxu', dataElementId: 'Ok9OQpitjQr', programRuleActionType: 'HIDEFIELD' }, + ], + }, + ]; + const programRuleVariables = [ + { + id: 'Z92dJO9gIje', + dataElementId: 'sWoqcoByYmD', + displayName: 'womanSmoking', + programId: 'lxAQ7Zs9VYR', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: true, + }, + { + id: 'omrL0gtPpDL', + dataElementId: 'vANAXwtLwcT', + displayName: 'hemoglobin', + programId: 'lxAQ7Zs9VYR', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: true, + }, + ]; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; const optionSets = {}; describe.each([ [ { vANAXwtLwcT: 0 }, - [{ id: 'vANAXwtLwcT', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, message: 'Hemoglobin value lower than normal ', type: 'SHOWWARNING' }, { id: 'Ok9OQpitjQr', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }], + [ + { + id: 'vANAXwtLwcT', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + message: 'Hemoglobin value lower than normal ', + type: 'SHOWWARNING', + }, + { id: 'Ok9OQpitjQr', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }, + ], ], [ { vANAXwtLwcT: 9 }, @@ -25,7 +101,15 @@ describe('Event Event rules engine', () => { ], [ { vANAXwtLwcT: 100 }, - [{ id: 'vANAXwtLwcT', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, message: 'The hemoglobin value cannot be above 99 ', type: 'SHOWERROR' }, { id: 'Ok9OQpitjQr', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }], + [ + { + id: 'vANAXwtLwcT', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + message: 'The hemoglobin value cannot be above 99 ', + type: 'SHOWERROR', + }, + { id: 'Ok9OQpitjQr', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }, + ], ], ])('where value needs to >= 9 and <= 99', (currentEvent, expected) => { test(`and given value(s): ${JSON.stringify(currentEvent)}`, () => { @@ -44,11 +128,26 @@ describe('Event Event rules engine', () => { describe.each([ [ { sWoqcoByYmD: true }, - [{ id: 'vANAXwtLwcT', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, message: 'Hemoglobin value lower than normal ', type: 'SHOWWARNING' }], + [ + { + id: 'vANAXwtLwcT', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + message: 'Hemoglobin value lower than normal ', + type: 'SHOWWARNING', + }, + ], ], [ { sWoqcoByYmD: false }, - [{ id: 'vANAXwtLwcT', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, message: 'Hemoglobin value lower than normal ', type: 'SHOWWARNING' }, { id: 'Ok9OQpitjQr', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }], + [ + { + id: 'vANAXwtLwcT', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + message: 'Hemoglobin value lower than normal ', + type: 'SHOWWARNING', + }, + { id: 'Ok9OQpitjQr', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }, + ], ], ])('where field is hidden regarding a boolean value', (currentEvent, expected) => { test(`and given value(s): ${JSON.stringify(currentEvent)}`, () => { @@ -67,25 +166,138 @@ describe('Event Event rules engine', () => { describe('Event rules engine', () => { // these variables are shared between each test - const constants = [{ id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }]; - const dataElementsInProgram = { oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'BOOLEAN', optionSetId: undefined }, qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'INTEGER', optionSetId: undefined }, GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER', optionSetId: undefined }, vV9UWAZohSf: { id: 'vV9UWAZohSf', valueType: 'INTEGER_POSITIVE', optionSetId: undefined }, eMyVanycQSC: { id: 'eMyVanycQSC', valueType: 'DATE', optionSetId: undefined }, K6uUAvq500H: { id: 'K6uUAvq500H', valueType: 'TEXT', optionSetId: 'eUZ79clX7y1' }, msodh3rEMJa: { id: 'msodh3rEMJa', valueType: 'DATE', optionSetId: undefined }, S33cRBsnXPo: { id: 'S33cRBsnXPo', valueType: 'ORGANISATION_UNIT', optionSetId: undefined }, fWIAEtYVEGk: { id: 'fWIAEtYVEGk', valueType: 'TEXT', optionSetId: 'iDFPKpFTiVw' }, ulD2zW0TIy2: { id: 'ulD2zW0TIy2', valueType: 'FILE_RESOURCE' } }; - const programRules = [{ id: 'fd3wL1quxGb', condition: "#{gender} == 'Male'", description: 'Hide pregnant if gender is male', displayName: 'Hide pregnant if gender is male', programId: 'eBAyeGv0exc', programRuleActions: [{ id: 'IrmpncBsypT', dataElementId: 'SWfdB5lX0fk', programRuleActionType: 'HIDEFIELD', programStageSectionId: 'd7ZILSbPgYh' }] }, { id: 'x7PaHGvgWY2', condition: 'true', description: 'Body Mass Index. Weight in kg / height in m square.', displayName: 'BMI', programId: 'eBAyeGv0exc', programRuleActions: [{ id: 'x7PaHGvgWY2', content: 'BMI', displayContent: 'BMI', data: '#{Zj7UnCAulEk.vV9UWAZohSf}/((#{Zj7UnCAulEk.GieVkTxp4HH}/100)*(#{Zj7UnCAulEk.GieVkTxp4HH}/100))', programRuleActionType: 'DISPLAYKEYVALUEPAIR', location: 'indicators' }] }]; - const programRuleVariables = [{ id: 'RycV5uDi66i', dataElementId: 'qrur9Dvnyt5', displayName: 'age', programId: 'eBAyeGv0exc', programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', useNameForOptionSet: true }, { id: 'zINGRka3g9N', dataElementId: 'oZg33kd9taw', displayName: 'gender', programId: 'eBAyeGv0exc', programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', useNameForOptionSet: true }, { id: 'Zj7UnCAulEk.vV9UWAZohSf', displayName: 'Zj7UnCAulEk.vV9UWAZohSf', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', dataElementId: 'vV9UWAZohSf', programId: 'eBAyeGv0exc' }, { id: 'Zj7UnCAulEk.GieVkTxp4HH', displayName: 'Zj7UnCAulEk.GieVkTxp4HH', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', dataElementId: 'GieVkTxp4HH', programId: 'eBAyeGv0exc' }, { id: 'Zj7UnCAulEk.GieVkTxp4HH', displayName: 'Zj7UnCAulEk.GieVkTxp4HH', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', dataElementId: 'GieVkTxp4HH', programId: 'eBAyeGv0exc' }]; + const constants = [ + { id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, + { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }, + ]; + const dataElementsInProgram = { + oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, + SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'TRUE_ONLY', optionSetId: undefined }, + qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'INTEGER', optionSetId: undefined }, + GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER', optionSetId: undefined }, + vV9UWAZohSf: { id: 'vV9UWAZohSf', valueType: 'INTEGER_POSITIVE', optionSetId: undefined }, + eMyVanycQSC: { id: 'eMyVanycQSC', valueType: 'DATE', optionSetId: undefined }, + K6uUAvq500H: { id: 'K6uUAvq500H', valueType: 'TEXT', optionSetId: 'eUZ79clX7y1' }, + msodh3rEMJa: { id: 'msodh3rEMJa', valueType: 'DATE', optionSetId: undefined }, + S33cRBsnXPo: { id: 'S33cRBsnXPo', valueType: 'ORGANISATION_UNIT', optionSetId: undefined }, + fWIAEtYVEGk: { id: 'fWIAEtYVEGk', valueType: 'TEXT', optionSetId: 'iDFPKpFTiVw' }, + ulD2zW0TIy2: { id: 'ulD2zW0TIy2', valueType: 'FILE_RESOURCE' }, + }; + const programRules = [ + { + id: 'fd3wL1quxGb', + condition: "#{gender} == 'Male'", + description: 'Hide pregnant if gender is male', + displayName: 'Hide pregnant if gender is male', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'IrmpncBsypT', + dataElementId: 'SWfdB5lX0fk', + programRuleActionType: 'HIDEFIELD', + programStageSectionId: 'd7ZILSbPgYh', + }, + ], + }, + { + id: 'x7PaHGvgWY2', + condition: 'true', + description: 'Body Mass Index. Weight in kg / height in m square.', + displayName: 'BMI', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'x7PaHGvgWY2', + content: 'BMI', + displayContent: 'BMI', + data: '#{Zj7UnCAulEk.vV9UWAZohSf}/((#{Zj7UnCAulEk.GieVkTxp4HH}/100)*(#{Zj7UnCAulEk.GieVkTxp4HH}/100))', + programRuleActionType: 'DISPLAYKEYVALUEPAIR', + location: 'indicators', + }, + ], + }, + ]; + const programRuleVariables = [ + { + id: 'RycV5uDi66i', + dataElementId: 'qrur9Dvnyt5', + displayName: 'age', + programId: 'eBAyeGv0exc', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: true, + }, + { + id: 'zINGRka3g9N', + dataElementId: 'oZg33kd9taw', + displayName: 'gender', + programId: 'eBAyeGv0exc', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: true, + }, + { + id: 'Zj7UnCAulEk.vV9UWAZohSf', + displayName: 'Zj7UnCAulEk.vV9UWAZohSf', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + dataElementId: 'vV9UWAZohSf', + programId: 'eBAyeGv0exc', + }, + { + id: 'Zj7UnCAulEk.GieVkTxp4HH', + displayName: 'Zj7UnCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + dataElementId: 'GieVkTxp4HH', + programId: 'eBAyeGv0exc', + }, + { + id: 'Zj7UnCAulEk.GieVkTxp4HH', + displayName: 'Zj7UnCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + dataElementId: 'GieVkTxp4HH', + programId: 'eBAyeGv0exc', + }, + ]; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; - const optionSets = { pC3N9N77UmT: { id: 'pC3N9N77UmT', displayName: 'Gender', version: 0, valueType: 'TEXT', options: [{ id: 'rBvjJYbMCVx', displayName: 'Male', code: 'Male', translations: [] }, { id: 'Mnp3oXrpAbK', displayName: 'Female', code: 'Female', translations: [] }] } }; + const optionSets = { + pC3N9N77UmT: { + id: 'pC3N9N77UmT', + displayName: 'Gender', + version: 0, + valueType: 'TEXT', + options: undefined, + }, + }; describe.each([ [ { oZg33kd9taw: 'Female', SWfdB5lX0fk: null }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { oZg33kd9taw: 'Male', SWfdB5lX0fk: null }, - [{ id: 'SWfdB5lX0fk', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }, { displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { id: 'SWfdB5lX0fk', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, type: 'HIDEFIELD' }, + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { oZg33kd9taw: null, SWfdB5lX0fk: null }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], ])('where field is hidden regarding the gender of the event', (currentEvent, expected) => { test(`and given value(s): ${JSON.stringify(currentEvent)}`, () => { @@ -104,35 +316,83 @@ describe('Event rules engine', () => { describe.each([ [ { qrur9Dvnyt5: null, GieVkTxp4HH: null, vV9UWAZohSf: null }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { qrur9Dvnyt5: null, GieVkTxp4HH: null, vV9UWAZohSf: 85 }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { qrur9Dvnyt5: null, GieVkTxp4HH: 180, vV9UWAZohSf: null }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '0' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '0' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { qrur9Dvnyt5: null, GieVkTxp4HH: 180, vV9UWAZohSf: 85 }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '26.234567901234566' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '26.234567901234566' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { qrur9Dvnyt5: 40, GieVkTxp4HH: null, vV9UWAZohSf: null }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { qrur9Dvnyt5: 40, GieVkTxp4HH: null, vV9UWAZohSf: 85 }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { qrur9Dvnyt5: 40, GieVkTxp4HH: 180, vV9UWAZohSf: null }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '0' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '0' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], [ { qrur9Dvnyt5: 40, GieVkTxp4HH: 180, vV9UWAZohSf: 85 }, - [{ displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '26.234567901234566' }, id: 'indicators', type: 'DISPLAYKEYVALUEPAIR' }], + [ + { + displayKeyValuePair: { id: 'x7PaHGvgWY2', key: 'BMI', value: '26.234567901234566' }, + id: 'indicators', + type: 'DISPLAYKEYVALUEPAIR', + }, + ], ], ])('where BMI is calculated', (currentEvent, expected) => { test(`and given value(s): ${JSON.stringify(currentEvent)}`, () => { @@ -151,14 +411,503 @@ describe('Event rules engine', () => { describe('Event rules engine', () => { // these variables are shared between each test - const constants = [{ id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }]; - const dataElementsInProgram = { dyfYIsTFTjG: { id: 'dyfYIsTFTjG', valueType: 'TEXT' }, XOaXLWuhKzV: { id: 'XOaXLWuhKzV', valueType: 'TEXT', optionSetId: 'WDUwjiW2rGH' }, AtFlciXnstG: { id: 'AtFlciXnstG', valueType: 'INTEGER_ZERO_OR_POSITIVE' }, JGnHr6WI3AY: { id: 'JGnHr6WI3AY', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, s3eoonJ8OJb: { id: 's3eoonJ8OJb', valueType: 'DATE' }, gktroFPckdr: { id: 'gktroFPckdr', valueType: 'TEXT', optionSetId: 'UYDsNdpo2BU' }, QQLXTXVidW2: { id: 'QQLXTXVidW2', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, ovY6E8BSdto: { id: 'ovY6E8BSdto', valueType: 'TEXT', optionSetId: 'dsgBmIZ0Yrq' }, Z5z8vFQy0w0: { id: 'Z5z8vFQy0w0', valueType: 'TEXT' }, TzqawmlPkI5: { id: 'TzqawmlPkI5', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'TEXT', optionSetId: 'xD9QOIvNAjw' }, jBBkFuPKctq: { id: 'jBBkFuPKctq', valueType: 'TEXT', optionSetId: 'T9zjyaIkRqH' }, A4Fg6jgWauf: { id: 'A4Fg6jgWauf', valueType: 'TEXT', optionSetId: 'w1vUkxq8IOl' }, CUbZcLm9LyN: { id: 'CUbZcLm9LyN', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, p8htbyJHydl: { id: 'p8htbyJHydl', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, SbXES4EPgqP: { id: 'SbXES4EPgqP', valueType: 'NUMBER' }, bOYWVEBaWy6: { id: 'bOYWVEBaWy6', valueType: 'TEXT', optionSetId: 'qI4cs9ocBwn' }, PFXeJV8d7ja: { id: 'PFXeJV8d7ja', valueType: 'DATE' } }; - const programRules = [{ id: 'DOz4wl8ErDD', condition: 'true', description: 'Hide Irrelevant Test Result Options', displayName: 'Hide Test Result Options', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'XuM1JizlcF1', dataElementId: 'ovY6E8BSdto', programRuleActionType: 'HIDEOPTION', optionId: 'MkeWrqeqZXL' }, { id: 'FRfTFXSwKDU', dataElementId: 'ovY6E8BSdto', programRuleActionType: 'HIDEOPTION', optionId: 'fPV0gQ8ds6D' }] }, { id: 'DtfaG1TgyZk', condition: "(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'No') ||\n(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && #{TEST_RESULT} == 'Inconclusive')", description: "Automation: Assign 'Probable Case' to Case Classification", displayName: "Assign 'Probable Case' to Case Classification", programId: 'PNClHaZARtz', programRuleActions: [{ id: 'NPvy6sF6axT', data: "'Probable Case'", dataElementId: 'Z5z8vFQy0w0', programRuleActionType: 'ASSIGN' }], priority: 4 }, { id: 'E9ghdhg6ABQ', condition: "#{SYMPTOMS} != 'Yes'", description: 'Hide Onset of Symptoms Date if no symptoms', displayName: 'Hide Onset of Symptoms Date', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'VcnnsBPtzlW', dataElementId: 's3eoonJ8OJb', programRuleActionType: 'HIDEFIELD' }] }, { id: 'FnSVDp8v0H9', condition: 'true', description: 'Hide Irrelevant Unknown Options', displayName: 'Hide Unknown Options', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'UqDEcMuF5DF', dataElementId: 'JGnHr6WI3AY', programRuleActionType: 'HIDEOPTION', optionId: 'pqxvAQU1z9W' }, { id: 'GrFjkYTT07o', dataElementId: 'p8htbyJHydl', programRuleActionType: 'HIDEOPTION', optionId: 'pqxvAQU1z9W' }, { id: 'HlyTQaTz00f', dataElementId: 'CUbZcLm9LyN', programRuleActionType: 'HIDEOPTION', optionId: 'pqxvAQU1z9W' }] }, { id: 'L8bP6GifQXL', condition: "!d2:hasValue( #{INFECTION_SOURCE} ) || #{INFECTION_SOURCE} == 'IMPORTED_CASE' || #{INFECTION_SOURCE} == 'EXPOSURE_UNKNOWN'", description: 'Hide Case Type for Imported Cases', displayName: 'Hide Case Type', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'k05Owr8pwIn', dataElementId: 'A4Fg6jgWauf', programRuleActionType: 'HIDEFIELD' }] }, { id: 'MLS5vZLguQM', condition: "#{INFECTION_SOURCE} != 'LOCAL_TRANSMISSION'", description: "Hide 'Specify Local Infection Source' unless Local Transmission is selected", displayName: "Hide 'Specify Local Infection Source'", programId: 'PNClHaZARtz', programRuleActions: [{ id: 'ho7xRPUB0Gl', dataElementId: 'jBBkFuPKctq', programRuleActionType: 'HIDEFIELD' }] }, { id: 'NXWk8sq70OV', condition: "#{TRAVEL_HISTORY} == 'No'", description: "Hide 'Imported Case' if not traveled", displayName: "Hide 'Imported Case'", programId: 'PNClHaZARtz', programRuleActions: [{ id: 'fJIgmDK53Vp', dataElementId: 'f8j4XDEozvj', programRuleActionType: 'HIDEOPTION', optionId: 'PMGTqmVIF4T' }] }, { id: 'NZaVqr7dPfQ', condition: '!d2:hasValue( #{ONSET_DATE} ) && !d2:hasValue(V{event_date})', description: 'Automation: Assign Empty date if no Onset date and no event date is available', displayName: 'Assign Empty Date', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'q060xuOwQx3', data: "''", dataElementId: 'PFXeJV8d7ja', programRuleActionType: 'ASSIGN' }], priority: 3 }, { id: 'QrJx9LI9KRo', condition: "d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && !d2:hasValue( #{TEST_RESULT} )", description: 'Automation: Assign Empty Value to Class Classification', displayName: 'Assign Empty Value to Class Classification', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'rFaZAbOgMSz', data: "''", dataElementId: 'Z5z8vFQy0w0', programRuleActionType: 'ASSIGN' }], priority: 2 }, { id: 'R6oEX1xlQma', condition: 'true', description: 'Hide irrelevant Outcome Options', displayName: 'Hide Outcome Options', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'Ov7qHXf0Q2s', dataElementId: 'bOYWVEBaWy6', programRuleActionType: 'HIDEOPTION', optionId: 'dUeRcF2cApV' }, { id: 'V95rgvUlqY0', dataElementId: 'bOYWVEBaWy6', programRuleActionType: 'HIDEOPTION', optionId: 'bYt4why1tL3' }, { id: 'eZUUOjykbLv', dataElementId: 'bOYWVEBaWy6', programRuleActionType: 'HIDEOPTION', optionId: 'xBoo6HyaYcd' }, { id: 'Zcs7rz5VEF7', dataElementId: 'bOYWVEBaWy6', programRuleActionType: 'HIDEOPTION', optionId: 'RCT079wdeKT' }] }, { id: 'dZsTiQEUg5L', condition: '!d2:hasValue( #{ONSET_DATE} ) && d2:hasValue(V{event_date})', description: 'Automation: Assign Event date if no Onset date is available', displayName: 'Assign Event Date', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'sQKFBORp5P1', data: 'V{event_date}', dataElementId: 'PFXeJV8d7ja', programRuleActionType: 'ASSIGN' }], priority: 2 }, { id: 'kVBrxwODyTj', condition: '!d2:hasValue( #{LAB_TEST} )', description: 'Hide Case Classification Field until Lab Test question is answered', displayName: 'Hide Case Classification Field', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'ZT8AexxBPl0', dataElementId: 'Z5z8vFQy0w0', programRuleActionType: 'HIDEFIELD' }] }, { id: 'q2QbEfeDlI9', condition: "!d2:hasValue( #{HOSPITALISED} ) || #{HOSPITALISED} == 'No'", description: 'Hide ICU field unless Hospitalised', displayName: 'Hide ICU field', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'wwvxLCpuOCx', dataElementId: 'p8htbyJHydl', programRuleActionType: 'HIDEFIELD' }] }, { id: 'rZUpiMuJIKH', condition: "d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && #{TEST_RESULT} == 'Positive'", description: "Automation: Assign 'Laboratory Confirmed Case' to Case Classification", displayName: "Assign 'Laboratory Confirmed Case' to Case Classification", programId: 'PNClHaZARtz', programRuleActions: [{ id: 'hcamYSDn00P', data: "'Laboratory Confirmed Case'", dataElementId: 'Z5z8vFQy0w0', programRuleActionType: 'ASSIGN' }], priority: 3 }, { id: 'sEQsGGAQSJT', condition: "(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Unknown') ||\n(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && (#{TEST_RESULT} == 'Negative' || #{TEST_RESULT} == 'Unknown'))", description: "Automation: Assign Suspected Case' to Case Classification", displayName: "Assign 'Suspected Case' to Case Classification", programId: 'PNClHaZARtz', programRuleActions: [{ id: 'zxb2XDboGAF', data: "'Suspected Case'", dataElementId: 'Z5z8vFQy0w0', programRuleActionType: 'ASSIGN' }], priority: 1 }, { id: 'sKCZMuWwOKA', condition: 'd2:hasValue( #{ONSET_DATE} )', description: 'Automation: Assign Symptoms Onset Date if available', displayName: 'Assign Onset Date', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'lJOYxhjupxz', data: '#{ONSET_DATE}', dataElementId: 'PFXeJV8d7ja', programRuleActionType: 'ASSIGN' }], priority: 1 }, { id: 'vj5GWKIrhKh', condition: "#{LAB_TEST} != 'Yes'", description: 'Hide Test Result Field until Lab Test question is answered with yes', displayName: 'Hide Test Result Field', programId: 'PNClHaZARtz', programRuleActions: [{ id: 'VxxxIX2598r', dataElementId: 'ovY6E8BSdto', programRuleActionType: 'HIDEFIELD' }] }]; - const programRuleVariables = [{ id: 'DoRHHfNPccb', dataElementId: 'f8j4XDEozvj', displayName: 'INFECTION_SOURCE', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: false }, { id: 'EnpvdmYrwLb', dataElementId: 'TzqawmlPkI5', displayName: 'TRAVEL_HISTORY', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: true }, { id: 'JPIyrAmJapV', dataElementId: 'CUbZcLm9LyN', displayName: 'HOSPITALISED', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: true }, { id: 'LAaPMTz69L7', displayName: 'CASE_CLASSIFICATION', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'CALCULATED_VALUE', useNameForOptionSet: true, valueType: 'TEXT' }, { id: 'MpixycZvu0m', dataElementId: 'ovY6E8BSdto', displayName: 'TEST_RESULT', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: true }, { id: 'XcPYCpTOPwB', dataElementId: 'QQLXTXVidW2', displayName: 'LAB_TEST', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: true }, { id: 'cZSslcAEupI', dataElementId: 's3eoonJ8OJb', displayName: 'ONSET_DATE', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: true }, { id: 'eSq3nc1t2F6', dataElementId: 'dyfYIsTFTjG', displayName: 'PATIENT_ID', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: true }, { id: 'lY0yJGU1D4e', dataElementId: 'JGnHr6WI3AY', displayName: 'SYMPTOMS', programId: 'PNClHaZARtz', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', useNameForOptionSet: false }]; + const constants = [ + { id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, + { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }, + ]; + const dataElementsInProgram = { + dyfYIsTFTjG: { id: 'dyfYIsTFTjG', valueType: 'TEXT' }, + XOaXLWuhKzV: { id: 'XOaXLWuhKzV', valueType: 'TEXT', optionSetId: 'WDUwjiW2rGH' }, + AtFlciXnstG: { id: 'AtFlciXnstG', valueType: 'INTEGER_ZERO_OR_POSITIVE' }, + JGnHr6WI3AY: { id: 'JGnHr6WI3AY', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, + s3eoonJ8OJb: { id: 's3eoonJ8OJb', valueType: 'DATE' }, + gktroFPckdr: { id: 'gktroFPckdr', valueType: 'TEXT', optionSetId: 'UYDsNdpo2BU' }, + QQLXTXVidW2: { id: 'QQLXTXVidW2', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, + ovY6E8BSdto: { id: 'ovY6E8BSdto', valueType: 'TEXT', optionSetId: 'dsgBmIZ0Yrq' }, + Z5z8vFQy0w0: { id: 'Z5z8vFQy0w0', valueType: 'TEXT' }, + TzqawmlPkI5: { id: 'TzqawmlPkI5', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'TEXT', optionSetId: 'xD9QOIvNAjw' }, + jBBkFuPKctq: { id: 'jBBkFuPKctq', valueType: 'TEXT', optionSetId: 'T9zjyaIkRqH' }, + A4Fg6jgWauf: { id: 'A4Fg6jgWauf', valueType: 'TEXT', optionSetId: 'w1vUkxq8IOl' }, + CUbZcLm9LyN: { id: 'CUbZcLm9LyN', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, + p8htbyJHydl: { id: 'p8htbyJHydl', valueType: 'TEXT', optionSetId: 'L6eMZDJkCwX' }, + SbXES4EPgqP: { id: 'SbXES4EPgqP', valueType: 'NUMBER' }, + bOYWVEBaWy6: { id: 'bOYWVEBaWy6', valueType: 'TEXT', optionSetId: 'qI4cs9ocBwn' }, + PFXeJV8d7ja: { id: 'PFXeJV8d7ja', valueType: 'DATE' }, + }; + const programRules = [ + { + id: 'DOz4wl8ErDD', + condition: 'true', + description: 'Hide Irrelevant Test Result Options', + displayName: 'Hide Test Result Options', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'XuM1JizlcF1', + dataElementId: 'ovY6E8BSdto', + programRuleActionType: 'HIDEOPTION', + optionId: 'MkeWrqeqZXL', + }, + { + id: 'FRfTFXSwKDU', + dataElementId: 'ovY6E8BSdto', + programRuleActionType: 'HIDEOPTION', + optionId: 'fPV0gQ8ds6D', + }, + ], + }, + { + id: 'DtfaG1TgyZk', + condition: + "(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'No') ||\n(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && #{TEST_RESULT} == 'Inconclusive')", + description: "Automation: Assign 'Probable Case' to Case Classification", + displayName: "Assign 'Probable Case' to Case Classification", + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'NPvy6sF6axT', + data: "'Probable Case'", + dataElementId: 'Z5z8vFQy0w0', + programRuleActionType: 'ASSIGN', + }, + ], + priority: 4, + }, + { + id: 'E9ghdhg6ABQ', + condition: "#{SYMPTOMS} != 'Yes'", + description: 'Hide Onset of Symptoms Date if no symptoms', + displayName: 'Hide Onset of Symptoms Date', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'VcnnsBPtzlW', dataElementId: 's3eoonJ8OJb', programRuleActionType: 'HIDEFIELD' }, + ], + }, + { + id: 'FnSVDp8v0H9', + condition: 'true', + description: 'Hide Irrelevant Unknown Options', + displayName: 'Hide Unknown Options', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'UqDEcMuF5DF', + dataElementId: 'JGnHr6WI3AY', + programRuleActionType: 'HIDEOPTION', + optionId: 'pqxvAQU1z9W', + }, + { + id: 'GrFjkYTT07o', + dataElementId: 'p8htbyJHydl', + programRuleActionType: 'HIDEOPTION', + optionId: 'pqxvAQU1z9W', + }, + { + id: 'HlyTQaTz00f', + dataElementId: 'CUbZcLm9LyN', + programRuleActionType: 'HIDEOPTION', + optionId: 'pqxvAQU1z9W', + }, + ], + }, + { + id: 'L8bP6GifQXL', + condition: + "!d2:hasValue( #{INFECTION_SOURCE} ) || #{INFECTION_SOURCE} == 'IMPORTED_CASE' || #{INFECTION_SOURCE} == 'EXPOSURE_UNKNOWN'", + description: 'Hide Case Type for Imported Cases', + displayName: 'Hide Case Type', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'k05Owr8pwIn', dataElementId: 'A4Fg6jgWauf', programRuleActionType: 'HIDEFIELD' }, + ], + }, + { + id: 'MLS5vZLguQM', + condition: "#{INFECTION_SOURCE} != 'LOCAL_TRANSMISSION'", + description: "Hide 'Specify Local Infection Source' unless Local Transmission is selected", + displayName: "Hide 'Specify Local Infection Source'", + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'ho7xRPUB0Gl', dataElementId: 'jBBkFuPKctq', programRuleActionType: 'HIDEFIELD' }, + ], + }, + { + id: 'NXWk8sq70OV', + condition: "#{TRAVEL_HISTORY} == 'No'", + description: "Hide 'Imported Case' if not traveled", + displayName: "Hide 'Imported Case'", + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'fJIgmDK53Vp', + dataElementId: 'f8j4XDEozvj', + programRuleActionType: 'HIDEOPTION', + optionId: 'PMGTqmVIF4T', + }, + ], + }, + { + id: 'NZaVqr7dPfQ', + condition: '!d2:hasValue( #{ONSET_DATE} ) && !d2:hasValue(V{event_date})', + description: 'Automation: Assign Empty date if no Onset date and no event date is available', + displayName: 'Assign Empty Date', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'q060xuOwQx3', data: "''", dataElementId: 'PFXeJV8d7ja', programRuleActionType: 'ASSIGN' }, + ], + priority: 3, + }, + { + id: 'QrJx9LI9KRo', + condition: "d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && !d2:hasValue( #{TEST_RESULT} )", + description: 'Automation: Assign Empty Value to Class Classification', + displayName: 'Assign Empty Value to Class Classification', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'rFaZAbOgMSz', data: "''", dataElementId: 'Z5z8vFQy0w0', programRuleActionType: 'ASSIGN' }, + ], + priority: 2, + }, + { + id: 'R6oEX1xlQma', + condition: 'true', + description: 'Hide irrelevant Outcome Options', + displayName: 'Hide Outcome Options', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'Ov7qHXf0Q2s', + dataElementId: 'bOYWVEBaWy6', + programRuleActionType: 'HIDEOPTION', + optionId: 'dUeRcF2cApV', + }, + { + id: 'V95rgvUlqY0', + dataElementId: 'bOYWVEBaWy6', + programRuleActionType: 'HIDEOPTION', + optionId: 'bYt4why1tL3', + }, + { + id: 'eZUUOjykbLv', + dataElementId: 'bOYWVEBaWy6', + programRuleActionType: 'HIDEOPTION', + optionId: 'xBoo6HyaYcd', + }, + { + id: 'Zcs7rz5VEF7', + dataElementId: 'bOYWVEBaWy6', + programRuleActionType: 'HIDEOPTION', + optionId: 'RCT079wdeKT', + }, + ], + }, + { + id: 'dZsTiQEUg5L', + condition: '!d2:hasValue( #{ONSET_DATE} ) && d2:hasValue(V{event_date})', + description: 'Automation: Assign Event date if no Onset date is available', + displayName: 'Assign Event Date', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'sQKFBORp5P1', + data: 'V{event_date}', + dataElementId: 'PFXeJV8d7ja', + programRuleActionType: 'ASSIGN', + }, + ], + priority: 2, + }, + { + id: 'kVBrxwODyTj', + condition: '!d2:hasValue( #{LAB_TEST} )', + description: 'Hide Case Classification Field until Lab Test question is answered', + displayName: 'Hide Case Classification Field', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'ZT8AexxBPl0', dataElementId: 'Z5z8vFQy0w0', programRuleActionType: 'HIDEFIELD' }, + ], + }, + { + id: 'q2QbEfeDlI9', + condition: "!d2:hasValue( #{HOSPITALISED} ) || #{HOSPITALISED} == 'No'", + description: 'Hide ICU field unless Hospitalised', + displayName: 'Hide ICU field', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'wwvxLCpuOCx', dataElementId: 'p8htbyJHydl', programRuleActionType: 'HIDEFIELD' }, + ], + }, + { + id: 'rZUpiMuJIKH', + condition: "d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && #{TEST_RESULT} == 'Positive'", + description: "Automation: Assign 'Laboratory Confirmed Case' to Case Classification", + displayName: "Assign 'Laboratory Confirmed Case' to Case Classification", + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'hcamYSDn00P', + data: "'Laboratory Confirmed Case'", + dataElementId: 'Z5z8vFQy0w0', + programRuleActionType: 'ASSIGN', + }, + ], + priority: 3, + }, + { + id: 'sEQsGGAQSJT', + condition: + "(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Unknown') ||\n(d2:hasValue( #{LAB_TEST} ) && #{LAB_TEST} == 'Yes' && (#{TEST_RESULT} == 'Negative' || #{TEST_RESULT} == 'Unknown'))", + description: "Automation: Assign Suspected Case' to Case Classification", + displayName: "Assign 'Suspected Case' to Case Classification", + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'zxb2XDboGAF', + data: "'Suspected Case'", + dataElementId: 'Z5z8vFQy0w0', + programRuleActionType: 'ASSIGN', + }, + ], + priority: 1, + }, + { + id: 'sKCZMuWwOKA', + condition: 'd2:hasValue( #{ONSET_DATE} )', + description: 'Automation: Assign Symptoms Onset Date if available', + displayName: 'Assign Onset Date', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'lJOYxhjupxz', + data: '#{ONSET_DATE}', + dataElementId: 'PFXeJV8d7ja', + programRuleActionType: 'ASSIGN', + }, + ], + priority: 1, + }, + { + id: 'vj5GWKIrhKh', + condition: "#{LAB_TEST} != 'Yes'", + description: 'Hide Test Result Field until Lab Test question is answered with yes', + displayName: 'Hide Test Result Field', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'VxxxIX2598r', dataElementId: 'ovY6E8BSdto', programRuleActionType: 'HIDEFIELD' }, + ], + }, + ]; + const programRuleVariables = [ + { + id: 'DoRHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: false, + }, + { + id: 'EnpvdmYrwLb', + dataElementId: 'TzqawmlPkI5', + displayName: 'TRAVEL_HISTORY', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: true, + }, + { + id: 'JPIyrAmJapV', + dataElementId: 'CUbZcLm9LyN', + displayName: 'HOSPITALISED', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: true, + }, + { + id: 'LAaPMTz69L7', + displayName: 'CASE_CLASSIFICATION', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.CALCULATED_VALUE, + useNameForOptionSet: true, + valueType: 'TEXT', + }, + { + id: 'MpixycZvu0m', + dataElementId: 'ovY6E8BSdto', + displayName: 'TEST_RESULT', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: true, + }, + { + id: 'XcPYCpTOPwB', + dataElementId: 'QQLXTXVidW2', + displayName: 'LAB_TEST', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: true, + }, + { + id: 'cZSslcAEupI', + dataElementId: 's3eoonJ8OJb', + displayName: 'ONSET_DATE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: true, + }, + { + id: 'eSq3nc1t2F6', + dataElementId: 'dyfYIsTFTjG', + displayName: 'PATIENT_ID', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: true, + }, + { + id: 'lY0yJGU1D4e', + dataElementId: 'JGnHr6WI3AY', + displayName: 'SYMPTOMS', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: false, + }, + ]; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; const optionSets = { - L6eMZDJkCwX: { id: 'L6eMZDJkCwX', displayName: 'Yes/No/Unknown', version: 3, valueType: 'TEXT', options: [{ id: 'x9yVKkv9koc', displayName: 'Yes', code: 'Yes', translations: [{ property: 'NAME', locale: 'uz@Latn', value: 'Ha' }, { property: 'NAME', locale: 'fr', value: 'Oui' }, { property: 'NAME', locale: 'pt', value: 'Sim' }, { property: 'NAME', locale: 'uz@Cyrl', value: 'Ҳа' }, { property: 'NAME', locale: 'es', value: 'Sí' }, { property: 'NAME', locale: 'nb', value: 'Ja' }, { property: 'NAME', locale: 'ru', value: 'да' }] }, { id: 'R98tI2c6rF5', displayName: 'No', code: 'No', translations: [{ property: 'NAME', locale: 'nb', value: 'Nei' }, { property: 'NAME', locale: 'es', value: 'No' }, { property: 'NAME', locale: 'uz@Latn', value: 'Yo`q' }, { property: 'NAME', locale: 'ru', value: 'нет' }, { property: 'NAME', locale: 'pt', value: 'Não' }, { property: 'NAME', locale: 'fr', value: 'Non' }, { property: 'NAME', locale: 'uz@Cyrl', value: 'Йўқ' }] }, { id: 'pqxvAQU1z9W', displayName: 'Unknown', code: 'Unknown', translations: [{ property: 'NAME', locale: 'es', value: 'DEsconocido' }, { property: 'NAME', locale: 'nb', value: 'Ukjent' }, { property: 'NAME', locale: 'uz@Cyrl', value: 'Ноаниқ' }, { property: 'NAME', locale: 'fr', value: 'Inconnu' }, { property: 'NAME', locale: 'uz@Latn', value: 'Noaniq' }, { property: 'NAME', locale: 'pt', value: 'Desconhecido' }, { property: 'NAME', locale: 'ru', value: 'Неизвестно' }] }] }, - dsgBmIZ0Yrq: { id: 'dsgBmIZ0Yrq', displayName: 'Test Result', version: 6, valueType: 'TEXT', options: [{ id: 'B44lkxTWbGO', displayName: 'Inconclusive', code: 'Inconclusive', translations: [{ property: 'NAME', locale: 'fr', value: 'Non concluant' }, { property: 'NAME', locale: 'pt', value: 'Inconclusivo' }, { property: 'NAME', locale: 'nb', value: 'Mangelfull' }, { property: 'NAME', locale: 'es', value: 'No concluyente' }, { property: 'NAME', locale: 'ru', value: 'Неокончательный' }] }, { id: 'ljClr1z2aE7', displayName: 'Negative', code: 'Negative', translations: [{ property: 'NAME', locale: 'fr', value: 'Négatif' }, { property: 'NAME', locale: 'nb', value: 'Negativ' }, { property: 'NAME', locale: 'es', value: 'Negativo' }, { property: 'NAME', locale: 'uz@Latn', value: 'Manfiy' }, { property: 'NAME', locale: 'pt', value: 'Negativo' }, { property: 'NAME', locale: 'ru', value: 'Отрицательный' }, { property: 'NAME', locale: 'uz@Cyrl', value: 'Манфий' }] }, { id: 'LKbwTJwocOk', displayName: 'Positive', code: 'Positive', translations: [{ property: 'NAME', locale: 'ru', value: 'Положительный' }, { property: 'NAME', locale: 'es', value: 'Positivo' }, { property: 'NAME', locale: 'uz@Latn', value: 'Musbat' }, { property: 'NAME', locale: 'uz@Cyrl', value: 'Мусбат' }, { property: 'NAME', locale: 'nb', value: 'Positiv' }, { property: 'NAME', locale: 'fr', value: 'Positif' }, { property: 'NAME', locale: 'pt', value: 'Positivo' }] }, { id: 'MkeWrqeqZXL', displayName: 'Not performed', code: 'Not performed', translations: [{ property: 'NAME', locale: 'ru', value: 'Не выполнен' }, { property: 'NAME', locale: 'pt', value: 'Não realizado' }, { property: 'NAME', locale: 'nb', value: 'Ikke utført' }, { property: 'NAME', locale: 'fr', value: 'Non réalisé' }, { property: 'NAME', locale: 'es', value: 'No realizado' }] }, { id: 'fPV0gQ8ds6D', displayName: 'Invalid', code: 'Invalid', translations: [{ property: 'NAME', locale: 'es', value: 'Inválido' }, { property: 'NAME', locale: 'fr', value: 'Invalide' }, { property: 'NAME', locale: 'pt', value: 'Inválido' }, { property: 'NAME', locale: 'nb', value: 'Ugyldig' }, { property: 'NAME', locale: 'ru', value: 'Недействительный' }] }, { id: 'YV3jCZlvwZe', displayName: 'Unknown', code: 'Unknown', translations: [{ property: 'NAME', locale: 'es', value: 'DEsconocido' }, { property: 'NAME', locale: 'nb', value: 'Ukjent' }, { property: 'NAME', locale: 'uz@Cyrl', value: 'Ноаниқ' }, { property: 'NAME', locale: 'fr', value: 'Inconnu' }, { property: 'NAME', locale: 'uz@Latn', value: 'Noaniq' }, { property: 'NAME', locale: 'pt', value: 'Desconhecido' }, { property: 'NAME', locale: 'ru', value: 'Неизвестно' }] }] }, + L6eMZDJkCwX: { + id: 'L6eMZDJkCwX', + displayName: 'Yes/No/Unknown', + version: 3, + valueType: 'TEXT', + options: [ + { + id: 'x9yVKkv9koc', + displayName: 'Yes', + code: 'Yes', + translations: [ + { property: 'NAME', locale: 'uz@Latn', value: 'Ha' }, + { property: 'NAME', locale: 'fr', value: 'Oui' }, + { property: 'NAME', locale: 'pt', value: 'Sim' }, + { property: 'NAME', locale: 'uz@Cyrl', value: 'Ҳа' }, + { property: 'NAME', locale: 'es', value: 'Sí' }, + { property: 'NAME', locale: 'nb', value: 'Ja' }, + { property: 'NAME', locale: 'ru', value: 'да' }, + ], + }, + { + id: 'R98tI2c6rF5', + displayName: 'No', + code: 'No', + translations: [ + { property: 'NAME', locale: 'nb', value: 'Nei' }, + { property: 'NAME', locale: 'es', value: 'No' }, + { property: 'NAME', locale: 'uz@Latn', value: 'Yo`q' }, + { property: 'NAME', locale: 'ru', value: 'нет' }, + { property: 'NAME', locale: 'pt', value: 'Não' }, + { property: 'NAME', locale: 'fr', value: 'Non' }, + { property: 'NAME', locale: 'uz@Cyrl', value: 'Йўқ' }, + ], + }, + { + id: 'pqxvAQU1z9W', + code: 'Unknown', + translations: [ + { property: 'NAME', locale: 'es', value: 'DEsconocido' }, + { property: 'NAME', locale: 'nb', value: 'Ukjent' }, + { property: 'NAME', locale: 'uz@Cyrl', value: 'Ноаниқ' }, + { property: 'NAME', locale: 'fr', value: 'Inconnu' }, + { property: 'NAME', locale: 'uz@Latn', value: 'Noaniq' }, + { property: 'NAME', locale: 'pt', value: 'Desconhecido' }, + { property: 'NAME', locale: 'ru', value: 'Неизвестно' }, + ], + }, + ], + }, + dsgBmIZ0Yrq: { + id: 'dsgBmIZ0Yrq', + displayName: 'Test Result', + version: 6, + valueType: 'TEXT', + options: [ + { + id: 'B44lkxTWbGO', + code: 'Inconclusive', + translations: [ + { property: 'NAME', locale: 'fr', value: 'Non concluant' }, + { property: 'NAME', locale: 'pt', value: 'Inconclusivo' }, + { property: 'NAME', locale: 'nb', value: 'Mangelfull' }, + { property: 'NAME', locale: 'es', value: 'No concluyente' }, + { property: 'NAME', locale: 'ru', value: 'Неокончательный' }, + ], + }, + { + id: 'ljClr1z2aE7', + displayName: 'Negative', + code: 'Negative', + translations: [ + { property: 'NAME', locale: 'fr', value: 'Négatif' }, + { property: 'NAME', locale: 'nb', value: 'Negativ' }, + { property: 'NAME', locale: 'es', value: 'Negativo' }, + { property: 'NAME', locale: 'uz@Latn', value: 'Manfiy' }, + { property: 'NAME', locale: 'pt', value: 'Negativo' }, + { property: 'NAME', locale: 'ru', value: 'Отрицательный' }, + { property: 'NAME', locale: 'uz@Cyrl', value: 'Манфий' }, + ], + }, + { + id: 'LKbwTJwocOk', + displayName: 'Positive', + code: 'Positive', + translations: [ + { property: 'NAME', locale: 'ru', value: 'Положительный' }, + { property: 'NAME', locale: 'es', value: 'Positivo' }, + { property: 'NAME', locale: 'uz@Latn', value: 'Musbat' }, + { property: 'NAME', locale: 'uz@Cyrl', value: 'Мусбат' }, + { property: 'NAME', locale: 'nb', value: 'Positiv' }, + { property: 'NAME', locale: 'fr', value: 'Positif' }, + { property: 'NAME', locale: 'pt', value: 'Positivo' }, + ], + }, + { + id: 'MkeWrqeqZXL', + displayName: 'Not performed', + code: 'Not performed', + translations: [ + { property: 'NAME', locale: 'ru', value: 'Не выполнен' }, + { property: 'NAME', locale: 'pt', value: 'Não realizado' }, + { property: 'NAME', locale: 'nb', value: 'Ikke utført' }, + { property: 'NAME', locale: 'fr', value: 'Non réalisé' }, + { property: 'NAME', locale: 'es', value: 'No realizado' }, + ], + }, + { + id: 'fPV0gQ8ds6D', + displayName: 'Invalid', + code: 'Invalid', + translations: [ + { property: 'NAME', locale: 'es', value: 'Inválido' }, + { property: 'NAME', locale: 'fr', value: 'Invalide' }, + { property: 'NAME', locale: 'pt', value: 'Inválido' }, + { property: 'NAME', locale: 'nb', value: 'Ugyldig' }, + { property: 'NAME', locale: 'ru', value: 'Недействительный' }, + ], + }, + { + id: 'YV3jCZlvwZe', + displayName: 'Unknown', + code: 'Unknown', + translations: [ + { property: 'NAME', locale: 'es', value: 'DEsconocido' }, + { property: 'NAME', locale: 'nb', value: 'Ukjent' }, + { property: 'NAME', locale: 'uz@Cyrl', value: 'Ноаниқ' }, + { property: 'NAME', locale: 'fr', value: 'Inconnu' }, + { property: 'NAME', locale: 'uz@Latn', value: 'Noaniq' }, + { property: 'NAME', locale: 'pt', value: 'Desconhecido' }, + { property: 'NAME', locale: 'ru', value: 'Неизвестно' }, + ], + }, + ], + }, }; // NOTE: in this test we dont use toMatchSnapshot instead we test again hardcoded values. Since the effects are plenty @@ -167,18 +916,68 @@ describe('Event rules engine', () => { [ { JGnHr6WI3AY: 'Yes' }, [ - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'Z5z8vFQy0w0', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, @@ -187,40 +986,150 @@ describe('Event rules engine', () => { [ { QQLXTXVidW2: 'Yes' }, [ - { type: 'ASSIGN', id: 'Z5z8vFQy0w0', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'Z5z8vFQy0w0', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], ], [ { QQLXTXVidW2: 'No' }, [ - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'Z5z8vFQy0w0', value: 'Probable Case', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'Z5z8vFQy0w0', + value: 'Probable Case', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], @@ -228,20 +1137,75 @@ describe('Event rules engine', () => { [ { QQLXTXVidW2: 'Unknown' }, [ - { type: 'ASSIGN', id: 'Z5z8vFQy0w0', value: 'Suspected Case', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'Z5z8vFQy0w0', + value: 'Suspected Case', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], @@ -249,19 +1213,69 @@ describe('Event rules engine', () => { [ { CUbZcLm9LyN: 'Yes' }, [ - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'Z5z8vFQy0w0', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], @@ -269,80 +1283,300 @@ describe('Event rules engine', () => { [ { QQLXTXVidW2: 'Yes', ovY6E8BSdto: 'Inconclusive' }, [ - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'Z5z8vFQy0w0', value: 'Probable Case', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'Z5z8vFQy0w0', + value: 'Probable Case', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], ], [ { QQLXTXVidW2: 'Yes', ovY6E8BSdto: 'Positive' }, [ - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'Z5z8vFQy0w0', value: 'Laboratory Confirmed Case', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'Z5z8vFQy0w0', + value: 'Laboratory Confirmed Case', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], ], [ { QQLXTXVidW2: 'Yes', ovY6E8BSdto: 'Negative' }, [ - { type: 'ASSIGN', id: 'Z5z8vFQy0w0', value: 'Suspected Case', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'Z5z8vFQy0w0', + value: 'Suspected Case', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], ], [ { QQLXTXVidW2: 'Yes', ovY6E8BSdto: 'Unknown' }, [ - { type: 'ASSIGN', id: 'Z5z8vFQy0w0', value: 'Suspected Case', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'PFXeJV8d7ja', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'MkeWrqeqZXL' }, - { type: 'HIDEOPTION', id: 'ovY6E8BSdto', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'fPV0gQ8ds6D' }, + { + type: 'ASSIGN', + id: 'Z5z8vFQy0w0', + value: 'Suspected Case', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'PFXeJV8d7ja', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'MkeWrqeqZXL', + }, + { + type: 'HIDEOPTION', + id: 'ovY6E8BSdto', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'fPV0gQ8ds6D', + }, { type: 'HIDEFIELD', id: 's3eoonJ8OJb', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'JGnHr6WI3AY', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, - { type: 'HIDEOPTION', id: 'CUbZcLm9LyN', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'pqxvAQU1z9W' }, + { + type: 'HIDEOPTION', + id: 'JGnHr6WI3AY', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'p8htbyJHydl', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, + { + type: 'HIDEOPTION', + id: 'CUbZcLm9LyN', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'pqxvAQU1z9W', + }, { type: 'HIDEFIELD', id: 'A4Fg6jgWauf', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, { type: 'HIDEFIELD', id: 'jBBkFuPKctq', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'dUeRcF2cApV' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'bYt4why1tL3' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'xBoo6HyaYcd' }, - { type: 'HIDEOPTION', id: 'bOYWVEBaWy6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, optionId: 'RCT079wdeKT' }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'dUeRcF2cApV', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'bYt4why1tL3', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'xBoo6HyaYcd', + }, + { + type: 'HIDEOPTION', + id: 'bOYWVEBaWy6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + optionId: 'RCT079wdeKT', + }, { type: 'HIDEFIELD', id: 'p8htbyJHydl', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, ], ], @@ -361,15 +1595,261 @@ describe('Event rules engine', () => { }); }); - describe('Event rules engine effects with functions and effects', () => { // these variables are shared between each test - const constants = [{ id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }]; - const dataElementsInProgram = { oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'BOOLEAN' }, qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'INTEGER' }, GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER' }, vV9UWAZohSf: { id: 'vV9UWAZohSf', valueType: 'INTEGER_POSITIVE' }, eMyVanycQSC: { id: 'eMyVanycQSC', valueType: 'DATE' }, K6uUAvq500H: { id: 'K6uUAvq500H', valueType: 'TEXT', optionSetId: 'eUZ79clX7y1' }, msodh3rEMJa: { id: 'msodh3rEMJa', valueType: 'DATE' }, S33cRBsnXPo: { id: 'S33cRBsnXPo', valueType: 'ORGANISATION_UNIT' }, fWIAEtYVEGk: { id: 'fWIAEtYVEGk', valueType: 'TEXT', optionSetId: 'iDFPKpFTiVw' }, ulD2zW0TIy2: { id: 'ulD2zW0TIy2', valueType: 'FILE_RESOURCE' } }; - const programRules = [{ id: 'CTzRoPyvf8v', condition: 'true', displayName: 'Testing the functions!', programId: 'eBAyeGv0exc', programRuleActions: [{ id: 'isP0uvT24jf', displayContent: "d2:yearsBetween( '2010-01-28', V{event_date}) =", data: "d2:yearsBetween( '2010-01-28', V{event_date})", programRuleActionType: 'DISPLAYTEXT' }, { id: 'vQCRnX6w9pM', displayContent: 'd2:oizp( -10000000 ) =', data: 'd2:oizp( -10000000 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'SYAL0GIDnxI', displayContent: 'display age = ', data: 'd2:hasValue(#{age}) && #{age}', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'Xa0tKyNk5YE', displayContent: 'org_unit = ', data: 'V{orgunit_code}', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'JXssEpbJdO2', displayContent: 'd2:right(#{age}, 3) = ', data: 'd2:hasValue(#{age}) && d2:right(#{age}, 3)', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'o0LLNIYsliy', displayContent: "d2:monthsBetween( '2020-01-28', V{event_date}) = ", data: "d2:monthsBetween( '2020-01-28', V{event_date})", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'k07KnI11Sf4', displayContent: 'd2:left(#{age}, 3) = ', data: 'd2:hasValue(#{age}) && d2:left(#{age}, 3)', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'OITs4nPfMQ3', displayContent: "d2:split('these-are-testing-values', '-', 2) = ", data: "d2:split('these-are-testing-values', '-', 2)", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'EzkFLDtAxCR', displayContent: 'd2:modulus( 12 , 100 ) = ', data: 'd2:modulus( 12 , 100 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'RCYEyOly0Mi', displayContent: "d2:countIfValue( #{gender}, 'Male' ) = ", data: "d2:countIfValue( #{gender}, 'Male' )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'bRnjbxIwIRd', displayContent: 'd2:round( 12.5 ) = ', data: 'd2:round( 12.5 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'BuEcHNoD98P', displayContent: 'd2:ceil(11.3) = ', data: 'd2:ceil(11.3)', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'Foc3PhzoAVr', displayContent: 'd2:count(#{age}) = ', data: 'd2:count(#{age})', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'QpeF2WDjwIV', displayContent: "d2:addDays( '2020-01-12', 5 ) = ", data: "d2:addDays( '2020-01-12', 5 )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'WJTjezLR4cJ', displayContent: "d2:weeksBetween('2020-01-28', V{event_date} ) = ", data: "d2:weeksBetween('2020-01-28', V{event_date} )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'YnE4dNJVF2P', displayContent: 'd2:zing( -2 ) = ', data: 'd2:zing( -2 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'bZsv2cUkbB7', displayContent: 'd2:floor( 11.5 ) =', data: 'd2:floor( 11.5 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'J8RxAbHlnO3', displayContent: 'd2:oizp( 10000000 ) = ', data: 'd2:oizp( 10000000 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'NT1wojA2RdT', displayContent: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = ", data: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking')", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'NUGe7EUVouK', displayContent: "d2:substring('hello dhis 2', 6, 10) = ", data: "d2:substring('hello dhis 2', 6, 10)", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'Ma6nCIGrBrd', displayContent: "d2:length( 'dhis2 rocks' ) = ", data: "d2:length( 'dhis2 rocks' )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'RRSDsxWiUMc', displayContent: 'd2:round( 0 ) = ', data: 'd2:round( 0 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'QUe0Pks4ckc', displayContent: 'd2:countIfValue( #{age}, 1 ) = ', data: 'd2:countIfValue( #{age}, 1 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'sHaE1YI0ur2', displayContent: 'd2:zing( 1000 ) = ', data: 'd2:zing( 1000 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'EojHcBMpW7q', displayContent: 'd2:hasValue( #{age} ) = ', data: 'd2:hasValue( #{age} )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }] }]; - const programRuleVariables = [{ id: 'RycV5uDi66i', dataElementId: 'qrur9Dvnyt5', displayName: 'age', programId: 'eBAyeGv0exc', programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', useNameForOptionSet: true }, { id: 'zINGRka3g9N', dataElementId: 'oZg33kd9taw', displayName: 'gender', programId: 'eBAyeGv0exc', programRuleVariableSourceType: 'DATAELEMENT_NEWEST_EVENT_PROGRAM', useNameForOptionSet: true }, { id: 'Zj7UnCAulEk.vV9UWAZohSf', displayName: 'Zj7UnCAulEk.vV9UWAZohSf', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', dataElementId: 'vV9UWAZohSf', programId: 'eBAyeGv0exc' }, { id: 'Zj7UnCAulEk.GieVkTxp4HH', displayName: 'Zj7UnCAulEk.GieVkTxp4HH', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', dataElementId: 'GieVkTxp4HH', programId: 'eBAyeGv0exc' }, { id: 'Zj7UnCAulEk.GieVkTxp4HH', displayName: 'Zj7UnCAulEk.GieVkTxp4HH', programRuleVariableSourceType: 'DATAELEMENT_CURRENT_EVENT', dataElementId: 'GieVkTxp4HH', programId: 'eBAyeGv0exc' }]; + const constants = [ + { id: 'Gfd3ppDfq8E', displayName: 'Commodity ordering overhead', value: 5 }, + { id: 'bCqvfPR02Im', displayName: 'Pi', value: 3.14 }, + ]; + const dataElementsInProgram = { + oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, + SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'BOOLEAN' }, + qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'INTEGER' }, + GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER' }, + vV9UWAZohSf: { id: 'vV9UWAZohSf', valueType: 'INTEGER_POSITIVE' }, + eMyVanycQSC: { id: 'eMyVanycQSC', valueType: 'DATE' }, + K6uUAvq500H: { id: 'K6uUAvq500H', valueType: 'TEXT', optionSetId: 'eUZ79clX7y1' }, + msodh3rEMJa: { id: 'msodh3rEMJa', valueType: 'DATE' }, + S33cRBsnXPo: { id: 'S33cRBsnXPo', valueType: 'ORGANISATION_UNIT' }, + fWIAEtYVEGk: { id: 'fWIAEtYVEGk', valueType: 'TEXT', optionSetId: 'iDFPKpFTiVw' }, + ulD2zW0TIy2: { id: 'ulD2zW0TIy2', valueType: 'FILE_RESOURCE' }, + }; + const programRules = [ + { + id: 'CTzRoPyvf8v', + condition: 'true', + displayName: 'Testing the functions!', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'isP0uvT24jf', + displayContent: "d2:yearsBetween( '2010-01-28', V{event_date}) =", + data: "d2:yearsBetween( '2010-01-28', V{event_date})", + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'vQCRnX6w9pM', + displayContent: 'd2:oizp( -10000000 ) =', + data: 'd2:oizp( -10000000 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'SYAL0GIDnxI', + displayContent: 'display age = ', + data: 'd2:hasValue(#{age}) && #{age}', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Xa0tKyNk5YE', + displayContent: 'org_unit = ', + data: 'V{orgunit_code}', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'JXssEpbJdO2', + displayContent: 'd2:right(#{age}, 3) = ', + data: 'd2:hasValue(#{age}) && d2:right(#{age}, 3)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'o0LLNIYsliy', + displayContent: "d2:monthsBetween( '2020-01-28', V{event_date}) = ", + data: "d2:monthsBetween( '2020-01-28', V{event_date})", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'k07KnI11Sf4', + displayContent: 'd2:left(#{age}, 3) = ', + data: 'd2:hasValue(#{age}) && d2:left(#{age}, 3)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'OITs4nPfMQ3', + displayContent: "d2:split('these-are-testing-values', '-', 2) = ", + data: "d2:split('these-are-testing-values', '-', 2)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'EzkFLDtAxCR', + displayContent: 'd2:modulus( 12 , 100 ) = ', + data: 'd2:modulus( 12 , 100 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'RCYEyOly0Mi', + displayContent: "d2:countIfValue( #{gender}, 'Male' ) = ", + data: "d2:countIfValue( #{gender}, 'Male' )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'bRnjbxIwIRd', + displayContent: 'd2:round( 12.5 ) = ', + data: 'd2:round( 12.5 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'BuEcHNoD98P', + displayContent: 'd2:ceil(11.3) = ', + data: 'd2:ceil(11.3)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Foc3PhzoAVr', + displayContent: 'd2:count(#{age}) = ', + data: 'd2:count(#{age})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'QpeF2WDjwIV', + displayContent: "d2:addDays( '2020-01-12', 5 ) = ", + data: "d2:addDays( '2020-01-12', 5 )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'WJTjezLR4cJ', + displayContent: "d2:weeksBetween('2020-01-28', V{event_date} ) = ", + data: "d2:weeksBetween('2020-01-28', V{event_date} )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'YnE4dNJVF2P', + displayContent: 'd2:zing( -2 ) = ', + data: 'd2:zing( -2 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'bZsv2cUkbB7', + displayContent: 'd2:floor( 11.5 ) =', + data: 'd2:floor( 11.5 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'J8RxAbHlnO3', + displayContent: 'd2:oizp( 10000000 ) = ', + data: 'd2:oizp( 10000000 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'NT1wojA2RdT', + displayContent: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = ", + data: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'NUGe7EUVouK', + displayContent: "d2:substring('hello dhis 2', 6, 10) = ", + data: "d2:substring('hello dhis 2', 6, 10)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Ma6nCIGrBrd', + displayContent: "d2:length( 'dhis2 rocks' ) = ", + data: "d2:length( 'dhis2 rocks' )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'RRSDsxWiUMc', + displayContent: 'd2:round( 0 ) = ', + data: 'd2:round( 0 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'QUe0Pks4ckc', + displayContent: 'd2:countIfValue( #{age}, 1 ) = ', + data: 'd2:countIfValue( #{age}, 1 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'sHaE1YI0ur2', + displayContent: 'd2:zing( 1000 ) = ', + data: 'd2:zing( 1000 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'EojHcBMpW7q', + displayContent: 'd2:hasValue( #{age} ) = ', + data: 'd2:hasValue( #{age} )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + ], + }, + ]; + const programRuleVariables = [ + { + id: 'RycV5uDi66i', + dataElementId: 'qrur9Dvnyt5', + displayName: 'age', + programId: 'eBAyeGv0exc', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: true, + }, + { + id: 'zINGRka3g9N', + dataElementId: 'oZg33kd9taw', + displayName: 'gender', + programId: 'eBAyeGv0exc', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: true, + }, + { + id: 'Zj7UnCAulEk.vV9UWAZohSf', + displayName: 'Zj7UnCAulEk.vV9UWAZohSf', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_PREVIOUS_EVENT, + dataElementId: 'vV9UWAZohSf', + programId: 'eBAyeGv0exc', + }, + { + id: 'Zj7UnCAulEk.GieVkTxp4HH', + displayName: 'Zj7UnCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.TEI_ATTRIBUTE, + dataElementId: 'GieVkTxp4HH', + programId: 'eBAyeGv0exc', + }, + { + id: 'Zj7UnCAulEk.GieVkTxp4HH', + displayName: 'Zj7UnCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: 'UNKNOWN', + dataElementId: 'GieVkTxp4HH', + programId: 'eBAyeGv0exc', + }, + ]; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC', code: 'OU_559' }; - const optionSets = { pC3N9N77UmT: { id: 'pC3N9N77UmT', displayName: 'Gender', version: 0, valueType: 'TEXT', options: [{ id: 'rBvjJYbMCVx', displayName: 'Male', code: 'Male', translations: [] }, { id: 'Mnp3oXrpAbK', displayName: 'Female', code: 'Female', translations: [] }] } }; + const optionSets = { + pC3N9N77UmT: { + id: 'pC3N9N77UmT', + displayName: 'Gender', + version: 0, + valueType: 'TEXT', + options: [ + { id: 'rBvjJYbMCVx', displayName: 'Male', code: 'Male', translations: [] }, + { id: 'Mnp3oXrpAbK', displayName: 'Female', code: 'Female', translations: [] }, + ], + }, + }; // NOTE: in this test we dont use toMatchSnapshot instead we test again hardcoded values. Since the effects are plenty // here each time this way we can avoid mistakes in comparing snapshots @@ -377,95 +1857,410 @@ describe('Event rules engine effects with functions and effects', () => { [ {}, [ - { type: 'DISPLAYTEXT', displayText: { id: 'isP0uvT24jf', message: "d2:yearsBetween( '2010-01-28', V{event_date}) = " } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'vQCRnX6w9pM', message: 'd2:oizp( -10000000 ) = 0' } }, + { + type: 'DISPLAYTEXT', + displayText: { id: 'isP0uvT24jf', message: "d2:yearsBetween( '2010-01-28', V{event_date}) = " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'vQCRnX6w9pM', message: 'd2:oizp( -10000000 ) = 0' }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'SYAL0GIDnxI', message: 'display age = ' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Xa0tKyNk5YE', message: 'org_unit = OU_559' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'JXssEpbJdO2', message: 'd2:right(#{age}, 3) = ' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'o0LLNIYsliy', message: "d2:monthsBetween( '2020-01-28', V{event_date}) = " } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'k07KnI11Sf4', message: 'd2:left(#{age}, 3) = ' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'OITs4nPfMQ3', message: "d2:split('these-are-testing-values', '-', 2) = testing" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'EzkFLDtAxCR', message: 'd2:modulus( 12 , 100 ) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'RCYEyOly0Mi', message: "d2:countIfValue( #{gender}, 'Male' ) = 0" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'bRnjbxIwIRd', message: 'd2:round( 12.5 ) = 13' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'BuEcHNoD98P', message: 'd2:ceil(11.3) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{age}) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'QpeF2WDjwIV', message: "d2:addDays( '2020-01-12', 5 ) = 2020-01-17" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'WJTjezLR4cJ', message: "d2:weeksBetween('2020-01-28', V{event_date} ) = " } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'YnE4dNJVF2P', message: 'd2:zing( -2 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'bZsv2cUkbB7', message: 'd2:floor( 11.5 ) = 11' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'J8RxAbHlnO3', message: 'd2:oizp( 10000000 ) = 1' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NT1wojA2RdT', message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NUGe7EUVouK', message: "d2:substring('hello dhis 2', 6, 10) = dhis" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Ma6nCIGrBrd', message: "d2:length( 'dhis2 rocks' ) = 11" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'RRSDsxWiUMc', message: 'd2:round( 0 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'QUe0Pks4ckc', message: 'd2:countIfValue( #{age}, 1 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'sHaE1YI0ur2', message: 'd2:zing( 1000 ) = 1000' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'EojHcBMpW7q', message: 'd2:hasValue( #{age} ) = ' } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Xa0tKyNk5YE', message: 'org_unit = OU_559' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'JXssEpbJdO2', message: 'd2:right(#{age}, 3) = ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'o0LLNIYsliy', message: "d2:monthsBetween( '2020-01-28', V{event_date}) = " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'k07KnI11Sf4', message: 'd2:left(#{age}, 3) = ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'OITs4nPfMQ3', + message: "d2:split('these-are-testing-values', '-', 2) = testing", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'EzkFLDtAxCR', message: 'd2:modulus( 12 , 100 ) = 12' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'RCYEyOly0Mi', message: "d2:countIfValue( #{gender}, 'Male' ) = 0" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'bRnjbxIwIRd', message: 'd2:round( 12.5 ) = 13' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'BuEcHNoD98P', message: 'd2:ceil(11.3) = 12' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{age}) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'QpeF2WDjwIV', message: "d2:addDays( '2020-01-12', 5 ) = 2020-01-17" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'WJTjezLR4cJ', message: "d2:weeksBetween('2020-01-28', V{event_date} ) = " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'YnE4dNJVF2P', message: 'd2:zing( -2 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'bZsv2cUkbB7', message: 'd2:floor( 11.5 ) = 11' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'J8RxAbHlnO3', message: 'd2:oizp( 10000000 ) = 1' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'NT1wojA2RdT', + message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'NUGe7EUVouK', message: "d2:substring('hello dhis 2', 6, 10) = dhis" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Ma6nCIGrBrd', message: "d2:length( 'dhis2 rocks' ) = 11" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'RRSDsxWiUMc', message: 'd2:round( 0 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'QUe0Pks4ckc', message: 'd2:countIfValue( #{age}, 1 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'sHaE1YI0ur2', message: 'd2:zing( 1000 ) = 1000' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'EojHcBMpW7q', message: 'd2:hasValue( #{age} ) = ' }, + }, ], ], [ { - oZg33kd9taw: 'Male', qrur9Dvnyt5: 0, occurredAt: '2020-04-30T22:00:00.000Z', + oZg33kd9taw: 'Male', + qrur9Dvnyt5: 0, + occurredAt: '2020-04-30T22:00:00.000Z', }, [ - { type: 'DISPLAYTEXT', displayText: { id: 'isP0uvT24jf', message: "d2:yearsBetween( '2010-01-28', V{event_date}) = 10" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'vQCRnX6w9pM', message: 'd2:oizp( -10000000 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'SYAL0GIDnxI', message: 'display age = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Xa0tKyNk5YE', message: 'org_unit = OU_559' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'JXssEpbJdO2', message: 'd2:right(#{age}, 3) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'o0LLNIYsliy', message: "d2:monthsBetween( '2020-01-28', V{event_date}) = 3" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'k07KnI11Sf4', message: 'd2:left(#{age}, 3) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'OITs4nPfMQ3', message: "d2:split('these-are-testing-values', '-', 2) = testing" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'EzkFLDtAxCR', message: 'd2:modulus( 12 , 100 ) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'RCYEyOly0Mi', message: "d2:countIfValue( #{gender}, 'Male' ) = 1" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'bRnjbxIwIRd', message: 'd2:round( 12.5 ) = 13' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'BuEcHNoD98P', message: 'd2:ceil(11.3) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{age}) = 1' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'QpeF2WDjwIV', message: "d2:addDays( '2020-01-12', 5 ) = 2020-01-17" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'WJTjezLR4cJ', message: "d2:weeksBetween('2020-01-28', V{event_date} ) = 13" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'YnE4dNJVF2P', message: 'd2:zing( -2 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'bZsv2cUkbB7', message: 'd2:floor( 11.5 ) = 11' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'J8RxAbHlnO3', message: 'd2:oizp( 10000000 ) = 1' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NT1wojA2RdT', message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NUGe7EUVouK', message: "d2:substring('hello dhis 2', 6, 10) = dhis" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Ma6nCIGrBrd', message: "d2:length( 'dhis2 rocks' ) = 11" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'RRSDsxWiUMc', message: 'd2:round( 0 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'QUe0Pks4ckc', message: 'd2:countIfValue( #{age}, 1 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'sHaE1YI0ur2', message: 'd2:zing( 1000 ) = 1000' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'EojHcBMpW7q', message: 'd2:hasValue( #{age} ) = true' } }, + { + type: 'DISPLAYTEXT', + displayText: { id: 'isP0uvT24jf', message: "d2:yearsBetween( '2010-01-28', V{event_date}) = 10" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'vQCRnX6w9pM', message: 'd2:oizp( -10000000 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'SYAL0GIDnxI', message: 'display age = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Xa0tKyNk5YE', message: 'org_unit = OU_559' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'JXssEpbJdO2', message: 'd2:right(#{age}, 3) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'o0LLNIYsliy', message: "d2:monthsBetween( '2020-01-28', V{event_date}) = 3" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'k07KnI11Sf4', message: 'd2:left(#{age}, 3) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'OITs4nPfMQ3', + message: "d2:split('these-are-testing-values', '-', 2) = testing", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'EzkFLDtAxCR', message: 'd2:modulus( 12 , 100 ) = 12' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'RCYEyOly0Mi', message: "d2:countIfValue( #{gender}, 'Male' ) = 1" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'bRnjbxIwIRd', message: 'd2:round( 12.5 ) = 13' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'BuEcHNoD98P', message: 'd2:ceil(11.3) = 12' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{age}) = 1' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'QpeF2WDjwIV', message: "d2:addDays( '2020-01-12', 5 ) = 2020-01-17" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'WJTjezLR4cJ', message: "d2:weeksBetween('2020-01-28', V{event_date} ) = 13" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'YnE4dNJVF2P', message: 'd2:zing( -2 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'bZsv2cUkbB7', message: 'd2:floor( 11.5 ) = 11' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'J8RxAbHlnO3', message: 'd2:oizp( 10000000 ) = 1' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'NT1wojA2RdT', + message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'NUGe7EUVouK', message: "d2:substring('hello dhis 2', 6, 10) = dhis" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Ma6nCIGrBrd', message: "d2:length( 'dhis2 rocks' ) = 11" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'RRSDsxWiUMc', message: 'd2:round( 0 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'QUe0Pks4ckc', message: 'd2:countIfValue( #{age}, 1 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'sHaE1YI0ur2', message: 'd2:zing( 1000 ) = 1000' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'EojHcBMpW7q', message: 'd2:hasValue( #{age} ) = true' }, + }, ], ], [ { - oZg33kd9taw: 'Male', qrur9Dvnyt5: 1000000000, occurredAt: '2020-04-30T22:00:00.000Z', + oZg33kd9taw: 'Male', + qrur9Dvnyt5: 1000000000, + occurredAt: '2020-04-30T22:00:00.000Z', }, [ - { type: 'DISPLAYTEXT', displayText: { id: 'isP0uvT24jf', message: "d2:yearsBetween( '2010-01-28', V{event_date}) = 10" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'vQCRnX6w9pM', message: 'd2:oizp( -10000000 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'SYAL0GIDnxI', message: 'display age = 1000000000' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Xa0tKyNk5YE', message: 'org_unit = OU_559' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'JXssEpbJdO2', message: 'd2:right(#{age}, 3) = 000' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'o0LLNIYsliy', message: "d2:monthsBetween( '2020-01-28', V{event_date}) = 3" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'k07KnI11Sf4', message: 'd2:left(#{age}, 3) = 100' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'OITs4nPfMQ3', message: "d2:split('these-are-testing-values', '-', 2) = testing" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'EzkFLDtAxCR', message: 'd2:modulus( 12 , 100 ) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'RCYEyOly0Mi', message: "d2:countIfValue( #{gender}, 'Male' ) = 1" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'bRnjbxIwIRd', message: 'd2:round( 12.5 ) = 13' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'BuEcHNoD98P', message: 'd2:ceil(11.3) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{age}) = 1' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'QpeF2WDjwIV', message: "d2:addDays( '2020-01-12', 5 ) = 2020-01-17" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'WJTjezLR4cJ', message: "d2:weeksBetween('2020-01-28', V{event_date} ) = 13" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'YnE4dNJVF2P', message: 'd2:zing( -2 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'bZsv2cUkbB7', message: 'd2:floor( 11.5 ) = 11' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'J8RxAbHlnO3', message: 'd2:oizp( 10000000 ) = 1' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NT1wojA2RdT', message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NUGe7EUVouK', message: "d2:substring('hello dhis 2', 6, 10) = dhis" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Ma6nCIGrBrd', message: "d2:length( 'dhis2 rocks' ) = 11" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'RRSDsxWiUMc', message: 'd2:round( 0 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'QUe0Pks4ckc', message: 'd2:countIfValue( #{age}, 1 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'sHaE1YI0ur2', message: 'd2:zing( 1000 ) = 1000' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'EojHcBMpW7q', message: 'd2:hasValue( #{age} ) = true' } }, + { + type: 'DISPLAYTEXT', + displayText: { id: 'isP0uvT24jf', message: "d2:yearsBetween( '2010-01-28', V{event_date}) = 10" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'vQCRnX6w9pM', message: 'd2:oizp( -10000000 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'SYAL0GIDnxI', message: 'display age = 1000000000' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Xa0tKyNk5YE', message: 'org_unit = OU_559' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'JXssEpbJdO2', message: 'd2:right(#{age}, 3) = 000' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'o0LLNIYsliy', message: "d2:monthsBetween( '2020-01-28', V{event_date}) = 3" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'k07KnI11Sf4', message: 'd2:left(#{age}, 3) = 100' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'OITs4nPfMQ3', + message: "d2:split('these-are-testing-values', '-', 2) = testing", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'EzkFLDtAxCR', message: 'd2:modulus( 12 , 100 ) = 12' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'RCYEyOly0Mi', message: "d2:countIfValue( #{gender}, 'Male' ) = 1" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'bRnjbxIwIRd', message: 'd2:round( 12.5 ) = 13' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'BuEcHNoD98P', message: 'd2:ceil(11.3) = 12' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{age}) = 1' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'QpeF2WDjwIV', message: "d2:addDays( '2020-01-12', 5 ) = 2020-01-17" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'WJTjezLR4cJ', message: "d2:weeksBetween('2020-01-28', V{event_date} ) = 13" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'YnE4dNJVF2P', message: 'd2:zing( -2 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'bZsv2cUkbB7', message: 'd2:floor( 11.5 ) = 11' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'J8RxAbHlnO3', message: 'd2:oizp( 10000000 ) = 1' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'NT1wojA2RdT', + message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'NUGe7EUVouK', message: "d2:substring('hello dhis 2', 6, 10) = dhis" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Ma6nCIGrBrd', message: "d2:length( 'dhis2 rocks' ) = 11" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'RRSDsxWiUMc', message: 'd2:round( 0 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'QUe0Pks4ckc', message: 'd2:countIfValue( #{age}, 1 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'sHaE1YI0ur2', message: 'd2:zing( 1000 ) = 1000' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'EojHcBMpW7q', message: 'd2:hasValue( #{age} ) = true' }, + }, ], ], ])('where functions take place', (currentEvent, expected) => { @@ -485,8 +2280,71 @@ describe('Event rules engine effects with functions and effects', () => { describe('Event rules engine effects with functions and effects', () => { // these variables are shared between each test const constants = []; - const dataElementsInProgram = { oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'BOOLEAN' }, qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'INTEGER' }, GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER' }, vV9UWAZohSf: { id: 'vV9UWAZohSf', valueType: 'INTEGER_POSITIVE' }, eMyVanycQSC: { id: 'eMyVanycQSC', valueType: 'DATE' }, K6uUAvq500H: { id: 'K6uUAvq500H', valueType: 'TEXT', optionSetId: 'eUZ79clX7y1' }, msodh3rEMJa: { id: 'msodh3rEMJa', valueType: 'DATE' }, S33cRBsnXPo: { id: 'S33cRBsnXPo', valueType: 'ORGANISATION_UNIT' }, fWIAEtYVEGk: { id: 'fWIAEtYVEGk', valueType: 'TEXT', optionSetId: 'iDFPKpFTiVw' }, ulD2zW0TIy2: { id: 'ulD2zW0TIy2', valueType: 'FILE_RESOURCE' } }; - const programRules = [{ id: 'cq1dwUY4lVU', condition: 'true', displayName: 'testing the z-scores', programId: 'eBAyeGv0exc', programRuleActions: [{ id: 'I6pDcSm2m0g', displayContent: "d2:zScoreWFA( 20, 15, 'M' ) = ", data: "d2:zScoreWFA( 20, 15, 'M' )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'No0T9TgN1Px', displayContent: "d2:zScoreWFH( 100, 20, 'M' ) = ", data: "d2:zScoreWFH( 100, 20, 'M' )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'n1GUxR8fShY', displayContent: "d2:zScoreHFA( 15, 20, 'F' ) =", data: "d2:zScoreHFA( 15, 20, 'F' ) ", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'QJlZHo0GoVK', displayContent: "d2:zScoreWFH( 100, 20, 'F' ) = ", data: "d2:zScoreWFH( 100, 20, 'F' ) ", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'ItuKGBUuJgK', displayContent: "d2:zScoreWFA( 20, 15, 'F' ) = ", data: "d2:zScoreWFA( 20, 15, 'F' )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, { id: 'uhyaCDzzivm', displayContent: "d2:zScoreHFA( 15, 20, 'M' ) =", data: "d2:zScoreHFA( 15, 20, 'M' )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }] }]; + const dataElementsInProgram = { + oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, + SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'BOOLEAN' }, + qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'INTEGER' }, + GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER' }, + vV9UWAZohSf: { id: 'vV9UWAZohSf', valueType: 'INTEGER_POSITIVE' }, + eMyVanycQSC: { id: 'eMyVanycQSC', valueType: 'DATE' }, + K6uUAvq500H: { id: 'K6uUAvq500H', valueType: 'TEXT', optionSetId: 'eUZ79clX7y1' }, + msodh3rEMJa: { id: 'msodh3rEMJa', valueType: 'DATE' }, + S33cRBsnXPo: { id: 'S33cRBsnXPo', valueType: 'ORGANISATION_UNIT' }, + fWIAEtYVEGk: { id: 'fWIAEtYVEGk', valueType: 'TEXT', optionSetId: 'iDFPKpFTiVw' }, + ulD2zW0TIy2: { id: 'ulD2zW0TIy2', valueType: 'FILE_RESOURCE' }, + }; + const programRules = [ + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing the z-scores', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'I6pDcSm2m0g', + displayContent: "d2:zScoreWFA( 20, 15, 'M' ) = ", + data: "d2:zScoreWFA( 20, 15, 'M' )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'No0T9TgN1Px', + displayContent: "d2:zScoreWFH( 100, 20, 'M' ) = ", + data: "d2:zScoreWFH( 100, 20, 'M' )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'n1GUxR8fShY', + displayContent: "d2:zScoreHFA( 15, 20, 'F' ) =", + data: "d2:zScoreHFA( 15, 20, 'F' ) ", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'QJlZHo0GoVK', + displayContent: "d2:zScoreWFH( 100, 20, 'F' ) = ", + data: "d2:zScoreWFH( 100, 20, 'F' ) ", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'ItuKGBUuJgK', + displayContent: "d2:zScoreWFA( 20, 15, 'F' ) = ", + data: "d2:zScoreWFA( 20, 15, 'F' )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'uhyaCDzzivm', + displayContent: "d2:zScoreHFA( 15, 20, 'M' ) =", + data: "d2:zScoreHFA( 15, 20, 'M' )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + ], + }, + ]; const programRuleVariables = []; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; const optionSets = {}; @@ -505,12 +2363,36 @@ describe('Event rules engine effects with functions and effects', () => { // then expect(rulesEffects).toEqual([ - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'I6pDcSm2m0g', message: "d2:zScoreWFA( 20, 15, 'M' ) = 2.47" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'No0T9TgN1Px', message: "d2:zScoreWFH( 100, 20, 'M' ) = 3.5" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'n1GUxR8fShY', message: "d2:zScoreHFA( 15, 20, 'F' ) = -3.5" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'QJlZHo0GoVK', message: "d2:zScoreWFH( 100, 20, 'F' ) = 2.84" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'ItuKGBUuJgK', message: "d2:zScoreWFA( 20, 15, 'F' ) = 2.65" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'uhyaCDzzivm', message: "d2:zScoreHFA( 15, 20, 'M' ) = -3.5" } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'I6pDcSm2m0g', message: "d2:zScoreWFA( 20, 15, 'M' ) = 2.47" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'No0T9TgN1Px', message: "d2:zScoreWFH( 100, 20, 'M' ) = 3.5" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'n1GUxR8fShY', message: "d2:zScoreHFA( 15, 20, 'F' ) = -3.5" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'QJlZHo0GoVK', message: "d2:zScoreWFH( 100, 20, 'F' ) = 2.84" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'ItuKGBUuJgK', message: "d2:zScoreWFA( 20, 15, 'F' ) = 2.65" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'uhyaCDzzivm', message: "d2:zScoreHFA( 15, 20, 'M' ) = -3.5" }, + }, ]); }); }); @@ -519,7 +2401,11 @@ describe('Event rules engine effects with functions and effects', () => { describe('Event rules engine', () => { // these variables are shared between each test const constants = []; - const dataElementsInProgram = { oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'BOOLEAN' }, SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'TRUE_ONLY' }, qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'NUMBER' } }; + const dataElementsInProgram = { + oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'BOOLEAN' }, + SWfdB5lX0fk: { id: 'SWfdB5lX0fk', valueType: 'TRUE_ONLY' }, + qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'NUMBER' }, + }; const programRuleVariables = []; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; const optionSets = {}; @@ -527,50 +2413,110 @@ describe('Event rules engine', () => { describe.each([ [ - [{ - id: 'cq1dwUY4lVU', - condition: 'true', - displayName: 'testing assign actions', - programId: 'eBAyeGv0exc', - programRuleActions: [ - { id: 'lJOYxhjupxz', data: 'true', dataElementId: 'oZg33kd9taw', programRuleActionType: 'ASSIGN' }, - { id: 'lJOYxhjupx1', data: 'true', dataElementId: 'SWfdB5lX0fk', programRuleActionType: 'ASSIGN' }, - { id: 'lJOYxhjupx2', data: '6', dataElementId: 'qrur9Dvnyt5', programRuleActionType: 'ASSIGN' }, - ], - }], [ - { type: 'ASSIGN', id: 'oZg33kd9taw', value: 'true', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'SWfdB5lX0fk', value: 'true', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: '6', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing assign actions', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'lJOYxhjupxz', + data: 'true', + dataElementId: 'oZg33kd9taw', + programRuleActionType: 'ASSIGN', + }, + { + id: 'lJOYxhjupx1', + data: 'true', + dataElementId: 'SWfdB5lX0fk', + programRuleActionType: 'ASSIGN', + }, + { id: 'lJOYxhjupx2', data: '6', dataElementId: 'qrur9Dvnyt5', programRuleActionType: 'ASSIGN' }, + ], + }, ], - ], [ - [{ - id: 'cq1dwUY4lVU', - condition: 'true', - displayName: 'testing assign actions', - programId: 'eBAyeGv0exc', - programRuleActions: [ - { id: 'lJOYxhjupxz', data: '', dataElementId: 'oZg33kd9taw', programRuleActionType: 'ASSIGN' }, - { id: 'lJOYxhjupx1', data: '', dataElementId: 'SWfdB5lX0fk', programRuleActionType: 'ASSIGN' }, - { id: 'lJOYxhjupx2', data: '', dataElementId: 'qrur9Dvnyt5', programRuleActionType: 'ASSIGN' }, - ], - }], [ - { type: 'ASSIGN', id: 'oZg33kd9taw', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'SWfdB5lX0fk', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, - { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: null, targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }, + { + type: 'ASSIGN', + id: 'oZg33kd9taw', + value: 'true', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'SWfdB5lX0fk', + value: 'true', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'qrur9Dvnyt5', + value: '6', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + ], + ], + [ + [ + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing assign actions', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { id: 'lJOYxhjupxz', data: '', dataElementId: 'oZg33kd9taw', programRuleActionType: 'ASSIGN' }, + { id: 'lJOYxhjupx1', data: '', dataElementId: 'SWfdB5lX0fk', programRuleActionType: 'ASSIGN' }, + { id: 'lJOYxhjupx2', data: '', dataElementId: 'qrur9Dvnyt5', programRuleActionType: 'ASSIGN' }, + ], + }, + ], + [ + { + type: 'ASSIGN', + id: 'oZg33kd9taw', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'SWfdB5lX0fk', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + { + type: 'ASSIGN', + id: 'qrur9Dvnyt5', + value: null, + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, + ], + ], + [ + [ + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing assign actions', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'lJOYxhjupxz', + data: 'false', + dataElementId: 'oZg33kd9taw', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ], + [ + { + type: 'ASSIGN', + id: 'oZg33kd9taw', + value: 'false', + targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT, + }, ], - ], [ - [{ - id: 'cq1dwUY4lVU', - condition: 'true', - displayName: 'testing assign actions', - programId: 'eBAyeGv0exc', - programRuleActions: [ - { id: 'lJOYxhjupxz', data: 'false', dataElementId: 'oZg33kd9taw', programRuleActionType: 'ASSIGN' }, - ], - }], - [{ type: 'ASSIGN', id: 'oZg33kd9taw', value: 'false', targetDataType: rulesEngineEffectTargetDataTypes.DATA_ELEMENT }], ], ])('where assign actions are triggered', (programRules, expected) => { test(`with given value(s): ${JSON.stringify(programRules)}`, () => { @@ -586,3 +2532,393 @@ describe('Event rules engine', () => { }); }); }); + +describe('Assign effects', () => { + // these variables are shared between each test + const constants = []; + const dataElementsInProgram = { + qrur9Dvnyt5: { id: 'qrur9Dvnyt5', valueType: 'NUMBER' }, + hrur9Dvnyt5: { id: 'hrur9Dvnyt5', valueType: 'TRUE_ONLY' }, + g67r9Dvnyt5: { id: 'g67r9Dvnyt5', valueType: 'TRUE_ONLY' }, + sfer9Dvnyt5: { id: 'sfer9Dvnyt5', valueType: 'LONG_TEXT' }, + oZg33kd9taw: { id: 'oZg33kd9taw', valueType: 'BOOLEAN' }, + hjrr9Dvnyt5: { id: 'hjrr9Dvnyt5', valueType: 'LETTER' }, + lowr9Dvnyt5: { id: 'lowr9Dvnyt5', valueType: 'PHONE_NUMBER' }, + kht29Dvnyt5: { id: 'kht29Dvnyt5', valueType: 'EMAIL' }, + hyrt9Dvnyt5: { id: 'hyrt9Dvnyt5', valueType: 'DATE' }, + loir9Dvnyt5: { id: 'loir9Dvnyt5', valueType: 'DATETIME' }, + sldkjfjfjfe: { id: 'sldkjfjfjfe', valueType: 'TIME' }, + kyt49Dvnyt5: { id: 'kyt49Dvnyt5', valueType: 'PERCENTAGE' }, + kiyu9Dvnyt5: { id: 'kiyu9Dvnyt5', valueType: 'INTEGER' }, + frt59Dvnyt5: { id: 'frt59Dvnyt5', valueType: 'INTEGER_POSITIVE' }, + jhty9Dvnyt5: { id: 'jhty9Dvnyt5', valueType: 'INTEGER_NEGATIVE' }, + jhrr9Dvnyt5: { id: 'jhrr9Dvnyt5', valueType: 'INTEGER_ZERO_OR_POSITIVE' }, + plor9Dvnyt5: { id: 'plor9Dvnyt5', valueType: 'USERNAME' }, + frg39Dvnyt5: { id: 'frg39Dvnyt5', valueType: 'COORDINATE' }, + kjyu9Dvnyt5: { id: 'kjyu9Dvnyt5', valueType: 'ORGANISATION_UNIT' }, + kjfr9Dvnyt5: { id: 'kjfr9Dvnyt5', valueType: 'AGE' }, + lqwr9Dvnyt5: { id: 'lqwr9Dvnyt5', valueType: 'URL' }, + mjus9Dvnyt5: { id: 'mjus9Dvnyt5', valueType: 'FILE_RESOURCE' }, + fgrr9Dvnyt5: { id: 'fgrr9Dvnyt5', valueType: 'IMAGE' }, + oZ3fhkd9taw: { id: 'oZ3fhkd9taw', valueType: 'UNKNOWN' }, + }; + const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; + const optionSets = {}; + const currentEvent = {}; + + test('Assign effect corner cases', () => { + const programRules = [ + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing assign actions', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'lJOYxhjupx2', + data: "'string'", + dataElementId: 'qrur9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { id: 'lJOYxkjupx2', data: "'10'", dataElementId: 'qrur9Dvnyt5', programRuleActionType: 'ASSIGN' }, + { + id: 'lJhYxhjupxz', + data: "'string'", + dataElementId: 'oZg33kd9taw', + programRuleActionType: 'ASSIGN', + }, + { + id: 'lJfYxhjupxz', + data: "'string'", + dataElementId: 'oZ3fhkd9taw', + programRuleActionType: 'ASSIGN', + }, + { + id: 'sdfsd34567h', + data: 'value', + dataElementId: 'hrur9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'g67rgyvnyt5', + data: null, + dataElementId: 'g67r9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'fsafYxhjux2', + data: "'LONG_TEXT'", + dataElementId: 'sfer9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'o8rr9Dvnyt5', + data: "'LETTER'", + dataElementId: 'hjrr9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'lsdjlks4ffs', + data: '34535353', + dataElementId: 'lowr9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'hsjd7574jfj', + data: 'test@test.com', + dataElementId: 'kht29Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'sdf57549jco', + data: '11-11-2013', + dataElementId: 'hyrt9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'gtir9Dvnyt5', + data: '2021-05-31', + dataElementId: 'loir9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 's678jfjfjfe', + data: '11:11', + dataElementId: 'sldkjfjfjfe', + programRuleActionType: 'ASSIGN', + }, + { + id: 'sdhsfjks35f', + data: '30', + dataElementId: 'kyt49Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'kjosfjks35f', + data: '30', + dataElementId: 'kiyu9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'kjyofjks35f', + data: '30', + dataElementId: 'frt59Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'jhkj9Dvnyt5', + data: '-30', + dataElementId: 'jhty9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'hjyur9Dvnyt5', + data: '-30', + dataElementId: 'jhrr9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'lopr9Dvnyt5', + data: 'username', + dataElementId: 'plor9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'kjy39Dvnyt5', + data: "'12,4343'", + dataElementId: 'frg39Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'loiu9Dvnyt5', + data: 'ORGANISATION_UNIT', + dataElementId: 'kjyu9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'kjfr9Dvnyt5', + data: 'Invalid date', + dataElementId: 'kjfr9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'hywr9Dvnyt5', + data: 'urlPath', + dataElementId: 'lqwr9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'gt769Dvnyt5', + data: 'FILE_RESOURCE', + dataElementId: 'mjus9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + { + id: 'gt659Dvnyt5', + data: 'IMAGE', + dataElementId: 'fgrr9Dvnyt5', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ]; + const programRuleVariables = []; + systemSettingsStore.set({ dateFormat: 'YYYY-MM-DD' }); + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: null, targetDataType: 'dataElement' }, + { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: '10', targetDataType: 'dataElement' }, + { type: 'ASSIGN', id: 'oZg33kd9taw', value: 'false', targetDataType: 'dataElement' }, + { type: 'ASSIGN', id: 'oZ3fhkd9taw', value: '', targetDataType: 'dataElement' }, + { + id: 'hrur9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: 'false', + }, + { + id: 'g67r9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: null, + }, + { + id: 'sfer9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: 'LONG_TEXT', + }, + { + id: 'hjrr9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: 'LETTER', + }, + { + id: 'lowr9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: 34535353, + }, + { + id: 'kht29Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: false, + }, + { + id: 'hyrt9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '2013-01-01', + }, + { + id: 'loir9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: { + date: 'Invalid date', + time: 'Invalid date', + }, + }, + { + id: 'sldkjfjfjfe', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: false, + }, + { + id: 'kyt49Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '30', + }, + { + id: 'kiyu9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '30', + }, + { + id: 'frt59Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '30', + }, + { + id: 'jhty9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '-30', + }, + { + id: 'jhrr9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '-30', + }, + { + id: 'plor9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '', + }, + { + id: 'frg39Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: { + latitude: 2, + longitude: 434, + }, + }, + { + id: 'kjyu9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '', + }, + { + id: 'kjfr9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: { + date: 'Invalid date', + days: 'NaN', + months: 'NaN', + years: 'NaN', + }, + }, + { + id: 'lqwr9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: false, + }, + { + id: 'mjus9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '', + }, + { + id: 'fgrr9Dvnyt5', + targetDataType: 'dataElement', + type: 'ASSIGN', + value: '', + }, + ]); + }); + + test('Assign effect with the program rule variable id found in the content key', () => { + const programRules = [ + { + id: 'cq1dwUY4lVU', + condition: 'true', + displayName: 'testing assign actions', + programId: 'eBAyeGv0exc', + programRuleActions: [ + { + id: 'lJOYxhjupx2', + data: 'rowExpresion', + dataElementId: 'qrur9Dvnyt5', + programRuleActionType: 'ASSIGN', + content: 'Hemoglobin value lower than normal RycV5uDi66i', + }, + ], + }, + ]; + const programRuleVariables = [ + { + id: 'RycV5uDi66i', + dataElementId: 'qrur9Dvnyt5', + displayName: 'age', + programId: 'eBAyeGv0exc', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: true, + }, + ]; + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { type: 'ASSIGN', id: 'qrur9Dvnyt5', value: 'false', targetDataType: 'dataElement' }, + ]); + }); +}); diff --git a/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js b/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js index bbd2514ae8..213c6c5abf 100644 --- a/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js +++ b/src/core_modules/capture-core/rules/__tests__/rulesEffectsForTrackerProgram.test.js @@ -1,78 +1,865 @@ +import { variableSourceTypes } from '@dhis2/rules-engine-javascript'; import { rulesEngine } from '../rulesEngine'; test('expressions with d2Functions in tracker program', () => { // given const constants = []; - const trackedEntityAttributes = { w75KJ2mc4zz: { id: 'w75KJ2mc4zz', valueType: 'TEXT' }, zDhUuAYrxNC: { id: 'zDhUuAYrxNC', valueType: 'TEXT' }, cejWyOfXge6: { id: 'cejWyOfXge6', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, lZGmxYbs97q: { id: 'lZGmxYbs97q', valueType: 'NUMBER' } }; - const programRules = [{ - id: 'g82J3xsNer9', - condition: 'true', - displayName: 'Testing the functions!', - programId: 'IpHINAT79UW', - programRuleActions: [ - { id: 'Eeb7Ixr4Pvx', displayContent: "d2:left('dhis', 3) = ", data: "d2:left('dhis', 3)", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'lbIGJYVI57u', displayContent: 'd2:zing( -2 ) = ', data: 'd2:zing( -2 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'kwKhYpVRDyj', displayContent: "d2:monthsBetween( '2020-01-28', V{enrollment_date}) = ", data: "d2:monthsBetween( '2020-01-28', V{enrollment_date})", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'AFkfzcDf4Fs', displayContent: "d2:inOrgUnitGroup('CHC') = ", data: "d2:inOrgUnitGroup('CHC')", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'wmAQnxbs7V8', displayContent: 'd2:round( 12.5 ) = ', data: 'd2:round( 12.5 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'tFGwyDBQk3b', displayContent: 'd2:round( 0 ) = ', data: 'd2:round( 0 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'NSQV537qvyu', displayContent: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = ", data: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking')", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'M7vYMD5uNwD', displayContent: 'd2:ceil(11.3) = ', data: 'd2:ceil(11.3)', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'cgdUEJkqq0J', displayContent: "d2:yearsBetween( '2010-01-28', V{enrollment_date}) = ", data: "d2:yearsBetween( '2010-01-28', V{enrollment_date})", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'xZpYbFPXWG2', displayContent: 'd2:zing( 1000 ) = ', data: 'd2:zing( 1000 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'IGpzruAKVzk', displayContent: "d2:split('these-are-testing-values', '-', 2) = ", data: "d2:split('these-are-testing-values', '-', 2)", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'NLsawa3P5hc', displayContent: "d2:substring('hello dhis 2', 6, 10) = ", data: "d2:substring('hello dhis 2', 6, 10)", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'i0OgulFyVPQ', displayContent: 'd2:oizp( -10000000 ) = ', data: 'd2:oizp( -10000000 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'PqzKFmEMmuz', displayContent: "d2:right('dhis', 3) = ", data: "d2:right('dhis', 3)", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'xIUDr1lRV6N', displayContent: "d2:addDays( '2018-04-20', 100 ) = ", data: "d2:addDays( '2018-04-20', 100 )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'RXmprywJ0Rb', displayContent: 'd2:floor( 11.5 ) = ', data: 'd2:floor( 11.5 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'cZQngI2IC1a', displayContent: "d2:length( 'dhis2 rocks' ) = ", data: "d2:length( 'dhis2 rocks' )", location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'qSe8GmlwpgZ', displayContent: "d2:weeksBetween('2020-01-28', V{enrollment_date} ) = ", data: "d2:weeksBetween('2020-01-28', V{enrollment_date} )", location: 'feedback', programRuleActionType: 'DISPLAYKEYVALUEPAIR' }, - { id: 'Tx4gHcLselM', displayContent: 'd2:oizp( 10000000 ) = ', data: 'd2:oizp( 10000000 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'f3MrrcCf1z2', displayContent: 'd2:modulus( 12 , 100 ) = ', data: 'd2:modulus( 12 , 100 )', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'Eeb7Ixr4Pv6', displayContent: 'd2:floor((5+5) / 2) = ', data: 'd2:floor((5+5) / 2)', location: 'feedback', programRuleActionType: 'DISPLAYTEXT' }, - { id: 'nKNmayYigcy', programStageId: 'PUZaKR0Jh2k', programRuleActionType: 'HIDEPROGRAMSTAGE' }, - ], - }]; - const programRulesVariables = []; + const trackedEntityAttributes = { + w75KJ2mc4zz: { id: 'w75KJ2mc4zz', valueType: 'TEXT' }, + zDhUuAYrxNC: { id: 'zDhUuAYrxNC', valueType: 'TEXT' }, + cejWyOfXge6: { id: 'cejWyOfXge6', valueType: 'TEXT', optionSetId: 'pC3N9N77UmT' }, + lZGmxYbs97q: { id: 'lZGmxYbs97q', valueType: 'NUMBER' }, + Z5z8vFQy0w0: { id: 'Z5z8vFQy0w0', valueType: 'URL' }, + TzqawmlPkI5: { id: 'TzqawmlPkI5', valueType: 'AGE' }, + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'FILE_RESOURCE' }, + jBBkFuPKctq: { id: 'jBBkFuPKctq', valueType: 'ORGANISATION_UNIT' }, + A4Fg6jgWauf: { id: 'A4Fg6jgWauf', valueType: 'IMAGE' }, + CUbZcLm9LyN: { id: 'CUbZcLm9LyN', valueType: 'USERNAME' }, + p8htbyJHydl: { id: 'p8htbyJHydl', valueType: 'COORDINATE' }, + }; + const programRules = [ + { + id: 'g82J3xsNer9', + condition: 'true', + displayName: 'Testing the functions!', + programId: 'IpHINAT79UW', + programRuleActions: [ + { + id: 'Eeb7Ixr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lbIGJYVI57u', + displayContent: 'd2:zing( -2 ) = ', + data: 'd2:zing( -2 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'kwKhYpVRDyj', + displayContent: "d2:monthsBetween( '2020-01-28', V{enrollment_date}) = ", + data: "d2:monthsBetween( '2020-01-28', V{enrollment_date})", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'polhYpVRDyj', + displayContent: "d2:daysBetween( '2020-01-28', V{enrollment_date}) = ", + data: "d2:daysBetween( '2020-01-28', V{enrollment_date})", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FkeGdlkYAVr', + displayContent: "d2:validatePattern('d2:daysBetween( '2020-01-28', V{enrollment_date})', 108)", + data: "d2:validatePattern(d2:daysBetween( '2020-01-28', V{enrollment_date}), 108)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Foc3PhzoAVr', + displayContent: 'd2:count(#{undefinedVariable}) = ', + data: 'd2:count(#{unknow})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FjkwPhzoAVr', + displayContent: "d2:countIfValue(#{undefinedVariable}, 'Male') = ", + data: "d2:countIfValue(#{undefinedVariable}, 'Male') = ", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'TkgrHcLselM', + displayContent: 'd2:countIfZeroPos(100) = ', + data: 'd2:countIfZeroPos(100)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Foc3PhzoAVr', + displayContent: 'd2:hasValue(#{undefinedVariable})', + data: 'd2:hasValue(#{undefinedVariable})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdkeoAVr', + displayContent: "d2:validatePattern('Male', 'a')", + data: "d2:validatePattern('Male','a')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdlkYAVr', + displayContent: "d2:validatePattern('Male', 'Male')", + data: "d2:validatePattern('Male','Male')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'kpljdlkYAVr', + displayContent: 'd2:validatePattern()', + data: 'd2:validatePattern()', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdjkRAVr', + displayContent: "d2:hasUserRole('admin')", + data: "d2:hasUserRole('admin')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FoljdkLRAVr', + displayContent: "d2:extractDataMatrixValue('batch number', 3)", + data: "d2:extractDataMatrixValue('batch number', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FollkyLRAVr', + displayContent: "d2:extractDataMatrixValue('batch number', 'unknow')", + data: "d2:extractDataMatrixValue('batch number', 'unknow')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkEUdkLRAVr', + displayContent: "d2:extractDataMatrixValue('gtin', ']d2')", + data: "d2:extractDataMatrixValue('gtin', ']d2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FlkodkLRAVr', + displayContent: "d2:extractDataMatrixValue('batch number', ']d2')", + data: "d2:extractDataMatrixValue('batch number', ']d2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'kuYodkLRAVr', + displayContent: "d2:extractDataMatrixValue('production date', ']Q3unknown')", + data: "d2:extractDataMatrixValue('production date', ']Q3unknown')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'kuYookLRAVr', + displayContent: "d2:extractDataMatrixValue('lot number', ']Q3703')", + data: "d2:extractDataMatrixValue('lot number', ']Q3703')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyodkLRAVr', + displayContent: "d2:extractDataMatrixValue('best before date', ']Q3369')", + data: "d2:extractDataMatrixValue('best before date', ']Q3369')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkpyodkLRAVr', + displayContent: "d2:extractDataMatrixValue('expiration date', ']Q3369')", + data: "d2:extractDataMatrixValue('expiration date', ']Q3369')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyohgyRAVr', + displayContent: "d2:extractDataMatrixValue('serial number', ']Q3369')", + data: "d2:extractDataMatrixValue('serial number', ']Q3369')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyolktRAVr', + displayContent: "d2:extractDataMatrixValue('unknow key', ']d2')", + data: "d2:extractDataMatrixValue('unknow key', ']d2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FolpqkLRAVr', + displayContent: 'd2:lastEventDate(V{event_id})', + data: 'd2:lastEventDate(V{event_id})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FolkokLRAVr', + displayContent: 'd2:lastEventDate(V{enrollment_date})', + data: 'd2:lastEventDate(V{enrollment_date})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FolkwkLRAVr', + displayContent: 'd2:lastEventDate(#{undefinedVariable})', + data: 'd2:lastEventDate(#{undefinedVariable})', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'llokowfRAVr', + displayContent: "d2:addControlDigits('2')", + data: "d2:addControlDigits('2')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lloksfegwAVr', + displayContent: "d2:addControlDigits('7')", + data: "d2:addControlDigits('7')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'lkyksfegwAVr', + displayContent: "d2:addControlDigits('9')", + data: "d2:addControlDigits('9')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'llolkyfRAVr', + displayContent: "d2:addControlDigits('12345678912')", + data: "d2:addControlDigits('12345678912')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'FollowfRAVr', + displayContent: 'd2:checkControlDigits(1)', + data: 'd2:checkControlDigits(1)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'AFkfzcDf4Fs', + displayContent: "d2:inOrgUnitGroup('CHC') = ", + data: "d2:inOrgUnitGroup('CHC')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'wmAQnxbs7V8', + displayContent: 'd2:round( 12.5, 1 ) = ', + data: 'd2:round( 12.5, 1 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'tFGwyDBQk3b', + displayContent: 'd2:round( 0 ) = ', + data: 'd2:round( 0 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'NSQV537qvyu', + displayContent: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = ", + data: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking')", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'M7vYMD5uNwD', + displayContent: 'd2:ceil(11.3) = ', + data: 'd2:ceil(11.3)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'cgdUEJkqq0J', + displayContent: "d2:yearsBetween( '2010-01-28', V{enrollment_date}) = ", + data: "d2:yearsBetween( '2010-01-28', V{enrollment_date})", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'xZpYbFPXWG2', + displayContent: 'd2:zing( 1000 ) = ', + data: 'd2:zing( 1000 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'IGpzruAKVzk', + displayContent: "d2:split('these-are-testing-values', '-', 2) = ", + data: "d2:split('these-are-testing-values', '-', 2)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'NLsawa3P5hc', + displayContent: "d2:substring('hello dhis 2', -1, 10) = ", + data: "d2:substring('hello dhis 2', -1, 10)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'i0OgulFyVPQ', + displayContent: 'd2:oizp( -10000000 ) = ', + data: 'd2:oizp( -10000000 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'PqzKFmEMmuz', + displayContent: "d2:right('dhis', 3) = ", + data: "d2:right('dhis', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'xIUDr1lRV6N', + displayContent: "d2:addDays( '2018-04-20', 100 ) = ", + data: "d2:addDays( '2018-04-20', 100 )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'RXmprywJ0Rb', + displayContent: 'd2:floor( 11.5 ) = ', + data: 'd2:floor( 11.5 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'cZQngI2IC1a', + displayContent: "d2:length( 'dhis2 rocks' ) = ", + data: "d2:length( 'dhis2 rocks' )", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'qSe8GmlwpgZ', + displayContent: "d2:weeksBetween('2020-01-28', V{enrollment_date} ) = ", + data: "d2:weeksBetween('2020-01-28', V{enrollment_date} )", + location: 'feedback', + programRuleActionType: 'DISPLAYKEYVALUEPAIR', + }, + { + id: 'Tx4gHcLselM', + displayContent: 'd2:oizp( 10000000 ) = ', + data: 'd2:oizp( 10000000 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'f3MrrcCf1z2', + displayContent: 'd2:modulus( 12 , 100 ) = ', + data: 'd2:modulus( 12 , 100 )', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'Eeb7Ixr4Pv6', + displayContent: 'd2:floor((5+5) / 2) = ', + data: 'd2:floor((5+5) / 2)', + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { id: 'nKNmayYigcy', programStageId: 'PUZaKR0Jh2k', programRuleActionType: 'HIDEPROGRAMSTAGE' }, + { + id: 'nKNmayYigcy', + programStageSectionId: 'SWfdBhglX0fk', + programRuleActionType: 'HIDESECTION', + }, + { + id: 'lJOYxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'ASSIGN', + }, + { + id: 'lJOdxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'ERRORONCOMPLETE', + }, + { + id: 'ljlYxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'WARNINGONCOMPLETE', + }, + { + id: 'lljoxhjupxz', + data: 'true', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programRuleActionType: 'SETMANDATORYFIELD', + }, + { + id: 'lgjoxhjupxz', + data: 'true', + optionGroupId: 'w75KJ2mc4zz', + programRuleActionType: 'HIDEOPTIONGROUP', + }, + { + id: 'lljljhjupxz', + data: 'true', + optionGroupId: 'w75KJ2mc4zz', + programRuleActionType: 'SHOWOPTIONGROUP', + }, + { id: 'hwgyO59SSxu', trackedEntityAttributeId: 'zDhUuAYrxNC', programRuleActionType: 'HIDEFIELD' }, + { id: 'khygyO59Sxu', trackedEntityAttributeId: 'unknown', programRuleActionType: 'HIDEFIELD' }, + { id: 'khtgyO5SSxu', trackedEntityAttributeId: 'Z5z8vFQy0w0', programRuleActionType: 'HIDEFIELD' }, + { id: 'hwgyOgy4Sxu', trackedEntityAttributeId: 'TzqawmlPkI5', programRuleActionType: 'HIDEFIELD' }, + { id: 'hwgyloeSSxu', trackedEntityAttributeId: 'f8j4XDEozvj', programRuleActionType: 'HIDEFIELD' }, + { id: 'lkoeO59SSxu', trackedEntityAttributeId: 'jBBkFuPKctq', programRuleActionType: 'HIDEFIELD' }, + { id: 'poe4O59SSxu', trackedEntityAttributeId: 'A4Fg6jgWauf', programRuleActionType: 'HIDEFIELD' }, + { id: 'gtyyO59SSxu', trackedEntityAttributeId: 'CUbZcLm9LyN', programRuleActionType: 'HIDEFIELD' }, + { id: 'kopyO59SSxu', trackedEntityAttributeId: 'p8htbyJHydl', programRuleActionType: 'HIDEFIELD' }, + { id: 'dsfsdfsw4rh', dataElementId: 'unknown', programRuleActionType: 'HIDEFIELD' }, + { + id: 'khy8GmlwpgZ', + displayContent: "d2:weeksBetween('2020-01-28', V{unknown} ) = ", + data: "d2:weeksBetween('2020-01-28', V{unknown} )", + location: 'feedback', + programRuleActionType: 'DISPLAYKEYVALUEPAIR', + }, + ], + }, + ]; + const programRuleVariables = [ + { + id: 'DoRHHfNPccb', + trackedEntityAttributeId: 'w75KJ2mc4zz', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: false, + }, + { + id: 'lokHHfNPccb', + trackedEntityAttributeId: 'w75KJ2mc4zz', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: false, + }, + { + id: 'Zj7UnCAulEk', + displayName: 'Zj7UnCAulEk', + programRuleVariableSourceType: variableSourceTypes.TEI_ATTRIBUTE, + trackedEntityAttributeId: 'w75KJ2mc4zz', + programId: 'IpHINAT79UW', + }, + ]; const optionSets = {}; - const teiValues = {}; + const teiValues = { + zDhUuAYrxNC: 'value', + Z5z8vFQy0w0: 'https://www.google.com/', + TzqawmlPkI5: '30', + f8j4XDEozvj: 'FILE_RESOURCE', + jBBkFuPKctq: 'ORGANISATION_UNIT', + A4Fg6jgWauf: 'IMAGE', + CUbZcLm9LyN: 'USERNAME', + p8htbyJHydl: { latitude: '12.4353', longitude: '67.34534' }, + }; const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; - const enrollmentData = { enrolledAt: '2020-05-14T22:00:00.000Z' }; + const enrollmentData = { enrolledAt: '2020-05-14T10:00:00.000Z' }; + const currentEvent = { + occurredAt: '2020-07-14T10:00:00.000Z', + da1Id: 'currentEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + }; // when const rulesEffects = rulesEngine.getProgramRuleEffects({ - programRulesContainer: { programRulesVariables, programRules, constants }, + programRulesContainer: { programRuleVariables, programRules, constants }, trackedEntityAttributes, selectedEntity: teiValues, selectedEnrollment: enrollmentData, selectedOrgUnit: orgUnit, optionSets, + currentEvent, }); // then expect(rulesEffects).toEqual([ - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'lbIGJYVI57u', message: 'd2:zing( -2 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'kwKhYpVRDyj', message: "d2:monthsBetween( '2020-01-28', V{enrollment_date}) = 3" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'AFkfzcDf4Fs', message: "d2:inOrgUnitGroup('CHC') = " } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'wmAQnxbs7V8', message: 'd2:round( 12.5 ) = 13' } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'kwKhYpVRDyj', message: "d2:monthsBetween( '2020-01-28', V{enrollment_date}) = 3" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'polhYpVRDyj', message: "d2:daysBetween( '2020-01-28', V{enrollment_date}) = 107" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'FkeGdlkYAVr', + message: "d2:validatePattern('d2:daysBetween( '2020-01-28', V{enrollment_date})', 108) ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Foc3PhzoAVr', message: 'd2:count(#{undefinedVariable}) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FjkwPhzoAVr', message: "d2:countIfValue(#{undefinedVariable}, 'Male') = " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'TkgrHcLselM', message: 'd2:countIfZeroPos(100) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Foc3PhzoAVr', message: 'd2:hasValue(#{undefinedVariable}) ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FoljdkeoAVr', message: "d2:validatePattern('Male', 'a') " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FoljdlkYAVr', message: "d2:validatePattern('Male', 'Male') true" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'kpljdlkYAVr', message: 'd2:validatePattern() ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FoljdjkRAVr', message: "d2:hasUserRole('admin') " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'FoljdkLRAVr', + message: "d2:extractDataMatrixValue('batch number', 3) Incomplete DataMatrix input", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'FollkyLRAVr', + message: + "d2:extractDataMatrixValue('batch number', 'unknow') Unsupported GS1 identifier: {gs1Identifier}", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkEUdkLRAVr', + message: "d2:extractDataMatrixValue('gtin', ']d2') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'FlkodkLRAVr', + message: "d2:extractDataMatrixValue('batch number', ']d2') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'kuYodkLRAVr', + message: "d2:extractDataMatrixValue('production date', ']Q3unknown') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'kuYookLRAVr', + message: "d2:extractDataMatrixValue('lot number', ']Q3703') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkyodkLRAVr', + message: "d2:extractDataMatrixValue('best before date', ']Q3369') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkpyodkLRAVr', + message: "d2:extractDataMatrixValue('expiration date', ']Q3369') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkyohgyRAVr', + message: "d2:extractDataMatrixValue('serial number', ']Q3369') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'lkyolktRAVr', + message: "d2:extractDataMatrixValue('unknow key', ']d2') ", + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FolpqkLRAVr', message: 'd2:lastEventDate(V{event_id}) 2020-07-14' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FolkokLRAVr', message: 'd2:lastEventDate(V{enrollment_date}) ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FolkwkLRAVr', message: 'd2:lastEventDate(#{undefinedVariable}) ' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'llokowfRAVr', message: "d2:addControlDigits('2') 261" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'lloksfegwAVr', message: "d2:addControlDigits('7') 709" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'lkyksfegwAVr', message: "d2:addControlDigits('9') 950" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'llolkyfRAVr', message: "d2:addControlDigits('12345678912') 1234567891200" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'FollowfRAVr', message: 'd2:checkControlDigits(1) 1' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'AFkfzcDf4Fs', message: "d2:inOrgUnitGroup('CHC') = " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'wmAQnxbs7V8', message: 'd2:round( 12.5, 1 ) = 12.5' }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'tFGwyDBQk3b', message: 'd2:round( 0 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NSQV537qvyu', message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking" } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { + id: 'NSQV537qvyu', + message: "d2:concatenate( 'dh', 'is', 2, 'is', 'rocking') = dhis2isrocking", + }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'M7vYMD5uNwD', message: 'd2:ceil(11.3) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'cgdUEJkqq0J', message: "d2:yearsBetween( '2010-01-28', V{enrollment_date}) = 10" } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'cgdUEJkqq0J', message: "d2:yearsBetween( '2010-01-28', V{enrollment_date}) = 10" }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'xZpYbFPXWG2', message: 'd2:zing( 1000 ) = 1000' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'IGpzruAKVzk', message: "d2:split('these-are-testing-values', '-', 2) = testing" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'NLsawa3P5hc', message: "d2:substring('hello dhis 2', 6, 10) = dhis" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'i0OgulFyVPQ', message: 'd2:oizp( -10000000 ) = 0' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'PqzKFmEMmuz', message: "d2:right('dhis', 3) = his" } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'xIUDr1lRV6N', message: "d2:addDays( '2018-04-20', 100 ) = 2018-07-29" } }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'IGpzruAKVzk', message: "d2:split('these-are-testing-values', '-', 2) = testing" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'NLsawa3P5hc', message: "d2:substring('hello dhis 2', -1, 10) = " }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'i0OgulFyVPQ', message: 'd2:oizp( -10000000 ) = 0' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'PqzKFmEMmuz', message: "d2:right('dhis', 3) = his" }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'xIUDr1lRV6N', message: "d2:addDays( '2018-04-20', 100 ) = 2018-07-29" }, + }, { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'RXmprywJ0Rb', message: 'd2:floor( 11.5 ) = 11' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'cZQngI2IC1a', message: "d2:length( 'dhis2 rocks' ) = 11" } }, - { type: 'DISPLAYKEYVALUEPAIR', id: 'feedback', displayKeyValuePair: { id: 'qSe8GmlwpgZ', key: "d2:weeksBetween('2020-01-28', V{enrollment_date} ) = ", value: '15' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Tx4gHcLselM', message: 'd2:oizp( 10000000 ) = 1' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'f3MrrcCf1z2', message: 'd2:modulus( 12 , 100 ) = 12' } }, - { type: 'DISPLAYTEXT', id: 'feedback', displayText: { id: 'Eeb7Ixr4Pv6', message: 'd2:floor((5+5) / 2) = 5' } }, // check double parentheses + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'cZQngI2IC1a', message: "d2:length( 'dhis2 rocks' ) = 11" }, + }, + { + type: 'DISPLAYKEYVALUEPAIR', + id: 'feedback', + displayKeyValuePair: { + id: 'qSe8GmlwpgZ', + key: "d2:weeksBetween('2020-01-28', V{enrollment_date} ) = ", + value: '15', + }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Tx4gHcLselM', message: 'd2:oizp( 10000000 ) = 1' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'f3MrrcCf1z2', message: 'd2:modulus( 12 , 100 ) = 12' }, + }, + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pv6', message: 'd2:floor((5+5) / 2) = 5' }, + }, { type: 'HIDEPROGRAMSTAGE', id: 'PUZaKR0Jh2k' }, + { id: 'SWfdBhglX0fk', type: 'HIDESECTION' }, + { id: 'w75KJ2mc4zz', targetDataType: 'trackedEntityAttribute', type: 'ASSIGN', value: 'true' }, + { + id: 'w75KJ2mc4zz', + message: ' true', + targetDataType: 'trackedEntityAttribute', + type: 'ERRORONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + message: ' true', + targetDataType: 'trackedEntityAttribute', + type: 'WARNINGONCOMPLETE', + }, + { + id: 'w75KJ2mc4zz', + targetDataType: 'trackedEntityAttribute', + type: 'SETMANDATORYFIELD', + }, + { + content: undefined, + id: 'zDhUuAYrxNC', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + hadValue: true, + name: undefined, + }, + { + content: undefined, + id: 'unknown', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + }, + { + content: undefined, + id: 'Z5z8vFQy0w0', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + hadValue: true, + name: undefined, + }, + { + content: undefined, + id: 'TzqawmlPkI5', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + hadValue: true, + name: undefined, + }, + { + content: undefined, + id: 'f8j4XDEozvj', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + }, + { + content: undefined, + id: 'jBBkFuPKctq', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + }, + { + content: undefined, + id: 'A4Fg6jgWauf', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + }, + { + content: undefined, + id: 'CUbZcLm9LyN', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + hadValue: true, + name: undefined, + }, + { + content: undefined, + id: 'p8htbyJHydl', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + hadValue: true, + name: undefined, + }, + { + content: undefined, + id: 'unknown', + targetDataType: 'dataElement', + type: 'HIDEFIELD', + }, + { + displayKeyValuePair: { + id: 'khy8GmlwpgZ', + key: "d2:weeksBetween('2020-01-28', V{unknown} ) = ", + value: '', + }, + id: 'feedback', + type: 'DISPLAYKEYVALUEPAIR', + }, ]); }); diff --git a/src/core_modules/capture-core/rules/__tests__/rulesEngine.test.js b/src/core_modules/capture-core/rules/__tests__/rulesEngine.test.js new file mode 100644 index 0000000000..9aebeadd75 --- /dev/null +++ b/src/core_modules/capture-core/rules/__tests__/rulesEngine.test.js @@ -0,0 +1,767 @@ +import { variableSourceTypes } from '@dhis2/rules-engine-javascript'; +import { rulesEngine } from '../rulesEngine'; + +describe('Rules engine', () => { + const constants = []; + const dataElementsInProgram = {}; + const programRuleVariables = []; + const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; + const optionSets = {}; + const currentEvent = {}; + + test('Rules engine without programRules', () => { + // when + const programRules = undefined; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('Program rule without an condition', () => { + // when + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: undefined, + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'suS9GnraCx1', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + dataElementId: 'vANAXwtLwcT', + programRuleActionType: 'SHOWWARNING', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('Program rule condition error handeling', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'i am a condition with error', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'suS9GnraCx1', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + dataElementId: 'vANAXwtLwcT', + programRuleActionType: 'SHOWWARNING', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('user roles', () => { + rulesEngine.setSelectedUserRoles(['ADMIN']); + expect(rulesEngine.userRoles).toEqual(['ADMIN']); + }); + + test('SHOW_WARNING program rule effect with a general target', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'SWfdB5lX0fk', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + programRuleActionType: 'SHOWWARNING', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { + id: 'general', + type: 'SHOWWARNING', + warning: { + id: 'SWfdB5lX0fk', + message: 'Hemoglobin value lower than normal ', + }, + }, + ]); + }); + + test('SHOW_ERROR program rule effect with a general target', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show error if hemoglobin is dangerously low', + displayName: 'Hemoglobin error', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'SWfdB5lX0fk', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + programRuleActionType: 'SHOWERROR', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { + id: 'general', + type: 'SHOWERROR', + error: { + id: 'SWfdB5lX0fk', + message: 'Hemoglobin value lower than normal ', + }, + }, + ]); + }); + + test('HIDE_PROGRAM_STAGE program rule effect corner case. The action does not have a program stage id', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'nKNmayYigcy', + programRuleActionType: 'HIDEPROGRAMSTAGE', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('HIDE_SECTION program rule effect corner case. The action does not have a program stage section id', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show warning if hemoglobin is dangerously low', + displayName: 'Hemoglobin warning', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'nKNmayYigcy', + programRuleActionType: 'HIDESECTION', + }, + ], + }, + ]; + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('The rules engine can enable verbose logging', () => { + // When + rulesEngine.setFlags({ verbose: true }); + + // Then + expect(rulesEngine.getFlags()).toEqual({ verbose: true }); + }); + + test('Rules are calculated when verbose is set', () => { + const programRules = [ + { + id: 'GC4gpdoSD4r', + condition: 'true', + description: 'Show error if hemoglobin is dangerously low', + displayName: 'Hemoglobin error', + programId: 'lxAQ7Zs9VYR', + programRuleActions: [ + { + id: 'SWfdB5lX0fk', + content: 'Hemoglobin value lower than normal', + displayContent: 'Hemoglobin value lower than normal', + programRuleActionType: 'SHOWERROR', + }, + ], + }, + ]; + + // When + rulesEngine.setFlags({ verbose: true }); + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + currentEvent, + dataElements: dataElementsInProgram, + selectedOrgUnit: orgUnit, + optionSets, + }); + + // then + expect(rulesEffects).toEqual([ + { + id: 'general', + type: 'SHOWERROR', + error: { + id: 'SWfdB5lX0fk', + message: 'Hemoglobin value lower than normal ', + }, + }, + ]); + }); +}); + +describe('Program Rule Variables corner cases', () => { + const constants = []; + const orgUnit = { id: 'DiszpKrYNg8', name: 'Ngelehun CHC' }; + const optionSets = {}; + + test('without currentEvent and without otherEvents', () => { + // given + const programRules = [ + { + id: 'g82J3xsNer9', + condition: 'true', + displayName: 'Testing the variables source type', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'Eeb7Ixr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'ElktIxr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + content: 'event_status', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ]; + const dataElementsInProgram = { + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'INTEGER', optionSetId: undefined }, + }; + const currentEvent = undefined; + const programRuleVariables = [ + { + id: 'DoRHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: false, + }, + { + id: 'lokHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_PREVIOUS_EVENT, + useNameForOptionSet: false, + }, + { + id: 'DolgHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM, + useNameForOptionSet: false, + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + dataElements: dataElementsInProgram, + }); + + // then + expect(rulesEffects).toEqual([ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, + ]); + }); + + test('without currentEvent and with otherEvents', () => { + // given + const programRules = [ + { + id: 'g82J3xsNer9', + condition: 'true', + displayName: 'Testing the variables source type', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'Eeb7Ixr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'ElktIxr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + content: 'event_status', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ]; + const dataElementsInProgram = { + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'INTEGER', optionSetId: undefined }, + }; + const currentEvent = undefined; + const otherEvents = [ + { + da1Id: 'otherEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + occurredAt: '2021-05-31T00:00:00.000', + }, + ]; + const programRuleVariables = [ + { + id: 'lokHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'PNClHaZARtz', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_PREVIOUS_EVENT, + useNameForOptionSet: false, + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + dataElements: dataElementsInProgram, + otherEvents, + }); + + // then + expect(rulesEffects).toEqual([ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, + ]); + }); + + test('with currentEvent and with otherEvents', () => { + // given + const programRules = [ + { + id: 'g82J3xsNer9', + condition: 'true', + displayName: 'Testing the variables source type', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'Eeb7Ixr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'ElktIxr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + content: 'event_status', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ]; + const dataElementsInProgram = { + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'INTEGER', optionSetId: undefined }, + GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER', optionSetId: undefined }, + }; + const currentEvent = { + occurredAt: '2020-07-14T22:00:00.000Z', + da1Id: 'currentEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BlgrDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCsfsAWG17I', + }; + const otherEvents = [ + { + da1Id: 'otherEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + occurredAt: '2021-05-31T00:00:00.000', + }, + ]; + const programRuleVariables = [ + { + id: 'lokHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_PREVIOUS_EVENT, + useNameForOptionSet: false, + }, + { + id: 'ZghUnCAulEk.GieVkTxp4HH', + displayName: 'ZghUnCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE, + programStageId: 'A03MvHHogjR', + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + { + id: 'Zj7UnsdhlEk.GieVkTxp4HH', + displayName: 'Zj7UnsdhlEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE, + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + { + id: 'Zj7luCAulEk.GieVkTxp4HH', + displayName: 'Zj7luCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE, + programStageId: 'AsdfMvHHgjR', + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + dataElements: dataElementsInProgram, + otherEvents, + }); + + // then + expect(rulesEffects).toEqual([ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, + ]); + }); + + test('currentEvent without occurredAt date', () => { + // given + const programRules = [ + { + id: 'g82J3xsNer9', + condition: 'true', + displayName: 'Testing the variables source type', + programId: 'PNClHaZARtz', + programRuleActions: [ + { + id: 'Eeb7Ixr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + location: 'feedback', + programRuleActionType: 'DISPLAYTEXT', + }, + { + id: 'ElktIxr4Pvx', + displayContent: "d2:left('dhis', 3) = ", + data: "d2:left('dhis', 3)", + content: 'event_status', + programRuleActionType: 'ASSIGN', + }, + ], + }, + ]; + const dataElementsInProgram = { + f8j4XDEozvj: { id: 'f8j4XDEozvj', valueType: 'INTEGER', optionSetId: undefined }, + GieVkTxp4HH: { id: 'GieVkTxp4HH', valueType: 'NUMBER', optionSetId: undefined }, + }; + const currentEvent = { + da1Id: 'currentEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BlgrDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCsfsAWG17I', + }; + const otherEvents = [ + { + da1Id: 'otherEventText', + dueDate: '2021-05-31T09:51:38.134', + enrollmentId: 'vVtmDlsu3me', + enrollmentStatus: 'ACTIVE', + eventDate: '2021-05-31T00:00:00.000', + eventId: 'BxGzDJK3JqN', + orgUnitId: 'DiszpKrYNg8', + orgUnitName: 'Ngelehun CHC', + programId: 'IpHINAT79UW', + programStageId: 'A03MvHHogjR', + status: 'ACTIVE', + trackedEntityInstanceId: 'vCGpQAWG17I', + occurredAt: '2021-05-31T00:00:00.000', + }, + ]; + const programRuleVariables = [ + { + id: 'lokHHfNPccb', + dataElementId: 'f8j4XDEozvj', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_PREVIOUS_EVENT, + useNameForOptionSet: false, + }, + { + id: 'ZghUnCAulEk.GieVkTxp4HH', + displayName: 'ZghUnCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE, + programStageId: 'A03MvHHogjR', + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + { + id: 'Zj7UnsdhlEk.GieVkTxp4HH', + displayName: 'Zj7UnsdhlEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE, + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + { + id: 'Zj7luCAulEk.GieVkTxp4HH', + displayName: 'Zj7luCAulEk.GieVkTxp4HH', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_NEWEST_EVENT_PROGRAM_STAGE, + programStageId: 'AsdfMvHHgjR', + dataElementId: 'GieVkTxp4HH', + programId: 'IpHINAT79UW', + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + dataElements: dataElementsInProgram, + otherEvents, + }); + + // then + expect(rulesEffects).toEqual([ + { + type: 'DISPLAYTEXT', + id: 'feedback', + displayText: { id: 'Eeb7Ixr4Pvx', message: "d2:left('dhis', 3) = dhi" }, + }, + ]); + }); + + test('programRuleVariable with a value type not supported', () => { + // given + const trackedEntityAttributes = { + w75KJ2mc4zz: { id: 'w75KJ2mc4zz', valueType: 'UNKNOWN' }, + }; + const programRules = []; + const currentEvent = {}; + const programRuleVariables = [ + { + id: 'DoRHHfNPccb', + displayName: 'INFECTION_SOURCE', + trackedEntityAttributeId: 'w75KJ2mc4zz', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: variableSourceTypes.TEI_ATTRIBUTE, + useNameForOptionSet: false, + }, + ]; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + trackedEntityAttributes, + }); + + // then + expect(rulesEffects).toEqual([]); + }); + + test('HIDEFIELD effect when the form values are empty', () => { + // given + const programRules = [ + { + id: 'g82J3xsNer9', + condition: 'true', + displayName: 'Testing the variables source type', + programId: 'PNClHaZARtz', + programRuleActions: [ + { id: 'hwgyO59SSxu', trackedEntityAttributeId: 'zDhUuAYrxNC', programRuleActionType: 'HIDEFIELD' }, + ], + }, + ]; + const trackedEntityAttributes = { + zDhUuAYrxNC: { id: 'zDhUuAYrxNC', valueType: 'TEXT' }, + }; + const programRuleVariables = [ + { + id: 'DoRHHfNPccb', + trackedEntityAttributeId: 'w75KJ2mc4zz', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: false, + }, + { + id: 'lokHHfNPccb', + trackedEntityAttributeId: 'w75KJ2mc4zz', + displayName: 'INFECTION_SOURCE', + programId: 'IpHINAT79UW', + programRuleVariableSourceType: variableSourceTypes.DATAELEMENT_CURRENT_EVENT, + useNameForOptionSet: false, + }, + { + id: 'Zj7UnCAulEk', + displayName: 'Zj7UnCAulEk', + programRuleVariableSourceType: variableSourceTypes.TEI_ATTRIBUTE, + trackedEntityAttributeId: 'w75KJ2mc4zz', + programId: 'IpHINAT79UW', + }, + ]; + const teiValues = {}; + const enrollmentData = { enrolledAt: '2020-05-14T22:00:00.000Z' }; + const currentEvent = undefined; + + // when + const rulesEffects = rulesEngine.getProgramRuleEffects({ + programRulesContainer: { programRuleVariables, programRules, constants }, + trackedEntityAttributes, + selectedEntity: teiValues, + selectedEnrollment: enrollmentData, + selectedOrgUnit: orgUnit, + optionSets, + currentEvent, + }); + + // then + expect(rulesEffects).toEqual([ + { + content: undefined, + id: 'zDhUuAYrxNC', + targetDataType: 'trackedEntityAttribute', + type: 'HIDEFIELD', + }, + ]); + }); +}); diff --git a/src/core_modules/capture-core/rules/converters/inputConverter.js b/src/core_modules/capture-core/rules/converters/inputConverter.js index 7fdc5c1cad..98556e68d7 100644 --- a/src/core_modules/capture-core/rules/converters/inputConverter.js +++ b/src/core_modules/capture-core/rules/converters/inputConverter.js @@ -8,6 +8,7 @@ const dateMomentFormat = 'YYYY-MM-DD'; const convertStringValue = (value: ?string): ?string => (value || null); const convertNumericValue = (value: any): ?number => (typeof value === 'number' ? value : null); +const convertObjectToString = (value: ?{ name: string }) => (value ? value.name : null); export const inputConverter: IConvertInputRulesValue = { convertText: convertStringValue, @@ -38,24 +39,12 @@ export const inputConverter: IConvertInputRulesValue = { log.warn('convertTrackerAssociate not implemented', value); return null; }, - convertUserName: (value: any): ?string => { - log.warn('convertUserName not implemented', value); - return null; - }, + convertUserName: convertStringValue, convertCoordinate: (value: any): ?string => ( (value && value.latitude && value.longitude) ? `[${value.latitude},${value.longitude}]` : null), - convertOrganisationUnit: (value: any): ?string => { - log.warn('convertOrganisationUnit not implemented', value); - return null; - }, + convertOrganisationUnit: convertObjectToString, convertAge: (value: any): ?string => inputConverter.convertDate(value), convertUrl: convertStringValue, - convertFile(value: any): ?string { - log.warn('convertFile not implemented', value); - return null; - }, - convertImage(value: any): ?string { - log.warn('convertImage not implemented', value); - return null; - }, + convertFile: convertObjectToString, + convertImage: convertObjectToString, }; diff --git a/src/core_modules/capture-core/rules/converters/outputConverter.js b/src/core_modules/capture-core/rules/converters/outputConverter.js index b50efc187c..6d9ae5e2dc 100644 --- a/src/core_modules/capture-core/rules/converters/outputConverter.js +++ b/src/core_modules/capture-core/rules/converters/outputConverter.js @@ -5,6 +5,9 @@ import moment from 'moment'; import type { IConvertOutputRulesEffectsValue } from '@dhis2/rules-engine-javascript'; import { convertMomentToDateFormatString } from '../../utils/converters/date'; + +// These functions are only used for creating assignment effects + const dateMomentFormat = 'YYYY-MM-DD'; export const outputConverter: IConvertOutputRulesEffectsValue = { diff --git a/src/core_modules/capture-core/rules/rulesEngine.js b/src/core_modules/capture-core/rules/rulesEngine.js index 06983888c3..dc91f9f957 100644 --- a/src/core_modules/capture-core/rules/rulesEngine.js +++ b/src/core_modules/capture-core/rules/rulesEngine.js @@ -6,4 +6,9 @@ import { dateUtils, } from './converters'; -export const rulesEngine = new RulesEngine(inputConverter, outputConverter, dateUtils, environmentTypes.WebClient); +export const rulesEngine = new RulesEngine( + inputConverter, + outputConverter, + dateUtils, + environmentTypes.WebClient, +); diff --git a/src/core_modules/capture-core/rules/useRuleEngineFlags.js b/src/core_modules/capture-core/rules/useRuleEngineFlags.js new file mode 100644 index 0000000000..ee6cd84c40 --- /dev/null +++ b/src/core_modules/capture-core/rules/useRuleEngineFlags.js @@ -0,0 +1,23 @@ +// @flow +import { useLayoutEffect } from 'react'; +import { useLocationQuery } from '../utils/routing'; +import { rulesEngine } from './rulesEngine'; + +export const useRuleEngineFlags = () => { + // This hook is used to set the verbose flag on the rules engine + // based on the verbose query param in the URL + + const { verbose } = useLocationQuery(); + + const updateFlags = (flags) => { + rulesEngine.setFlags({ ...rulesEngine.getFlags(), ...flags }); + }; + + useLayoutEffect(() => { + if (verbose === 'true') { + updateFlags({ verbose: true }); + } else { + updateFlags({ verbose: false }); + } + }, [verbose]); +}; diff --git a/src/core_modules/capture-core/storageControllers/cache.types.js b/src/core_modules/capture-core/storageControllers/cache.types.js index 49ce4a64ca..01ba461309 100644 --- a/src/core_modules/capture-core/storageControllers/cache.types.js +++ b/src/core_modules/capture-core/storageControllers/cache.types.js @@ -26,6 +26,7 @@ export type CachedTrackedEntityAttribute = { translations: Array, valueType: string, optionSetValue: boolean, + inherit: boolean, optionSet: { id: string }, unique: ?boolean, orgunitScope: ?boolean, diff --git a/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js b/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js index 8ab794914e..6b0c916a73 100644 --- a/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js +++ b/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js @@ -6,7 +6,7 @@ import type { Result } from './useMetadataQuery.types'; import { ReactQueryAppNamespace } from '../reactQueryHelpers.const'; export const useApiDataQuery = ( - queryKey: Array, + queryKey: Array, queryObject: ResourceQuery, queryOptions: UseQueryOptions, ): Result => { diff --git a/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js b/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js index 48eecdaca7..568cb0cedc 100644 --- a/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js +++ b/src/core_modules/capture-ui/CoordinateField/CoordinateField.component.js @@ -4,7 +4,7 @@ import classNames from 'classnames'; import i18n from '@dhis2/d2-i18n'; import { Map, TileLayer, Marker, withLeaflet } from 'react-leaflet'; import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; -import { IconCross24, Button } from '@dhis2/ui'; +import { IconCross24, Button, ModalActions, ModalContent } from '@dhis2/ui'; import { IconButton } from 'capture-ui'; import { AddLocationIcon } from '../Icons'; import { CoordinateInput } from '../internal/CoordinateInput/CoordinateInput.component'; @@ -56,6 +56,13 @@ export class CoordinateField extends React.Component { }; } + componentDidUpdate() { + // Invalidate map size to fix rendering bug + if (this.mapInstance && this.state.showMap) { + this.mapInstance.leafletElement.invalidateSize(); + } + } + toSixDecimal = (value: string) => (parseFloat(value) ? parseFloat(value).toFixed(6) : null) handleBlur = (key: string, value: any) => { @@ -139,13 +146,15 @@ export class CoordinateField extends React.Component { const clonedDialog = React.cloneElement( this.props.mapDialog, - { open: this.state.showMap, onClose: this.closeMap }, + { hide: !this.state.showMap, onClose: this.closeMap }, [...React.Children.toArray(this.props.mapDialog.props.children), ( -
- {this.renderMap()} + <> + + {this.renderMap()} + {this.renderDialogActions()} -
+ )], ); return clonedDialog; @@ -185,7 +194,7 @@ export class CoordinateField extends React.Component { } renderDialogActions = () => ( -
+
{/* $FlowFixMe[prop-missing] automated comment */}
-
+ ); renderLatitude = () => { diff --git a/src/core_modules/capture-ui/CoordinateField/coordinateField.module.css b/src/core_modules/capture-ui/CoordinateField/coordinateField.module.css index d73c64bcf2..9f5709617c 100644 --- a/src/core_modules/capture-ui/CoordinateField/coordinateField.module.css +++ b/src/core_modules/capture-ui/CoordinateField/coordinateField.module.css @@ -3,7 +3,7 @@ display: flex; flex-direction: column; } - + .coordinateFieldsHorizontal { position: relative; display: flex; @@ -64,6 +64,8 @@ .mapContainer { flex-grow: 1; position: relative; + height: 100%; + width: 100%; } .leafletContainer { @@ -73,13 +75,11 @@ } .dialogContent { - flex-grow: 1; display: flex; + height: 600px; flex-direction: column; - padding: 0px 24px 24px 24px; } .buttonsContainerVertical { display: flex; } - \ No newline at end of file diff --git a/src/core_modules/capture-ui/PolygonField/PolygonField.component.js b/src/core_modules/capture-ui/PolygonField/PolygonField.component.js index fd2bfe043a..7ef585a16c 100644 --- a/src/core_modules/capture-ui/PolygonField/PolygonField.component.js +++ b/src/core_modules/capture-ui/PolygonField/PolygonField.component.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; import i18n from '@dhis2/d2-i18n'; -import { IconCheckmark16, IconLocation16, colors, Button } from '@dhis2/ui'; +import { IconCheckmark16, IconLocation16, colors, Button, ModalContent, ModalActions } from '@dhis2/ui'; import L from 'leaflet'; import { Map, TileLayer, FeatureGroup, withLeaflet } from 'react-leaflet'; import { ReactLeafletSearch } from 'react-leaflet-search-unpolyfilled'; @@ -62,6 +62,8 @@ function coordsToFeatureCollection(coordinates): ?FeatureCollection { } export class PolygonField extends React.Component { + mapInstance: ?any; + static defaultProps = { mapCenter: [51.505, -0.09], } @@ -75,6 +77,13 @@ export class PolygonField extends React.Component { }; } + componentDidUpdate() { + // Invalidate map size to fix rendering bug + if (this.mapInstance && this.state.showMap) { + this.mapInstance.leafletElement.invalidateSize(); + } + } + onFeatureGroupReady = (reactFGref: any, featureCollection: ?FeatureCollection) => { if (featureCollection) { const leafletGeoJSON = new L.GeoJSON(featureCollection); @@ -134,19 +143,25 @@ export class PolygonField extends React.Component { this.closeMap(); } + setMapInstance = (mapInstance: any) => { + this.mapInstance = mapInstance; + } + renderMapDialog = () => { const clonedDialog = React.cloneElement( // $FlowFixMe[incompatible-type] automated comment this.props.mapDialog, - { open: this.state.showMap, onClose: this.closeMap }, + { hide: !this.state.showMap, onClose: this.closeMap }, // $FlowFixMe[incompatible-use] automated comment [...React.Children.toArray(this.props.mapDialog.props.children), ( -
- {this.renderMap()} + <> + + {this.renderMap()} + {this.renderDialogActions()} -
+ )], ); return clonedDialog; @@ -159,7 +174,13 @@ export class PolygonField extends React.Component { const center = this.getCenter(featureCollection); return (
- + { this.setMapInstance(mapInstance); }} + > { } renderDialogActions = () => ( -
+
{/* $FlowFixMe[prop-missing] automated comment */}
-
+ ); render() { diff --git a/src/core_modules/capture-ui/PolygonField/polygonField.module.css b/src/core_modules/capture-ui/PolygonField/polygonField.module.css index 3aa0c0454f..f4fa6c640e 100644 --- a/src/core_modules/capture-ui/PolygonField/polygonField.module.css +++ b/src/core_modules/capture-ui/PolygonField/polygonField.module.css @@ -21,6 +21,8 @@ .mapContainer { flex-grow: 1; position: relative; + width: 100%; + height: 100%; } .map { @@ -30,9 +32,8 @@ } .dialogContent { - flex-grow: 1; display: flex; flex-direction: column; - padding: 0px 24px 24px 24px; + height: 600px; } diff --git a/src/core_modules/capture-ui/SelectionBoxes/SelectionBoxes.component.js b/src/core_modules/capture-ui/SelectionBoxes/SelectionBoxes.component.js index 5ffa6d5d36..73fa068b3c 100644 --- a/src/core_modules/capture-ui/SelectionBoxes/SelectionBoxes.component.js +++ b/src/core_modules/capture-ui/SelectionBoxes/SelectionBoxes.component.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { MultiSelectionsBoxes } from '../internal/SelectionBoxes/MultiSelectionsBoxes/MultiSelectionsBoxes.component'; import { SingleSelectionBoxes } from '../internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectionBoxes.component'; +import { withKeyboardNavigation } from '../internal/SelectionBoxes/withKeyboardNavigation'; import { orientations } from '../constants/orientations.const'; @@ -10,7 +11,7 @@ type Props = { multiSelect?: ?boolean, }; -export const SelectionBoxes = (props: Props) => { +const SelectionBoxesPlain = (props: Props) => { const { multiSelect, ...passOnProps } = props; if (multiSelect) { @@ -31,3 +32,5 @@ export const SelectionBoxes = (props: Props) => { /> ); }; + +export const SelectionBoxes = withKeyboardNavigation()(SelectionBoxesPlain); diff --git a/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/SingleSelectBox.component.js b/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/SingleSelectBox.component.js index 0831a04c34..6b660a2481 100644 --- a/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/SingleSelectBox.component.js +++ b/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/SingleSelectBox.component.js @@ -3,6 +3,7 @@ import * as React from 'react'; import classNames from 'classnames'; import defaultClasses from './singleSelectBox.module.css'; import type { OptionRendererInputData } from '../../selectBoxes.types'; +import type { KeyboardManager } from '../../../../internal/SelectionBoxes/withKeyboardNavigation'; type Props = { optionData: OptionRendererInputData, @@ -10,47 +11,73 @@ type Props = { groupId: string, children: React.Node, onSelect: (value: any) => void, + onBlur: (event: SyntheticFocusEvent, value: any) => void, inputRef?: (instance: ?HTMLInputElement) => void, inFocus?: ?boolean, focusClass?: string, unFocusClass?: string, + keyboardManager: KeyboardManager, disabled?: ?boolean, }; -const keyboardKeys = { +const keys = { + TAB: 'Tab', SPACE: ' ', ENTER: 'Enter', + ARROW_LEFT: 'ArrowLeft', + ARROW_RIGHT: 'ArrowRight', + ARROW_UP: 'ArrowUp', + ARROW_DOWN: 'ArrowDown', }; +// Pressing space triggers an automatic onClick event. +// We do not want this event to be processed, as we already do all required work in the keyboard event. +// This ignoreFlag variable makes it possible to determine if a given onClick event is of automatic origin. +let ignoreFlag = false; + export class SingleSelectBox extends React.Component { - isSpaceClickWhenSelected: ?boolean; // Pressing space when the radio is already selected, triggers both onKeyPress and onClick. This variable is used to prevent the onClick event in these circumstances. handleSelect = () => { - if (this.isSpaceClickWhenSelected) { - this.isSpaceClickWhenSelected = false; - return; - } - const { onSelect, optionData, isSelected } = this.props; - if (isSelected) { - onSelect(null); - return; + if (ignoreFlag) { + ignoreFlag = false; + } else { + onSelect(isSelected ? null : optionData.value); } - onSelect(optionData.value); } - handleKeyPress = (event: SyntheticKeyboardEvent) => { - if ([keyboardKeys.SPACE, keyboardKeys.ENTER].includes(event.key)) { + handleKeyDown = (event: SyntheticKeyboardEvent) => { + const { keyboardManager } = this.props; + if (!keyboardManager.managedKeys.includes(event.key)) { + return; + } + const handleKeyPress = keyboardManager.keyDown(event.key); + if (!handleKeyPress) { + event.preventDefault(); + return; + } + if (event.key === keys.TAB) { + keyboardManager.clear(); + ignoreFlag = false; + } else if ([keys.SPACE, keys.ENTER].includes(event.key)) { const { onSelect, optionData, isSelected } = this.props; - if (isSelected) { - this.isSpaceClickWhenSelected = event.key === keyboardKeys.SPACE; - onSelect(null); - return; + onSelect(isSelected ? null : optionData.value); + if (event.key === keys.SPACE) { + ignoreFlag = true; } - this.isSpaceClickWhenSelected = false; - onSelect(optionData.value); + } else { + ignoreFlag = true; } } + handleKeyUp = (event: SyntheticKeyboardEvent) => { + this.props.keyboardManager.keyUp(event.key); + } + + handleBlur = (event: SyntheticFocusEvent) => { + const { isSelected, optionData } = this.props; + this.props.onBlur(event, isSelected ? optionData.value : null); + } + render() { const { optionData, @@ -62,6 +89,7 @@ export class SingleSelectBox extends React.Component { focusClass, unFocusClass, onSelect, + onBlur, disabled, ...passOnProps } = this.props; @@ -82,8 +110,10 @@ export class SingleSelectBox extends React.Component { checked={isSelected} value={optionData.value} onClick={this.handleSelect} + onBlur={this.handleBlur} + onKeyDown={this.handleKeyDown} + onKeyUp={this.handleKeyUp} onChange={() => {}} - onKeyPress={this.handleKeyPress} disabled={disabled} {...passOnProps} /> diff --git a/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/withFocusHandler.js b/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/withFocusHandler.js index cb465a0ecb..c8a5858eec 100644 --- a/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/withFocusHandler.js +++ b/src/core_modules/capture-ui/internal/SelectionBoxes/SingleSelectionBoxes/SingleSelectBox/withFocusHandler.js @@ -2,6 +2,7 @@ import * as React from 'react'; type Props = { + setInputRef: (element: HTMLInputElement) => void, onSetFocus?: () => void, onRemoveFocus?: () => void, inFocus: boolean, @@ -66,10 +67,11 @@ export const withFocusHandler = () => (InnerComponent: React.ComponentType) setInputInstance = (instance: HTMLInputElement) => { this.inputInstance = instance; + this.props.setInputRef(instance); } render() { - const { onSetFocus, onRemoveFocus, inFocus, ...passOnProps } = this.props; + const { setInputRef, onSetFocus, onRemoveFocus, inFocus, ...passOnProps } = this.props; return ( // $FlowFixMe[cannot-spread-inexact] automated comment void, onSetFocus?: () => void, onRemoveFocus?: () => void, + keyboardManager: KeyboardManager, disabled?: ?boolean, }; -export class SingleSelectionBoxes extends React.Component { +type State = { + refList: Array, +}; + +export class SingleSelectionBoxes extends React.Component { static getFocusClass(classes: Object, isSelected: boolean) { return isSelected ? classes.focusSelected : classes.focusUnselected; } + refList: Array; + constructor(props: Props) { + super(props); + this.state = { + refList: [], + }; + } + + onBlur = (event: SyntheticFocusEvent) => { + if (!this.state.refList.includes(event.relatedTarget)) { + this.props.keyboardManager.clear(); + const foundRef = this.state.refList.find(ref => ref.checked); + this.props.onSelect(foundRef ? foundRef.value : null); + } + } + getCheckedClass = (iconSelected: ?string, iconDisabled?: string, isDisabled: ?boolean) => classNames( iconSelected, iconDisabled && { [iconDisabled]: isDisabled }, @@ -85,29 +107,36 @@ export class SingleSelectionBoxes extends React.Component { } getOption(optionData: OptionRendererInputData, isSelected: boolean, index: number) { - const { orientation, id: groupId, value, onSelect, classes, onSetFocus, onRemoveFocus, disabled } = this.props; + const { orientation, id: groupId, value, onSelect, classes, onSetFocus, onRemoveFocus, keyboardManager, disabled } = this.props; const containerClass = orientation === orientations.HORIZONTAL ? defaultClasses.optionContainerHorizontal : defaultClasses.optionContainerVertical; const tabIndex = isSelected || (index === 0 && !value && value !== false && value !== 0) ? 0 : -1; const IconElement = this.getIconElement(optionData, isSelected); + const setInputRef = (element: HTMLInputElement) => { + this.setState((state) => { + state.refList[index] = element; + }); + }; return (
- { /* $FlowSuppress */ } {/* $FlowFixMe[prop-missing] automated comment */} {IconElement} diff --git a/src/core_modules/capture-ui/internal/SelectionBoxes/withKeyboardNavigation.js b/src/core_modules/capture-ui/internal/SelectionBoxes/withKeyboardNavigation.js new file mode 100644 index 0000000000..2f78b13d3d --- /dev/null +++ b/src/core_modules/capture-ui/internal/SelectionBoxes/withKeyboardNavigation.js @@ -0,0 +1,37 @@ +// @flow +import React, { type ComponentType, useRef, useMemo } from 'react'; + +const managedKeys = ['Tab', ' ', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; + +export type KeyboardManager = { + managedKeys: Array, + keyDown: (key: string) => boolean, + keyUp: (key: string) => void, + clear: () => void, +}; + +export const withKeyboardNavigation = () => (InnerComponent: ComponentType) => (props: any) => { + const pressedKeys = useRef(new Set); + + const keyboardManager = useMemo(() => ({ + managedKeys, + keyDown: (key: string) => { + const count = pressedKeys.current.size; + pressedKeys.current.add(key); + return count === 0; + }, + keyUp: (key: string) => { + pressedKeys.current.delete(key); + }, + clear: () => { + pressedKeys.current.clear(); + }, + }), [pressedKeys]); + + return ( + + ); +}; diff --git a/yarn.lock b/yarn.lock index bfda6921b7..8be06b9e02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5664,7 +5664,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1: +bn.js@^5.0.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -5830,7 +5830,7 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: +browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== @@ -5839,19 +5839,19 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" - integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + version "4.2.2" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" + integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" + bn.js "^5.2.1" + browserify-rsa "^4.1.0" create-hash "^1.2.0" create-hmac "^1.1.7" - elliptic "^6.5.3" + elliptic "^6.5.4" inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" + parse-asn1 "^5.1.6" + readable-stream "^3.6.2" + safe-buffer "^5.2.1" browserify-zlib@^0.2.0: version "0.2.0" @@ -8028,7 +8028,7 @@ electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.431: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.441.tgz#94dd9c1cbf081d83f032a4f1cd9f787e21fc24ce" integrity sha512-LlCgQ8zgYZPymf5H4aE9itwiIWH4YlCiv1HFLmmcBeFYi5E+3eaIFnjHzYtcFQbaKfAW+CqZ9pgxo33DZuoqPg== -elliptic@^6.5.3: +elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -14491,7 +14491,7 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0, parse-asn1@^5.1.5: +parse-asn1@^5.0.0, parse-asn1@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== @@ -16272,7 +16272,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.2.0, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.2.0, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==