From bc2582801a924fd8963a5b6682818425e24d7717 Mon Sep 17 00:00:00 2001 From: Joy A Date: Wed, 11 Oct 2023 09:50:42 +0530 Subject: [PATCH 01/16] avniproject/avni-security#28 | Additional protections on SecureTextInput - Clear clipboard onPressIn - Change keyboard type when password is shown to prevent showing of options such as clipboard (might not work on all devices) - Prevent auto capitalization of first letter of password (happening on some devices) --- .../src/views/common/SecureTextInput.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/openchs-android/src/views/common/SecureTextInput.js b/packages/openchs-android/src/views/common/SecureTextInput.js index 24b467d1f..3cf2a2232 100644 --- a/packages/openchs-android/src/views/common/SecureTextInput.js +++ b/packages/openchs-android/src/views/common/SecureTextInput.js @@ -12,14 +12,13 @@ export const SecureTextInput = (props) => { { - General.clearClipboard(); - }} - onBlur={() => { - General.clearClipboard(); - }} + onFocus={() => General.clearClipboard()} + onBlur={() => General.clearClipboard()} + onPressIn={() => General.clearClipboard()} onSelectionChange={onSelectionChange} selection={inputTextSelection} + keyboardType={props.secureTextEntry ? 'default' : 'visible-password'} // hides additional options like clipboard when password is shown (on some devices) + autoCapitalize={'none'} /> ) } \ No newline at end of file From 4ae74c3e77e957bd85804b360602bad373a58ac1 Mon Sep 17 00:00:00 2001 From: himeshr Date: Mon, 9 Oct 2023 11:36:14 +0530 Subject: [PATCH 02/16] #1125 | restore userInfo after fast sync db restore (cherry picked from commit 346909c2b300b5f840a9677a73cba23d1048a273) --- .../openchs-android/src/service/BackupRestoreRealm.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/openchs-android/src/service/BackupRestoreRealm.js b/packages/openchs-android/src/service/BackupRestoreRealm.js index 35e09390d..113cc534c 100644 --- a/packages/openchs-android/src/service/BackupRestoreRealm.js +++ b/packages/openchs-android/src/service/BackupRestoreRealm.js @@ -16,6 +16,7 @@ import SubjectTypeService from "./SubjectTypeService"; import IndividualService from "./IndividualService"; import SubjectMigrationService from "./SubjectMigrationService"; import FormMappingService from "./FormMappingService"; +import UserInfoService from './UserInfoService'; const REALM_FILE_NAME = "default.realm"; const REALM_FILE_FULL_PATH = `${fs.DocumentDirectoryPath}/${REALM_FILE_NAME}`; @@ -85,6 +86,7 @@ export default class BackupRestoreRealmService extends BaseService { let downloadedFile = `${fs.DocumentDirectoryPath}/${General.randomUUID()}.zip`; let downloadedUncompressedDir = `${fs.DocumentDirectoryPath}/${General.randomUUID()}`; const prevSettings = settingsService.getSettings().clone(); + const prevUserInfo = UserInfo.fromResource({username: prevSettings.userId, organisationName: 'dummy', name: prevSettings.userId}); General.logInfo("BackupRestoreRealm", `To be downloaded file: ${downloadedFile}, Unzipped directory: ${downloadedUncompressedDir}, Realm file: ${REALM_FILE_FULL_PATH}`); @@ -143,6 +145,11 @@ export default class BackupRestoreRealmService extends BaseService { this._deleteUserInfoAndIdAssignment(); General.logDebug("BackupRestoreRealmService", "Deleted user info and id assignment"); }) + .then(() => { + this._restoreUserInfo(prevUserInfo); + General.logDebug("BackupRestoreRealmService", "Restoring prev userInfo to ensure we have user details" + + " immediately after fast sync restore"); + }) .then(() => { this._deleteUserGroups(); General.logDebug("BackupRestoreRealmService", "Deleted user groups"); @@ -217,6 +224,10 @@ export default class BackupRestoreRealmService extends BaseService { this.getService(SettingsService).saveOrUpdate(prevSettings); } + _restoreUserInfo(prevUserInfo) { + this.getService(UserInfoService).saveOrUpdate(prevUserInfo); + } + resetSyncForSubjectType(subjectType, db) { const formMappingsForSubjectType = this.getService(FormMappingService).getFormMappingsForSubjectType(subjectType).map(_.identity); _.forEach(formMappingsForSubjectType, (formMapping) => { From da96a6183ee5c0c6b3d1ec2a6a518efb50af575f Mon Sep 17 00:00:00 2001 From: Joy A Date: Wed, 11 Oct 2023 11:22:57 +0530 Subject: [PATCH 03/16] avniproject/avni-security#28 | SecureTextInput - Clear clipboard only if it is not empty --- packages/openchs-android/package-lock.json | 30 +++++++++---------- packages/openchs-android/package.json | 2 +- .../openchs-android/src/utility/General.js | 9 ++++-- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/openchs-android/package-lock.json b/packages/openchs-android/package-lock.json index 7a43ab641..00bfad01d 100644 --- a/packages/openchs-android/package-lock.json +++ b/packages/openchs-android/package-lock.json @@ -11,7 +11,7 @@ "license": "AGPL-3.0", "dependencies": { "@react-native-async-storage/async-storage": "^1.18.2", - "@react-native-community/clipboard": "^1.5.1", + "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/datetimepicker": "^6.5.0", "@react-native-community/netinfo": "9.3.0", "@react-native-community/progress-bar-android": "^1.0.5", @@ -3943,6 +3943,15 @@ "react-native": "^0.0.0-0 || 0.60 - 0.72 || 1000.0.0" } }, + "node_modules/@react-native-clipboard/clipboard": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.12.1.tgz", + "integrity": "sha512-+PNk8kflpGte0W1Nz61/Dp8gHTxyuRjkVyRYBawymSIGTDHCC/zOJSbig6kGIkD8MeaGHC2vGYQJyUyCrgVPBQ==", + "peerDependencies": { + "react": ">=16.0", + "react-native": ">=0.57.0" + } + }, "node_modules/@react-native-community/cli": { "version": "11.3.5", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.5.tgz", @@ -4279,15 +4288,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@react-native-community/clipboard": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@react-native-community/clipboard/-/clipboard-1.5.1.tgz", - "integrity": "sha512-AHAmrkLEH5UtPaDiRqoULERHh3oNv7Dgs0bTC0hO5Z2GdNokAMPT5w8ci8aMcRemcwbtdHjxChgtjbeA38GBdA==", - "peerDependencies": { - "react": ">=16.0", - "react-native": ">=0.57.0" - } - }, "node_modules/@react-native-community/datetimepicker": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-6.7.0.tgz", @@ -25364,6 +25364,11 @@ "merge-options": "^3.0.4" } }, + "@react-native-clipboard/clipboard": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.12.1.tgz", + "integrity": "sha512-+PNk8kflpGte0W1Nz61/Dp8gHTxyuRjkVyRYBawymSIGTDHCC/zOJSbig6kGIkD8MeaGHC2vGYQJyUyCrgVPBQ==" + }, "@react-native-community/cli": { "version": "11.3.5", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.5.tgz", @@ -25637,11 +25642,6 @@ "joi": "^17.2.1" } }, - "@react-native-community/clipboard": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@react-native-community/clipboard/-/clipboard-1.5.1.tgz", - "integrity": "sha512-AHAmrkLEH5UtPaDiRqoULERHh3oNv7Dgs0bTC0hO5Z2GdNokAMPT5w8ci8aMcRemcwbtdHjxChgtjbeA38GBdA==" - }, "@react-native-community/datetimepicker": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-6.7.0.tgz", diff --git a/packages/openchs-android/package.json b/packages/openchs-android/package.json index dcd8d3f23..2b5955c68 100644 --- a/packages/openchs-android/package.json +++ b/packages/openchs-android/package.json @@ -35,7 +35,7 @@ ], "dependencies": { "@react-native-async-storage/async-storage": "^1.18.2", - "@react-native-community/clipboard": "^1.5.1", + "@react-native-clipboard/clipboard": "^1.12.1", "@react-native-community/datetimepicker": "^6.5.0", "@react-native-community/netinfo": "9.3.0", "@react-native-community/progress-bar-android": "^1.0.5", diff --git a/packages/openchs-android/src/utility/General.js b/packages/openchs-android/src/utility/General.js index 5f567bce3..a967e04fc 100644 --- a/packages/openchs-android/src/utility/General.js +++ b/packages/openchs-android/src/utility/General.js @@ -2,7 +2,7 @@ import {Duration, Observation, Concept} from 'avni-models'; import _ from 'lodash'; import moment from "moment"; import EnvironmentConfig from "../framework/EnvironmentConfig"; -import Clipboard from '@react-native-community/clipboard'; +import Clipboard from '@react-native-clipboard/clipboard'; let currentLogLevel; @@ -319,8 +319,11 @@ class General { static STORAGE_PERMISSIONS_DEPRECATED_API_LEVEL = 33; - static clearClipboard() { - Clipboard.setString(''); + static async clearClipboard() { + const clipboardText = await Clipboard.getString() + if (!_.isEmpty(clipboardText)) { + Clipboard.setString(''); + } } //from https://stackoverflow.com/questions/39085399/lodash-remove-items-recursively From 5c38010908d79a6472fa8656a33406042c2598b0 Mon Sep 17 00:00:00 2001 From: Joy A Date: Wed, 11 Oct 2023 11:30:56 +0530 Subject: [PATCH 04/16] avniproject/avni-security#28 | Move async clearClipboard method out of General --- packages/openchs-android/src/utility/General.js | 8 -------- .../src/views/common/SecureTextInput.js | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/openchs-android/src/utility/General.js b/packages/openchs-android/src/utility/General.js index a967e04fc..483b21b74 100644 --- a/packages/openchs-android/src/utility/General.js +++ b/packages/openchs-android/src/utility/General.js @@ -2,7 +2,6 @@ import {Duration, Observation, Concept} from 'avni-models'; import _ from 'lodash'; import moment from "moment"; import EnvironmentConfig from "../framework/EnvironmentConfig"; -import Clipboard from '@react-native-clipboard/clipboard'; let currentLogLevel; @@ -319,13 +318,6 @@ class General { static STORAGE_PERMISSIONS_DEPRECATED_API_LEVEL = 33; - static async clearClipboard() { - const clipboardText = await Clipboard.getString() - if (!_.isEmpty(clipboardText)) { - Clipboard.setString(''); - } - } - //from https://stackoverflow.com/questions/39085399/lodash-remove-items-recursively static deepOmit(obj, keysToOmit) { const keysToOmitIndex = _.keyBy(Array.isArray(keysToOmit) ? keysToOmit : [keysToOmit] ); // create an index object of the keys that should be omitted diff --git a/packages/openchs-android/src/views/common/SecureTextInput.js b/packages/openchs-android/src/views/common/SecureTextInput.js index 3cf2a2232..7b76a453d 100644 --- a/packages/openchs-android/src/views/common/SecureTextInput.js +++ b/packages/openchs-android/src/views/common/SecureTextInput.js @@ -1,6 +1,7 @@ import React, {useState} from "react"; import {TextInput} from "react-native"; -import General from "../../utility/General"; +import Clipboard from "@react-native-clipboard/clipboard"; +import _ from "lodash"; export const SecureTextInput = (props) => { const [inputTextSelection, setInputTextSelection] = useState({ start: 0, end: 0 }); @@ -8,13 +9,20 @@ export const SecureTextInput = (props) => { setInputTextSelection({ start: selection.end, end: selection.end } ); }; + const clearClipboard = async () => { + const clipboardText = await Clipboard.getString() + if (!_.isEmpty(clipboardText)) { + Clipboard.setString(''); + } + } + return ( General.clearClipboard()} - onBlur={() => General.clearClipboard()} - onPressIn={() => General.clearClipboard()} + onFocus={() => clearClipboard()} + onBlur={() => clearClipboard()} + onPressIn={() => clearClipboard()} onSelectionChange={onSelectionChange} selection={inputTextSelection} keyboardType={props.secureTextEntry ? 'default' : 'visible-password'} // hides additional options like clipboard when password is shown (on some devices) From 15ef622b40b3f9c8c25d814eeda300201d2e585f Mon Sep 17 00:00:00 2001 From: Vivek Singh <105867+petmongrels@users.noreply.github.com> Date: Wed, 11 Oct 2023 11:31:00 +0530 Subject: [PATCH 05/16] Update package.json #1130 - model version change --- packages/openchs-android/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openchs-android/package.json b/packages/openchs-android/package.json index 2b5955c68..6f0bfd434 100644 --- a/packages/openchs-android/package.json +++ b/packages/openchs-android/package.json @@ -57,7 +57,7 @@ "lodash": "4.17.21", "moment": "2.29.4", "native-base": "3.4.9", - "openchs-models": "1.30.72", + "openchs-models": "1.30.73", "prop-types": "15.8.1", "react": "18.2.0", "react-native": "0.72.3", From 19431203ea7e7608e263a39c454653cce2f3c754 Mon Sep 17 00:00:00 2001 From: Vivek Singh <105867+petmongrels@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:53:22 +0530 Subject: [PATCH 06/16] Update package.json #1130 model upgrade --- packages/openchs-android/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openchs-android/package.json b/packages/openchs-android/package.json index 6f0bfd434..7befeb455 100644 --- a/packages/openchs-android/package.json +++ b/packages/openchs-android/package.json @@ -57,7 +57,7 @@ "lodash": "4.17.21", "moment": "2.29.4", "native-base": "3.4.9", - "openchs-models": "1.30.73", + "openchs-models": "1.30.74", "prop-types": "15.8.1", "react": "18.2.0", "react-native": "0.72.3", From f705c7c6afa80f062a57a6e7b1425038187c33d4 Mon Sep 17 00:00:00 2001 From: himeshr Date: Wed, 11 Oct 2023 15:47:30 +0530 Subject: [PATCH 07/16] #1128 | Translate string while rendering SelectableItemGroup Single Value --- .../openchs-android/src/views/primitives/SelectableItemGroup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openchs-android/src/views/primitives/SelectableItemGroup.js b/packages/openchs-android/src/views/primitives/SelectableItemGroup.js index 8dcf6afad..050328b73 100644 --- a/packages/openchs-android/src/views/primitives/SelectableItemGroup.js +++ b/packages/openchs-android/src/views/primitives/SelectableItemGroup.js @@ -98,7 +98,7 @@ class SelectableItemGroup extends React.Component { this.props.onPress(radioLabelValue.value, radioLabelValue.label); } return ( - {radioLabelValue.label} + {this.props.I18n.t(radioLabelValue.label)} ) } From 6b722c98d9c5f574e471578b80bea34fd5020add Mon Sep 17 00:00:00 2001 From: Maha Lakshme S Date: Fri, 13 Oct 2023 15:19:40 +0530 Subject: [PATCH 08/16] #0 | Add support to prod_dev in Make tasks --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index c3482a037..ff38b2638 100644 --- a/Makefile +++ b/Makefile @@ -158,6 +158,8 @@ release_dev: setup_hosts as_dev release release_prod_without_clean: as_prod release upload-release-sourcemap release_prod: renew_env release_prod_without_clean +release_prod_dev_without_clean: as_prod_dev release upload-release-sourcemap + bundle_release_prod_without_clean: as_prod bundle_release upload-release-sourcemap bundle_release_prod: renew_env bundle_release_prod_without_clean @@ -365,6 +367,7 @@ upload-prod-apk-unsigned: ; $(call _upload_apk,prod) upload-staging-apk: ; $(call _upload_apk,staging) upload-prerelease-apk: ; $(call _upload_apk,prerelease) upload-uat-apk: ; $(call _upload_apk,uat) +upload-prod_dev-apk: ; $(call _upload_apk,prod_dev) define _inpremise_upload_prod_apk @aws s3 cp --acl public-read packages/openchs-android/android/app/build/outputs/apk/release/app-release.apk s3://samanvay/openchs/$(orgname)/apks/prod-$(sha)-$(dat).apk; From f5100ecbedc314b12300136f23fe0a73b535b1ee Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 19 Oct 2023 11:40:55 +0530 Subject: [PATCH 09/16] #1146 pull only user info before starting the data server sync for other entities. this is to allow the user settings that impacts the sync to be correct. required for running sync after fast sync db restore --- packages/openchs-android/package-lock.json | 14 +++++++------- packages/openchs-android/package.json | 2 +- .../openchs-android/src/service/SyncService.js | 8 +++++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/openchs-android/package-lock.json b/packages/openchs-android/package-lock.json index 00bfad01d..7261cb823 100644 --- a/packages/openchs-android/package-lock.json +++ b/packages/openchs-android/package-lock.json @@ -33,7 +33,7 @@ "lodash": "4.17.21", "moment": "2.29.4", "native-base": "3.4.9", - "openchs-models": "1.30.72", + "openchs-models": "1.30.76", "prop-types": "15.8.1", "react": "18.2.0", "react-native": "0.72.3", @@ -16431,9 +16431,9 @@ } }, "node_modules/openchs-models": { - "version": "1.30.72", - "resolved": "https://registry.npmjs.org/openchs-models/-/openchs-models-1.30.72.tgz", - "integrity": "sha512-1j0HY+LOMJvTH+fLvpXJ9u4OrxKhTCxwzUM7yt/2gw9ZdPM52Ez9bYUYfol44F8PgiF2grnTkd7mfrvB0V/C6w==", + "version": "1.30.76", + "resolved": "https://registry.npmjs.org/openchs-models/-/openchs-models-1.30.76.tgz", + "integrity": "sha512-18AFuUQHWLkq8+cL5ogjjP5SO4TBHtvdmm7o3vmzcr3Ctr+wUZO3NCQLJzDZf7Jjxf16GTqGSK0KwqDOpZzWkw==", "peerDependencies": { "lodash": "*", "moment": "*" @@ -35131,9 +35131,9 @@ } }, "openchs-models": { - "version": "1.30.72", - "resolved": "https://registry.npmjs.org/openchs-models/-/openchs-models-1.30.72.tgz", - "integrity": "sha512-1j0HY+LOMJvTH+fLvpXJ9u4OrxKhTCxwzUM7yt/2gw9ZdPM52Ez9bYUYfol44F8PgiF2grnTkd7mfrvB0V/C6w==" + "version": "1.30.76", + "resolved": "https://registry.npmjs.org/openchs-models/-/openchs-models-1.30.76.tgz", + "integrity": "sha512-18AFuUQHWLkq8+cL5ogjjP5SO4TBHtvdmm7o3vmzcr3Ctr+wUZO3NCQLJzDZf7Jjxf16GTqGSK0KwqDOpZzWkw==" }, "opencollective-postinstall": { "version": "2.0.3", diff --git a/packages/openchs-android/package.json b/packages/openchs-android/package.json index 7befeb455..5cc3842b3 100644 --- a/packages/openchs-android/package.json +++ b/packages/openchs-android/package.json @@ -57,7 +57,7 @@ "lodash": "4.17.21", "moment": "2.29.4", "native-base": "3.4.9", - "openchs-models": "1.30.74", + "openchs-models": "1.30.76", "prop-types": "15.8.1", "react": "18.2.0", "react-native": "0.72.3", diff --git a/packages/openchs-android/src/service/SyncService.js b/packages/openchs-android/src/service/SyncService.js index 036acd87f..0808ee8b9 100644 --- a/packages/openchs-android/src/service/SyncService.js +++ b/packages/openchs-android/src/service/SyncService.js @@ -4,7 +4,7 @@ import BaseService from "./BaseService"; import EntityService from "./EntityService"; import EntitySyncStatusService from "./EntitySyncStatusService"; import SettingsService from "./SettingsService"; -import {EntityMetaData, EntitySyncStatus, RuleFailureTelemetry, SyncTelemetry} from 'openchs-models'; +import {EntityMetaData, EntitySyncStatus, RuleFailureTelemetry, SyncTelemetry, UserInfo} from 'openchs-models'; import EntityQueueService from "./EntityQueueService"; import MessageService from "./MessageService"; import RuleEvaluationService from "./RuleEvaluationService"; @@ -180,15 +180,17 @@ class SyncService extends BaseService { const entitiesWithoutSubjectMigrationAndResetSync = _.filter(allEntitiesMetaData, ({entityName}) => !_.includes(['ResetSync', 'SubjectMigration'], entityName)); const filteredMetadata = _.filter(entitiesWithoutSubjectMigrationAndResetSync, ({entityName}) => _.find(syncDetails, sd => sd.entityName === entityName)); - const filteredRefData = this.getMetadataByType(filteredMetadata, "reference"); + const referenceEntityMetadata = this.getMetadataByType(filteredMetadata, "reference"); const filteredTxData = this.getMetadataByType(filteredMetadata, "tx"); + const userInfoData = _.filter(filteredMetadata, ({entityName}) => entityName === "UserInfo"); const subjectMigrationMetadata = _.filter(allEntitiesMetaData, ({entityName}) => entityName === "SubjectMigration"); const updatedSyncDetails = this.updateSyncDetailsBasedOnEntityMetadata(syncDetails, allEntitiesMetaData); General.logDebug("SyncService", `Entities to sync ${_.map(updatedSyncDetails, ({entityName, entityTypeUuid}) => [entityName, entityTypeUuid])}`); this.entitySyncStatusService.updateAsPerSyncDetails(updatedSyncDetails); return Promise.resolve(statusMessageCallBack("downloadForms")) - .then(() => this.getRefData(filteredRefData, onProgressPerEntity, now)) + .then(() => this.getTxData(userInfoData, onProgressPerEntity, updatedSyncDetails, endDateTime)) + .then(() => this.getRefData(referenceEntityMetadata, onProgressPerEntity, now, endDateTime)) .then(() => this.getService(EncryptionService).encryptOrDecryptDbIfRequired()) .then(() => this.updateAsPerNewPrivilege(allEntitiesMetaData, updateProgressSteps, updatedSyncDetails)) .then(() => statusMessageCallBack("downloadNewDataFromServer")) From 997ab088237218af503fa70c210f07adad750743 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 12 Oct 2023 14:49:34 +0530 Subject: [PATCH 10/16] avniproject/avni-server#624 - Update the sync details after getting the privilege from server to remove any non privileged entity. --- .../src/service/EntitySyncStatusService.js | 8 +++-- .../src/service/SyncService.js | 31 ++++++++++--------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/openchs-android/src/service/EntitySyncStatusService.js b/packages/openchs-android/src/service/EntitySyncStatusService.js index bd335425c..b387ec900 100644 --- a/packages/openchs-android/src/service/EntitySyncStatusService.js +++ b/packages/openchs-android/src/service/EntitySyncStatusService.js @@ -108,14 +108,18 @@ class EntitySyncStatusService extends BaseService { }) } - removeRevokedPrivileges(entityMetaDataModel) { + removeRevokedPrivileges(entityMetaDataModel, syncDetails) { const hasAllPrivileges = this.getService(PrivilegeService).hasAllPrivileges(); if (hasAllPrivileges) return; const privilegeEntities = entityMetaDataModel.filter(entity => entity.privilegeParam); + const syncDetailsWithPrivilege = [...syncDetails]; privilegeEntities.forEach(entity => { const {privilegeEntity, privilegeName, privilegeParam} = entity; - this.deleteRevokedEntries(entity, this.getService(PrivilegeService).getRevokedEntityTypeUuidList(privilegeEntity, privilegeName, privilegeParam)); + const revokedEntityTypeUuids = this.getService(PrivilegeService).getRevokedEntityTypeUuidList(privilegeEntity, privilegeName, privilegeParam); + this.deleteRevokedEntries(entity, revokedEntityTypeUuids); + _.remove(syncDetailsWithPrivilege, (x) => x.entityName === entity.entityName && _.some(revokedEntityTypeUuids, (y) => y === x.entityTypeUuid)) }) + return syncDetailsWithPrivilege; } deleteEntries(criteriaQuery) { diff --git a/packages/openchs-android/src/service/SyncService.js b/packages/openchs-android/src/service/SyncService.js index 0808ee8b9..9a8de0fed 100644 --- a/packages/openchs-android/src/service/SyncService.js +++ b/packages/openchs-android/src/service/SyncService.js @@ -4,7 +4,7 @@ import BaseService from "./BaseService"; import EntityService from "./EntityService"; import EntitySyncStatusService from "./EntitySyncStatusService"; import SettingsService from "./SettingsService"; -import {EntityMetaData, EntitySyncStatus, RuleFailureTelemetry, SyncTelemetry, UserInfo} from 'openchs-models'; +import {EntityMetaData, EntitySyncStatus, RuleFailureTelemetry, SyncTelemetry, Individual, UserInfo} from 'openchs-models'; import EntityQueueService from "./EntityQueueService"; import MessageService from "./MessageService"; import RuleEvaluationService from "./RuleEvaluationService"; @@ -60,7 +60,7 @@ class SyncService extends BaseService { async sync(allEntitiesMetaData, trackProgress, statusMessageCallBack = _.noop, connectionInfo, syncStartTime, syncSource = SyncService.syncSources.SYNC_BUTTON, userConfirmation) { General.logDebug("SyncService", "sync"); const progressBarStatus = new ProgressbarStatus(trackProgress, - AllSyncableEntityMetaData.getProgressSteps(this.mediaQueueService.isMediaUploadRequired(), allEntitiesMetaData, this.entityQueueService.getPresentEntities())); + AllSyncableEntityMetaData.getProgressSteps(this.mediaQueueService.isMediaUploadRequired(), allEntitiesMetaData, this.entityQueueService.getPresentEntities())); const updateProgressSteps = (entityMetadata, entitySyncStatus) => progressBarStatus.updateProgressSteps(entityMetadata, entitySyncStatus); const onProgressPerEntity = (entityType, numOfPages) => { progressBarStatus.onComplete(entityType, numOfPages); @@ -131,8 +131,8 @@ class SyncService extends BaseService { async getSyncDetails() { const url = this.getService(SettingsService).getSettings().serverURL; - const entitySyncStatus = this.entitySyncStatusService.findAll().map(_.identity); - return post(`${url}/v2/syncDetails`, entitySyncStatus, true) + const entitySyncStatuses = this.entitySyncStatusService.findAll().map(_.identity); + return post(`${url}/v2/syncDetails`, entitySyncStatuses, true) .then(res => res.json()) .then(({syncDetails, nowMinus10Seconds, now}) => ({ syncDetails, @@ -141,7 +141,7 @@ class SyncService extends BaseService { })); } - updateSyncDetailsBasedOnEntityMetadata(syncDetails, allEntitiesMetaData) { + retainEntitiesPresentInCurrentVersion(syncDetails, allEntitiesMetaData) { const entityMetadataEntityNames = _.map(allEntitiesMetaData, 'entityName'); return _.filter(syncDetails, (syncDetail) => entityMetadataEntityNames.includes(syncDetail.entityName) @@ -184,19 +184,20 @@ class SyncService extends BaseService { const filteredTxData = this.getMetadataByType(filteredMetadata, "tx"); const userInfoData = _.filter(filteredMetadata, ({entityName}) => entityName === "UserInfo"); const subjectMigrationMetadata = _.filter(allEntitiesMetaData, ({entityName}) => entityName === "SubjectMigration"); - const updatedSyncDetails = this.updateSyncDetailsBasedOnEntityMetadata(syncDetails, allEntitiesMetaData); - General.logDebug("SyncService", `Entities to sync ${_.map(updatedSyncDetails, ({entityName, entityTypeUuid}) => [entityName, entityTypeUuid])}`); - this.entitySyncStatusService.updateAsPerSyncDetails(updatedSyncDetails); + const currentVersionEntitySyncDetails = this.retainEntitiesPresentInCurrentVersion(syncDetails, allEntitiesMetaData); + General.logDebug("SyncService", `Entities to sync ${_.map(currentVersionEntitySyncDetails, ({entityName, entityTypeUuid}) => [entityName, entityTypeUuid])}`); + this.entitySyncStatusService.updateAsPerSyncDetails(currentVersionEntitySyncDetails); + let syncDetailsWithPrivileges; return Promise.resolve(statusMessageCallBack("downloadForms")) .then(() => this.getTxData(userInfoData, onProgressPerEntity, updatedSyncDetails, endDateTime)) .then(() => this.getRefData(referenceEntityMetadata, onProgressPerEntity, now, endDateTime)) .then(() => this.getService(EncryptionService).encryptOrDecryptDbIfRequired()) - .then(() => this.updateAsPerNewPrivilege(allEntitiesMetaData, updateProgressSteps, updatedSyncDetails)) + .then(() => syncDetailsWithPrivileges = this.updateAsPerNewPrivilege(allEntitiesMetaData, updateProgressSteps, currentVersionEntitySyncDetails)) .then(() => statusMessageCallBack("downloadNewDataFromServer")) - .then(() => this.getTxData(subjectMigrationMetadata, onProgressPerEntity, updatedSyncDetails, endDateTime)) - .then(() => this.getService(SubjectMigrationService).migrateSubjects()) - .then(() => this.getTxData(filteredTxData, onProgressPerEntity, updatedSyncDetails, endDateTime)) + .then(() => this.getTxData(subjectMigrationMetadata, onProgressPerEntity, syncDetailsWithPrivileges, endDateTime)) + .then(() => this.getService(SubjectMigrationService).migrateSubjects(onProgressPerEntity)) + .then(() => this.getTxData(filteredTxData, onProgressPerEntity, syncDetailsWithPrivileges, endDateTime)) .then(() => this.downloadNewsImages()) .then(() => this.downloadExtensions()) .then(() => this.downloadIcons()) @@ -217,8 +218,9 @@ class SyncService extends BaseService { } updateAsPerNewPrivilege(allEntitiesMetaData, updateProgressSteps, syncDetails) { - this.entitySyncStatusService.removeRevokedPrivileges(allEntitiesMetaData); + let syncDetailsWithPrivileges = this.entitySyncStatusService.removeRevokedPrivileges(allEntitiesMetaData, syncDetails); updateProgressSteps(allEntitiesMetaData, syncDetails); + return syncDetailsWithPrivileges; } getRefData(entitiesMetadata, afterEachPagePulled, now) { @@ -308,13 +310,14 @@ class SyncService extends BaseService { this.getService(UserSubjectAssignmentService).deleteUnassignedSubjectsAndDependents(entities); } + General.logDebugTemp("SyncService", `${entityMetaData.entityName} ${entityMetaData.syncStatus.entityTypeUuid}`); const currentEntitySyncStatus = this.entitySyncStatusService.get(entityMetaData.entityName, entityMetaData.syncStatus.entityTypeUuid); const entitySyncStatus = new EntitySyncStatus(); entitySyncStatus.entityName = entityMetaData.entityName; entitySyncStatus.entityTypeUuid = entityMetaData.syncStatus.entityTypeUuid; entitySyncStatus.uuid = currentEntitySyncStatus.uuid; entitySyncStatus.loadedSince = new Date(_.last(entityResources).lastModifiedDateTime); - General.logDebug("SyncService", `Creating entity create functions for ${entitySyncStatus}`); + General.logDebug("SyncService", `Creating entity create functions for ${currentEntitySyncStatus}`); this.bulkSaveOrUpdate(entitiesToCreateFns.concat(this.getCreateEntityFunctions(EntitySyncStatus.schema.name, [entitySyncStatus]))); this.dispatchAction(SyncTelemetryActions.ENTITY_PULL_COMPLETED, { entityName: entityMetaData.entityName, From e98b022c053e918eb65d66b2c95dfc63b01a7011 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 12 Oct 2023 14:57:16 +0530 Subject: [PATCH 11/16] avniproject/avni-server#624 - 1. do not add entities in subject migration, only remove. 2. Do not use the active realm collection of subject migration, while updating subject migration with migrated = true. --- .../integrationTest/IntegrationTestApp.js | 89 +++++++++++++++++-- .../integrationTest/UserInfoServiceTest.js | 34 +++++++ .../src/service/SubjectMigrationService.js | 30 +++---- .../test/model/TestSubjectTypeFactory.js | 4 +- .../test/model/TestUserInfoFactory.js | 17 ++++ 5 files changed, 145 insertions(+), 29 deletions(-) create mode 100644 packages/openchs-android/integrationTest/UserInfoServiceTest.js create mode 100644 packages/openchs-android/test/model/TestUserInfoFactory.js diff --git a/packages/openchs-android/integrationTest/IntegrationTestApp.js b/packages/openchs-android/integrationTest/IntegrationTestApp.js index e4d1adcf3..6e4157a68 100644 --- a/packages/openchs-android/integrationTest/IntegrationTestApp.js +++ b/packages/openchs-android/integrationTest/IntegrationTestApp.js @@ -1,4 +1,4 @@ -import {Button, LogBox, Text, View} from "react-native"; +import {Button, LogBox, SectionList, Text, View, StyleSheet} from "react-native"; import PropTypes from 'prop-types'; import React, {Component} from 'react'; import FileSystem from "../src/model/FileSystem"; @@ -7,6 +7,47 @@ import AppStore from "../src/store/AppStore"; import RealmFactory from "../src/framework/db/RealmFactory"; import PersonRegisterActionsIntegrationTest from "./PersonRegisterActionsIntegrationTest"; import RNRestart from 'react-native-restart'; +import DatabaseTest from "./DatabaseTest"; +import IntegrationTestRunner, {TestSuite} from "./IntegrationTestRunner"; +import UtilTest from "./UtilTest"; +import UserInfoServiceTest from "./UserInfoServiceTest"; + +const itemCommonStyle = { + padding: 10, + marginVertical: 8, + display: "flex", + flexDirection: "row", + flex: 1 +} + +const styles = StyleSheet.create({ + item: { + ...itemCommonStyle + }, + success: { + backgroundColor: 'green', + ...itemCommonStyle + }, + failure: { + backgroundColor: 'red', + ...itemCommonStyle + }, + header: { + backgroundColor: '#fff', + flexDirection: "row", + flex: 1, + justifyContent: "space-between" + }, + headerText: { + fontSize: 20, + paddingLeft: 10 + }, + title: { + fontSize: 14, + backgroundColor: '#f9c2ff', + flex: 0.9 + }, +}); class IntegrationTestApp extends Component { static childContextTypes = { @@ -19,7 +60,8 @@ class IntegrationTestApp extends Component { super(props, context); FileSystem.init(); this.getBean = this.getBean.bind(this); - this.state = {isInitialisationDone: false}; + this.integrationTestRunner = new IntegrationTestRunner(UserInfoServiceTest, DatabaseTest, PersonRegisterActionsIntegrationTest, UtilTest); + this.state = {isInitialisationDone: false, integrationTests: this.integrationTestRunner.testSuite}; } getChildContext = () => ({ @@ -40,19 +82,48 @@ class IntegrationTestApp extends Component { await globalContext.initialiseGlobalContext(AppStore, RealmFactory); } this.setState(state => ({...state, isInitialisationDone: true})); + // setTimeout(() => this.integrationTestRunner.run((x) => this.testRunObserver(x)), 100); + } + + testRunObserver(integrationTests) { + this.setState({integrationTests: integrationTests}); } render() { - LogBox.ignoreAllLogs(); + const {integrationTests} = this.state; + const dataSource = _.map(_.groupBy(integrationTests.testMethods, (x) => x.testClass.name), (testMethods, testClassName) => { + return {title: testClassName, data: testMethods, testClass: testMethods[0].testClass}; + }); + LogBox.ignoreAllLogs(); if (this.state.isInitialisationDone) { - return -