From f1567bea76a8a4f48157bc709f75041e6607bf5c Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Tue, 18 Jun 2024 22:26:27 +0300 Subject: [PATCH 1/4] Configure unique identifier assignment (#3075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement configurable unique ID assignment on Questionnaires Signed-off-by: Elly Kitoto * Document uniqueId assignment configuration Signed-off-by: Elly Kitoto * Mark unique ID resource as false when all IDs are used Signed-off-by: Elly Kitoto * Test retire used IDs Signed-off-by: Elly Kitoto * Test DefaultRepository#retrieveUniqueIdAssignmentResource Signed-off-by: Elly Kitoto * :art: Apply Spotless formatting * :construction: Exclude ID if it is the submitted ID * :art: Apply spotless formatting * :test_tube: Update unique ID assignment tests * :art: Apply spotless formatting * :art: Apply Spotless formatting * :recycle: Get ID in characteristics using a count Refactored implementation to use Group.quantity as a count to retrieve unique ID to be updated and update the resource only when an ID is consumed * :white_check_mark: Update unique ID test cases * :art: Apply Spotless formatting * :memo: Update Unique ID assignment documentation * :memo: Update Unique ID assignment documentation * :construction: Remove unused import * Run spotlessApply Signed-off-by: Elly Kitoto * add comma * ✅ Update DefaultRepo tests Remove unused import * 🚧 Remove filter by 'active' Param Removed since this it isn't a supported search param * ✅ Fix retrieve unique ID default repo test --------- Signed-off-by: Elly Kitoto Co-authored-by: Benjamin Mwalimu Co-authored-by: Peter Lubell-Doughtie Co-authored-by: Allan Onchuru <16164649+allan-on@users.noreply.github.com> Co-authored-by: Allan O --- .../configuration/QuestionnaireConfig.kt | 30 +++ .../engine/data/local/DefaultRepository.kt | 58 +++++- .../data/local/register/RegisterRepository.kt | 4 - .../data/local/DefaultRepositoryTest.kt | 67 +++++++ .../questionnaire/QuestionnaireViewModel.kt | 65 ++++++- .../QuestionnaireViewModelTest.kt | 180 ++++++++++++++++++ .../config-types/forms/questionnaire.mdx | 167 +++++++++++++++- 7 files changed, 555 insertions(+), 16 deletions(-) diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt index 11b4985b1c..b4baff78b3 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt @@ -23,9 +23,12 @@ import org.hl7.fhir.r4.model.ResourceType import org.smartregister.fhircore.engine.configuration.event.EventWorkflow import org.smartregister.fhircore.engine.domain.model.ActionConfig import org.smartregister.fhircore.engine.domain.model.ActionParameter +import org.smartregister.fhircore.engine.domain.model.DataQuery import org.smartregister.fhircore.engine.domain.model.QuestionnaireType +import org.smartregister.fhircore.engine.domain.model.ResourceFilterExpression import org.smartregister.fhircore.engine.domain.model.RuleConfig import org.smartregister.fhircore.engine.domain.model.SnackBarMessageConfig +import org.smartregister.fhircore.engine.domain.model.SortConfig import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid import org.smartregister.fhircore.engine.util.extension.interpolate @@ -60,6 +63,7 @@ data class QuestionnaireConfig( val showRequiredTextAsterisk: Boolean = true, val showRequiredText: Boolean = false, val managingEntityRelationshipCode: String? = null, + val uniqueIdAssignment: UniqueIdAssignmentConfig? = null, val linkIds: List? = null, ) : java.io.Serializable, Parcelable { @@ -90,6 +94,8 @@ data class QuestionnaireConfig( onSubmitActions = onSubmitActions?.map { it.interpolate(computedValuesMap) }, barcodeLinkId = barcodeLinkId?.interpolate(computedValuesMap), cqlInputResources = cqlInputResources?.map { it.interpolate(computedValuesMap) }, + uniqueIdAssignment = + uniqueIdAssignment?.copy(linkId = uniqueIdAssignment.linkId.interpolate(computedValuesMap)), linkIds = linkIds?.onEach { it.linkId.interpolate(computedValuesMap) }, saveButtonText = saveButtonText?.interpolate(computedValuesMap), ) @@ -120,6 +126,30 @@ data class ExtractedResourceUniquePropertyExpression( val fhirPathExpression: String, ) : java.io.Serializable, Parcelable +@Serializable +@Parcelize +/** + * @property linkId The linkId used to capture the OpenSRP unique ID in the Questionnaire. Typically + * a GUID. + * @property idFhirPathExpression The FHIR Path expression for extracting ID from the configured + * [resource] + * @property resource The type of resource used to store generated IDs + * @property readOnly Whether to disable/enable editing of link ID. + * @property dataQueries The queries used to filter resources used for representing OpenSRP unique + * IDs. + * @property sortConfigs Configuration for sorting resources + * @property resourceFilterExpression Expression used to filter the returned resources + */ +data class UniqueIdAssignmentConfig( + val linkId: String, + val idFhirPathExpression: String, + val readOnly: Boolean = true, + val resource: ResourceType, + val dataQueries: List = emptyList(), + val sortConfigs: List? = null, + val resourceFilterExpression: ResourceFilterExpression? = null, +) : java.io.Serializable, Parcelable + @Serializable @Parcelize data class LinkIdConfig( diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt index 84c96499ae..d22cc02127 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt @@ -30,6 +30,7 @@ import com.google.android.fhir.FhirEngine import com.google.android.fhir.datacapture.extensions.logicalId import com.google.android.fhir.db.ResourceNotFoundException import com.google.android.fhir.get +import com.google.android.fhir.search.Order import com.google.android.fhir.search.Search import com.google.android.fhir.search.filter.ReferenceParamFilterCriterion import com.google.android.fhir.search.filter.TokenParamFilterCriterion @@ -69,11 +70,11 @@ import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType import org.smartregister.fhircore.engine.R import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry +import org.smartregister.fhircore.engine.configuration.UniqueIdAssignmentConfig import org.smartregister.fhircore.engine.configuration.app.ConfigService import org.smartregister.fhircore.engine.configuration.event.EventWorkflow import org.smartregister.fhircore.engine.configuration.profile.ManagingEntityConfig import org.smartregister.fhircore.engine.configuration.register.ActiveResourceFilterConfig -import org.smartregister.fhircore.engine.data.local.register.RegisterRepository import org.smartregister.fhircore.engine.datastore.syncLocationIdsProtoStore import org.smartregister.fhircore.engine.domain.model.Code import org.smartregister.fhircore.engine.domain.model.DataQuery @@ -155,8 +156,8 @@ constructor( suspend inline fun count(search: Search) = fhirEngine.count(search) /** - * Saves a resource in the database. It also updates the [Resource.meta.lastUpdated] and generates - * the [Resource.id] if it is missing before saving the resource. + * Saves a resource in the database. It also updates the [Resource.meta] _lastUpdated and + * generates the [Resource.id] if it is missing before saving the resource. * * By default, mandatory Resource tags for sync are added but this can be disabled through the * param [addResourceTags] @@ -428,7 +429,7 @@ constructor( ) { val activeResource = filterActiveResources?.find { it.resourceType == resourceConfig.resource } if (!filterActiveResources.isNullOrEmpty() && activeResource?.active == true) { - filter(TokenClientParam(RegisterRepository.ACTIVE), { value = of(true) }) + filter(TokenClientParam(ACTIVE), { value = of(true) }) } resourceConfig.dataQueries?.forEach { dataQuery -> @@ -839,7 +840,7 @@ constructor( * @param resources - The list of resources to be filtered. Note that it only contains resources * of a single type. */ - fun filterResourcesByFhirPathExpression( + private fun filterResourcesByFhirPathExpression( resourceFilterExpressions: List?, resources: List, ): List { @@ -921,7 +922,7 @@ constructor( withContext(dispatcherProvider.io()) { fhirEngine.update(updatedResource as Resource) } } - fun getJsonContent(jsonElement: JsonElement): Any? { + private fun getJsonContent(jsonElement: JsonElement): Any? { return when (jsonElement) { is JsonPrimitive -> jsonElement.jsonPrimitive.content is JsonObject -> jsonElement.jsonObject @@ -1096,6 +1097,49 @@ constructor( } } + suspend fun retrieveUniqueIdAssignmentResource( + uniqueIdAssignmentConfig: UniqueIdAssignmentConfig?, + ): Resource? { + if (uniqueIdAssignmentConfig != null) { + val search = + Search(uniqueIdAssignmentConfig.resource).apply { + uniqueIdAssignmentConfig.dataQueries.forEach { + filterBy(dataQuery = it, configComputedRuleValues = emptyMap()) + } + if (uniqueIdAssignmentConfig.sortConfigs != null) { + sort(uniqueIdAssignmentConfig.sortConfigs) + } else { + sort( + DateClientParam(LAST_UPDATED), + Order.DESCENDING, + ) + } + } + + val resources = search(search) + val idResources = + if (uniqueIdAssignmentConfig.resourceFilterExpression != null) { + resources.filter { resource -> + val (conditionalFhirPathExpressions, matchAll) = + uniqueIdAssignmentConfig.resourceFilterExpression + if (matchAll) { + conditionalFhirPathExpressions.all { + fhirPathDataExtractor.extractValue(resource, it).toBoolean() + } + } else { + conditionalFhirPathExpressions.any { + fhirPathDataExtractor.extractValue(resource, it).toBoolean() + } + } + } + } else { + resources + } + return idResources.firstOrNull() + } + return null + } + /** * A wrapper data class to hold search results. All related resources are flattened into one Map * including the nested related resources as required by the Rules Engine facts. @@ -1110,5 +1154,7 @@ constructor( const val PATIENT_CONDITION_RESOLVED_CODE = "resolved" const val PATIENT_CONDITION_RESOLVED_DISPLAY = "Resolved" const val TAG = "_tag" + const val LAST_UPDATED = "_lastUpdated" + const val ACTIVE = "active" } } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepository.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepository.kt index 23577d7520..22f6f2044a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepository.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepository.kt @@ -179,8 +179,4 @@ constructor( paramsMap: Map?, ): RegisterConfiguration = configurationRegistry.retrieveConfiguration(ConfigType.Register, registerId, paramsMap) - - companion object { - const val ACTIVE = "active" - } } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/DefaultRepositoryTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/DefaultRepositoryTest.kt index 3de2d89ca7..6b5b57a242 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/DefaultRepositoryTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/DefaultRepositoryTest.kt @@ -49,6 +49,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.JsonPrimitive import org.hl7.fhir.r4.model.Address +import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CarePlan import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -76,6 +77,7 @@ import org.junit.Test import org.smartregister.fhircore.engine.app.AppConfigService import org.smartregister.fhircore.engine.app.fakes.Faker import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry +import org.smartregister.fhircore.engine.configuration.UniqueIdAssignmentConfig import org.smartregister.fhircore.engine.configuration.app.ConfigService import org.smartregister.fhircore.engine.configuration.event.EventWorkflow import org.smartregister.fhircore.engine.configuration.event.UpdateWorkflowValueConfig @@ -1418,4 +1420,69 @@ class DefaultRepositoryTest : RobolectricTest() { coVerify { fhirEngine.create(resource, isLocalOnly = true) } } + + @Test + fun testRetrieveUniqueIdAssignmentResourceShouldReturnAResource() = + runTest(timeout = 30.seconds) { + val group1 = + Group().apply { + id = "grp1" + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept().apply { text = "phn" }, + CodeableConcept().apply { text = "1234" }, + BooleanType(true), + ), + ) + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept().apply { text = "phn" }, + CodeableConcept().apply { text = "123456" }, + BooleanType(false), + ), + ) + active = true + type = Group.GroupType.DEVICE + name = "Unique IDs" + } + + val group2 = + Group().apply { + id = "grp2" + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept().apply { text = "phn" }, + CodeableConcept().apply { text = "56789" }, + BooleanType(false), + ), + ) + active = false + type = Group.GroupType.DEVICE + name = "Unique IDs" + } + + val uniqueIdAssignmentConfig = + UniqueIdAssignmentConfig( + linkId = "phn", + idFhirPathExpression = + "Group.characteristic.where(exclude=false and code.text='phn').first().value.text", + readOnly = false, + resource = ResourceType.Group, + resourceFilterExpression = + ResourceFilterExpression( + conditionalFhirPathExpressions = + listOf( + "Group.active = true and Group.type = 'device' and Group.name = 'Unique IDs'", + ), + matchAll = true, + ), + ) + + fhirEngine.create(group1, group2) + val resource = defaultRepository.retrieveUniqueIdAssignmentResource(uniqueIdAssignmentConfig) + Assert.assertNotNull(resource) + Assert.assertTrue(resource is Group) + Assert.assertEquals("1234", (resource as Group).characteristic[0].valueCodeableConcept.text) + Assert.assertFalse(resource.characteristic[1].exclude) + } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt index dd5a10c2ad..593f1981d7 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt @@ -135,9 +135,13 @@ constructor( configurationRegistry.retrieveConfiguration(ConfigType.Application) } + var uniqueIdResource: Resource? = null + /** * This function retrieves the [Questionnaire] as configured via the [QuestionnaireConfig]. The - * retrieved [Questionnaire] can be pre-populated with computed values from the Rules engine. + * retrieved [Questionnaire] can be pre-populated with computed values from the Rules engine as + * well as include initial values set on configured [QuestionnaireConfig.barcodeLinkId] or + * [QuestionnaireConfig.uniqueIdAssignment] properties. */ suspend fun retrieveQuestionnaire( questionnaireConfig: QuestionnaireConfig, @@ -196,6 +200,31 @@ constructor( } } } + + // Set configured openSrpId on Questionnaire + questionnaireConfig.uniqueIdAssignment?.let { uniqueIdAssignmentConfig -> + find(uniqueIdAssignmentConfig.linkId)?.apply { + // Extract ID from a Group, should be modified in future to support other resources + val uniqueIdResource = + defaultRepository.retrieveUniqueIdAssignmentResource( + questionnaireConfig.uniqueIdAssignment, + ) + + val extractedId = + fhirPathDataExtractor.extractValue( + base = uniqueIdResource, + expression = uniqueIdAssignmentConfig.idFhirPathExpression, + ) + if (uniqueIdResource != null && extractedId.isNotEmpty()) { + initial = + mutableListOf( + Questionnaire.QuestionnaireItemInitialComponent() + .setValue(StringType(extractedId)), + ) + } + readOnly = extractedId.isNotEmpty() && uniqueIdAssignmentConfig.readOnly + } + } } return questionnaire } @@ -292,6 +321,8 @@ constructor( softDeleteResources(questionnaireConfig) + retireUsedQuestionnaireUniqueId(questionnaireConfig, currentQuestionnaireResponse) + val idTypes = bundle.entry?.map { IdType(it.resource.resourceType.name, it.resource.logicalId) } ?: emptyList() @@ -299,6 +330,38 @@ constructor( } } + suspend fun retireUsedQuestionnaireUniqueId( + questionnaireConfig: QuestionnaireConfig, + questionnaireResponse: QuestionnaireResponse, + ) { + if (questionnaireConfig.uniqueIdAssignment != null) { + val uniqueIdLinkId = questionnaireConfig.uniqueIdAssignment!!.linkId + val submittedUniqueId = + questionnaireResponse.find(uniqueIdLinkId)?.answer?.first()?.value.toString() + + // Update Group resource. Can be extended in future to support other resources + if (uniqueIdResource is Group) { + with(uniqueIdResource as Group) { + val characteristic = this.characteristic[this.quantity] + if ( + characteristic.hasValueCodeableConcept() && + characteristic.valueCodeableConcept.text == submittedUniqueId + ) { + characteristic.exclude = true + this.quantity++ + this.active = + this.quantity < + this.characteristic.size // Mark Group as inactive when all IDs are retired + defaultRepository.addOrUpdate(resource = this) + } + } + } + Timber.i( + "ID '$submittedUniqueId' used'", + ) + } + } + suspend fun saveExtractedResources( bundle: Bundle, questionnaire: Questionnaire, diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt index f678ba0ccd..85b757ea2f 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt @@ -49,6 +49,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.hl7.fhir.r4.model.Address import org.hl7.fhir.r4.model.Basic +import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -81,6 +82,7 @@ import org.smartregister.fhircore.engine.configuration.GroupResourceConfig import org.smartregister.fhircore.engine.configuration.LinkIdConfig import org.smartregister.fhircore.engine.configuration.LinkIdType import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig +import org.smartregister.fhircore.engine.configuration.UniqueIdAssignmentConfig import org.smartregister.fhircore.engine.configuration.app.ConfigService import org.smartregister.fhircore.engine.data.local.DefaultRepository import org.smartregister.fhircore.engine.domain.model.ActionParameter @@ -1270,6 +1272,184 @@ class QuestionnaireViewModelTest : RobolectricTest() { assertEquals(linkId, listResource.id) } + @Test + fun testRetireUsedQuestionnaireUniqueIdShouldUpdateGroupResourceWhenIDIsUsed() = runTest { + val linkId = "phn" + val uniqueIdAssignmentConfig = + UniqueIdAssignmentConfig( + linkId = linkId, + idFhirPathExpression = "", + resource = ResourceType.Group, + ) + val questionnaireConfig = + QuestionnaireConfig( + id = "sample_config_123", + uniqueIdAssignment = uniqueIdAssignmentConfig, + ) + val group = + Group().apply { + id = "grp1" + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept(Coding()).setText("phn"), + CodeableConcept(Coding()).setText("1234"), + BooleanType(false), + ), + ) + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept(Coding()).setText("phn"), + CodeableConcept(Coding()).setText("12345"), + BooleanType(false), + ), + ) + } + + val questionnaireResponse = + extractionQuestionnaireResponse().apply { + addItem( + QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType(linkId)).apply { + addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { + setValue(StringType("1234")) + }, + ) + }, + ) + } + questionnaireViewModel.uniqueIdResource = group + coEvery { defaultRepository.addOrUpdate(resource = group) } just runs + questionnaireViewModel.retireUsedQuestionnaireUniqueId( + questionnaireConfig, + questionnaireResponse, + ) + + coVerify(exactly = 1) { defaultRepository.addOrUpdate(resource = group) } + Assert.assertTrue(group.characteristic.first().exclude) + Assert.assertFalse(group.characteristic.last().exclude) + Assert.assertTrue(group.active) + } + + @Test + fun testRetireUsedQuestionnaireUniqueIdShouldDeactivateGroupResourceWhenAllIDsAreUsed() = + runTest { + val linkId = "phn" + val uniqueIdAssignmentConfig = + UniqueIdAssignmentConfig( + linkId = linkId, + idFhirPathExpression = "", + resource = ResourceType.Group, + ) + val questionnaireConfig = + QuestionnaireConfig( + id = "sample_config_123", + uniqueIdAssignment = uniqueIdAssignmentConfig, + ) + val group = + Group().apply { + id = "grp2" + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept(Coding()).setText("phn"), + CodeableConcept(Coding()).setText("1234"), + BooleanType(true), + ), + ) + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept(Coding()).setText("phn"), + CodeableConcept(Coding()).setText("1235"), + BooleanType(false), + ), + ) + this.quantity = 1 + } + + val questionnaireResponse = + extractionQuestionnaireResponse().apply { + addItem( + QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType(linkId)).apply { + addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { + setValue(StringType("1235")) + }, + ) + }, + ) + } + + questionnaireViewModel.uniqueIdResource = group + coEvery { defaultRepository.addOrUpdate(resource = group) } just runs + questionnaireViewModel.retireUsedQuestionnaireUniqueId( + questionnaireConfig, + questionnaireResponse, + ) + + coVerify(exactly = 1) { defaultRepository.addOrUpdate(resource = group) } + Assert.assertTrue(group.characteristic.first().exclude) + Assert.assertTrue(group.characteristic.last().exclude) + Assert.assertFalse(group.active) + } + + @Test + fun testGroupResourceShouldNotBeUpdatedWhenCustomUniqueIDsAreUsed() = runTest { + val linkId = "phn" + val uniqueIdAssignmentConfig = + UniqueIdAssignmentConfig( + linkId = linkId, + idFhirPathExpression = "", + resource = ResourceType.Group, + ) + val questionnaireConfig = + QuestionnaireConfig( + id = "sample_config_123", + uniqueIdAssignment = uniqueIdAssignmentConfig, + ) + val group = + Group().apply { + id = "grp2" + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept(Coding()).setText("phn"), + CodeableConcept(Coding()).setText("1234"), + BooleanType(true), + ), + ) + addCharacteristic( + Group.GroupCharacteristicComponent( + CodeableConcept(Coding()).setText("phn"), + CodeableConcept(Coding()).setText("1235"), + BooleanType(false), + ), + ) + this.quantity = 1 + } + + val questionnaireResponse = + extractionQuestionnaireResponse().apply { + addItem( + QuestionnaireResponse.QuestionnaireResponseItemComponent(StringType(linkId)).apply { + addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { + setValue(StringType("CUSTID123456")) + }, + ) + }, + ) + } + + questionnaireViewModel.uniqueIdResource = group + questionnaireViewModel.retireUsedQuestionnaireUniqueId( + questionnaireConfig, + questionnaireResponse, + ) + + coVerify(exactly = 0) { defaultRepository.addOrUpdate(resource = group) } + Assert.assertTrue(group.characteristic.first().exclude) + Assert.assertFalse(group.characteristic.last().exclude) + Assert.assertFalse(group.active) + } + @Test fun testThatPopulateQuestionnaireSetInitialDefaultValueForQuestionnaireInitialExpression() = runTest { diff --git a/docs/engineering/app/configuring/config-types/forms/questionnaire.mdx b/docs/engineering/app/configuring/config-types/forms/questionnaire.mdx index 5a24fb2060..d197587692 100644 --- a/docs/engineering/app/configuring/config-types/forms/questionnaire.mdx +++ b/docs/engineering/app/configuring/config-types/forms/questionnaire.mdx @@ -124,6 +124,7 @@ These are used when generating other tasks, CarePlans and related resources.See | saveQuestionnaireResponse | Indicate whether to save QuestionnaireResponse or not | yes | true | | onSubmitActions | Configurations for actions invoked post Questionnaire submission | no | null | | extractedResourceUniquePropertyExpressions | Configurations for unique properties used to identify resources during Questionnaire edit | no | null | +| uniqueIdAssignment | Configuration for unique identifier assignment | no | null | ## Dynamic data pass between Profiles and Questionnaires @@ -327,9 +328,163 @@ triggerConditions | This defines an array of condition for to be met for the eve eventResourceId | uniqueId of resource id to be closed | yes | | eventResources | A list of resources to close(Type of ResourceConfig) | yes | | | +## Unique ID assignment + +Unique IDs are unique identifier values assigned to a resource (e.g. Patient) and are associated with a single entity. + +Unique ID assignment configs determine how pre-generated unique IDs are retrieved from a Group FHIR resource and subsequently populated in a Questionnaire field. + +Here is a sample configuration for the unique identifier assignment: + +```json +{ + "uniqueIdAssignment": { + "linkId": "phn", + "idFhirPathExpression": "Group.characteristic.where(exclude=false and code.text='phn').first().value.text", + "readOnly": false, + "resource": "Group", + "sortConfigs": [ + { + "paramName": "_lastUpdated", + "dataType": "DATE", + "order": "DESCENDING" + } + ], + "resourceFilterExpression": { + "conditionalFhirPathExpressions": [ + "Group.active = true and Group.type = 'device' and Group.name = 'Unique IDs'" + ], + "matchAll": true + } + } +} +``` + +The configuration contains the following properties: + +**linkId** - The linkId for the targeted Questionnaire item +**idFhirPathExpression** - The FHIR path expression used to extract ID from a resource +**readOnly** - Enable or disable editing of the field. Defaults to `true` +**resource** - FHIR resource used to store generated unique IDs +**sortConfigs** - For ordering resources. It is important to ensure the resources are ordered by last updated +**resourceFilterExpression** - Extra configurations to apply filter via code on the declared Resource + +*NOTE:* If the `readOnly` configuration is set to false, the ID field in the Questionnaire becomes editable. If the prepopulated ID +is modified and a different ID is submitted with the Questionnaire, the prepopulated ID will not be marked as used. +This means that it will still be prepopulated the next time the Questionnaire is launched. + +## Characteristic-based Group resource for unique IDs + +IDs are stored as `text` in a `valueCodeableConcept` in the `characteristic` field. +The batch of IDs is assigned to a Practitioner using the `managingEntity`. + +When an ID is used, the characteristic entry with that ID is updated to be excluded by setting `"exclude": true`. Once all IDs in the Group are used, the group is set to inactive. + +## Sample Group resource with unique IDs + +```json +{ + "resourceType": "Group", + "id": "37312ad4-538e-4535-82d2-ea14f40deeb9", + "meta": { + "versionId": "9", + "lastUpdated": "2023-12-22T06:43:35.986+00:00", + "source": "#04a1c85fb6adf0cc", + "tag": [ + { + "system": "https://smartregister.org/care-team-tag-id", + "code": "3e005baf-854b-40a7-bdd5-9b73f63aa9a3", + "display": "Practitioner CareTeam" + }, + { + "system": "https://smartregister.org/organisation-tag-id", + "code": "41eae946-bdc4-4179-b404-6503ff12f59c", + "display": "Practitioner Organization" + }, + { + "system": "https://smartregister.org/location-tag-id", + "code": "3816", + "display": "Practitioner Location" + }, + { + "system": "https://smartregister.org/practitioner-tag-id", + "code": "49b72a3d-44cd-4a74-9459-4dc9f6b543fa", + "display": "Practitioner" + }, + { + "system": "https://smartregister.org/app-version", + "code": "Not defined", + "display": "Application Version" + } + ] + }, + "identifier": [ + { + "system": "http://smartregister.org", + "value": "37312ad4-538e-4535-82d2-ea14f40deeb9" + } + ], + "active": true, + "type": "device", + "actual": true, + "name": "Unique IDs", + "managingEntity": { + "reference": "Practitioner/49b72a3d-44cd-4a74-9459-4dc9f6b543fa" + }, + "characteristic": [ + { + "code": { + "text": "phn" + }, + "valueCodeableConcept": { + "text": "1000010001" + }, + "exclude": false + }, + { + "code": { + "text": "phn" + }, + "valueCodeableConcept": { + "text": "1000020002" + }, + "exclude": false + }, + { + "code": { + "text": "phn" + }, + "valueCodeableConcept": { + "text": "1000030003" + }, + "exclude": false + }, + { + "code": { + "text": "phn" + }, + "valueCodeableConcept": { + "text": "1000040004" + }, + "exclude": false + }, + { + "code": { + "text": "phn" + }, + "valueCodeableConcept": { + "text": "1000050005" + }, + "exclude": false + } + ] +} +``` ## Hiding characters in a questionnaire -We can hide sensitive information typed on a questionnaire through adding a linkId extension. A sample linkId with password-widget extension looks like + +Sensitive information typed on a questionnaire can be hidden through adding a linkId extension. A sample linkId with password-widget extension looks like + ``` json { "extension": [ @@ -385,10 +540,12 @@ We can hide sensitive information typed on a questionnaire through adding a link "maxLength": 16 } ``` -Below is the specific extension for this. The extension is validated at class called [PasswordViewHolderFactory](https://github.com/opensrp/fhircore/blob/main/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/sdc/PasswordViewHolderFactory.kt) + +Below is the specific extension for this. The extension is validated in this class [PasswordViewHolderFactory](https://github.com/opensrp/fhircore/blob/main/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/sdc/PasswordViewHolderFactory.kt) + ``` json { - "url": "https://github.com/google/android-fhir/StructureDefinition/questionnaire-itemControl", - "valueString": "password-widget" - } + "url": "https://github.com/google/android-fhir/StructureDefinition/questionnaire-itemControl", + "valueString": "password-widget" + } ``` \ No newline at end of file From eebb9a7c7708e42b8d4ffb2b65f639f73c2598f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:38:05 -0400 Subject: [PATCH 2/4] Bump ws from 7.5.9 to 7.5.10 (#3343) Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 +- yarn.lock | 340 ++++++++++++++++++---------------------------- 2 files changed, 138 insertions(+), 214 deletions(-) diff --git a/package-lock.json b/package-lock.json index adf2372ea8..98200843fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14285,9 +14285,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, @@ -14529,9 +14529,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, diff --git a/yarn.lock b/yarn.lock index 17e96603da..0394f84e5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -84,7 +84,7 @@ "@algolia/requester-common" "4.23.3" "@algolia/transporter" "4.23.3" -"@algolia/client-search@>= 4.9.1 < 6", "@algolia/client-search@4.23.3": +"@algolia/client-search@4.23.3": version "4.23.3" resolved "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz" integrity sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw== @@ -176,7 +176,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz" integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.0.0-0 || ^8.0.0-0 <8.0.0", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.21.3", "@babel/core@^7.23.3", "@babel/core@^7.4.0 || ^8.0.0-0 <8.0.0": +"@babel/core@^7.21.3", "@babel/core@^7.23.3": version "7.24.7" resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz" integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== @@ -1223,7 +1223,7 @@ "@docsearch/css" "3.6.0" algoliasearch "^4.19.1" -"@docusaurus/core@^3.4.0", "@docusaurus/core@3.4.0": +"@docusaurus/core@3.4.0", "@docusaurus/core@^3.4.0": version "3.4.0" resolved "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz" integrity sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w== @@ -1345,7 +1345,7 @@ vfile "^6.0.1" webpack "^5.88.1" -"@docusaurus/module-type-aliases@^3.0.0", "@docusaurus/module-type-aliases@3.4.0": +"@docusaurus/module-type-aliases@3.4.0", "@docusaurus/module-type-aliases@^3.0.0": version "3.4.0" resolved "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz" integrity sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw== @@ -1494,7 +1494,7 @@ "@docusaurus/theme-search-algolia" "3.4.0" "@docusaurus/types" "3.4.0" -"@docusaurus/theme-classic@^3.4.0", "@docusaurus/theme-classic@3.4.0": +"@docusaurus/theme-classic@3.4.0", "@docusaurus/theme-classic@^3.4.0": version "3.4.0" resolved "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz" integrity sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q== @@ -1576,7 +1576,7 @@ fs-extra "^11.1.1" tslib "^2.6.0" -"@docusaurus/types@*", "@docusaurus/types@3.4.0": +"@docusaurus/types@3.4.0": version "3.4.0" resolved "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz" integrity sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A== @@ -1775,7 +1775,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -1909,7 +1909,7 @@ "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" "@svgr/babel-plugin-transform-svg-component" "8.0.0" -"@svgr/core@*", "@svgr/core@8.1.0": +"@svgr/core@8.1.0": version "8.1.0" resolved "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz" integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== @@ -2218,7 +2218,7 @@ "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@>= 16.8.0 < 19.0.0", "@types/react@>=16": +"@types/react@*": version "18.0.25" resolved "https://registry.npmjs.org/@types/react/-/react-18.0.25.tgz" integrity sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g== @@ -2309,7 +2309,7 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@webassemblyjs/ast@^1.11.5", "@webassemblyjs/ast@1.11.6": +"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz" integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== @@ -2410,7 +2410,7 @@ "@webassemblyjs/wasm-gen" "1.11.6" "@webassemblyjs/wasm-parser" "1.11.6" -"@webassemblyjs/wasm-parser@^1.11.5", "@webassemblyjs/wasm-parser@1.11.6": +"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": version "1.11.6" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz" integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== @@ -2463,7 +2463,7 @@ acorn-walk@^8.0.0: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz" integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8, acorn@^8.0.0, acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: +acorn@^8.0.0, acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: version "8.11.2" resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== @@ -2488,12 +2488,7 @@ ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" -ajv-keywords@^3.4.1: - version "3.5.2" - resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^3.5.2: +ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== @@ -2505,7 +2500,7 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.12.2, ajv@^6.12.5, ajv@^6.9.1: +ajv@^6.12.2, ajv@^6.12.5: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2515,7 +2510,7 @@ ajv@^6.12.2, ajv@^6.12.5, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.8.2, ajv@^8.9.0: +ajv@^8.0.0, ajv@^8.9.0: version "8.11.2" resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz" integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== @@ -2532,7 +2527,7 @@ algoliasearch-helper@^3.13.3: dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.18.0, algoliasearch@^4.19.1, "algoliasearch@>= 3.1 < 6", "algoliasearch@>= 4.9.1 < 6": +algoliasearch@^4.18.0, algoliasearch@^4.19.1: version "4.23.3" resolved "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz" integrity sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg== @@ -2619,16 +2614,16 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +array-flatten@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" @@ -2796,7 +2791,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.22.1, browserslist@^4.22.2, browserslist@^4.23.0, "browserslist@>= 4.21.0": +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.22.1, browserslist@^4.22.2, browserslist@^4.23.0: version "4.23.1" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz" integrity sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw== @@ -3054,16 +3049,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colord@^2.9.3: version "2.9.3" resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" @@ -3429,27 +3424,20 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz" integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== -debug@^2.6.0: +debug@2.6.9, debug@^2.6.0: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@4: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - decode-named-character-reference@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz" @@ -3522,16 +3510,16 @@ del@^6.1.1: rimraf "^3.0.2" slash "^3.0.0" -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - depd@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + dequal@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" @@ -4025,7 +4013,7 @@ feed@^4.2.2: dependencies: xml-js "^1.6.11" -file-loader@*, file-loader@^6.2.0: +file-loader@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz" integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== @@ -4172,6 +4160,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -4314,7 +4307,7 @@ got@^12.1.0: p-cancelable "^3.0.0" responselike "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9, graceful-fs@4.2.10: +graceful-fs@4.2.10, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -4617,16 +4610,6 @@ http-deceiver@^1.2.7: resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -4638,6 +4621,16 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-parser-js@>=0.5.1: version "0.5.8" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" @@ -4741,7 +4734,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@2, inherits@2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4751,16 +4744,16 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - ini@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" @@ -4783,16 +4776,16 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ipaddr.js@^2.0.1: - version "2.1.0" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz" - integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +ipaddr.js@^2.0.1: + version "2.1.0" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz" + integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== + is-alphabetical@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz" @@ -4968,16 +4961,16 @@ is-yarn-global@^0.4.0: resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz" integrity sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ== -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isarray@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" @@ -5963,7 +5956,7 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" -"mime-db@>= 1.43.0 < 2": +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== @@ -5973,40 +5966,14 @@ mime-db@~1.33.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.27: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime-types@^2.1.31: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime-types@~2.1.17, mime-types@2.1.18: +mime-types@2.1.18, mime-types@~2.1.17: version "2.1.18" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz" integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== dependencies: mime-db "~1.33.0" -mime-types@~2.1.24: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime-types@~2.1.34: +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -6045,7 +6012,7 @@ minimalistic-assert@^1.0.0: resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@3.1.2: +minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -6416,13 +6383,6 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" @@ -6433,6 +6393,13 @@ path-to-regexp@2.2.1: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz" integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" @@ -6751,7 +6718,7 @@ postcss-zindex@^6.0.2: resolved "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz" integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== -"postcss@^7.0.0 || ^8.0.1", postcss@^8.0.9, postcss@^8.1.0, postcss@^8.2.2, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.31, postcss@^8.4.38: +postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.38: version "8.4.38" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz" integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== @@ -6799,7 +6766,7 @@ prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.2, prop-types@^15.7.2, "prop-types@>= 15.5.4": +prop-types@^15.6.2, prop-types@^15.7.2: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -6874,21 +6841,16 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - range-parser@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + raw-body@2.5.2: version "2.5.2" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" @@ -6939,7 +6901,7 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@*, "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, "react-dom@>= 15.0.0", "react-dom@>= 16.8.0 < 19.0.0": +react-dom@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -6990,7 +6952,7 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" -react-loadable@*, "react-loadable@npm:@docusaurus/react-loadable@6.0.0": +"react-loadable@npm:@docusaurus/react-loadable@6.0.0": version "6.0.0" resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz" integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ== @@ -7017,7 +6979,7 @@ react-router-dom@^5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@^5.3.4, react-router@>=5, react-router@5.3.4: +react-router@5.3.4, react-router@^5.3.4: version "5.3.4" resolved "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz" integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== @@ -7032,7 +6994,7 @@ react-router@^5.3.4, react-router@>=5, react-router@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react@*, "react@^16.13.1 || ^17.0.0 || ^18.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, "react@>= 15.0.0", "react@>= 16.8.0 < 19.0.0", react@>=15, react@>=16, react@>=16.0.0: +react@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -7335,20 +7297,15 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@^5.1.0, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== "safer-buffer@>= 2.1.2 < 3": version "2.1.2" @@ -7367,16 +7324,16 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" -schema-utils@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" -schema-utils@^3.1.1: +schema-utils@^3.0.0, schema-utils@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== @@ -7404,20 +7361,6 @@ schema-utils@^4.0.0, schema-utils@^4.0.1: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -"search-insights@>= 1 < 3": - version "2.14.0" - resolved "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz" - integrity sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw== - section-matter@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz" @@ -7672,7 +7615,7 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0: +source-map@^0.6.0, source-map@~0.6.0: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -7682,11 +7625,6 @@ source-map@^0.7.0: resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== -source-map@~0.6.0: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - space-separated-tokens@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz" @@ -7725,45 +7663,22 @@ srcset@^4.0.0: resolved "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz" integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - statuses@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + std-env@^3.0.1: version "3.3.1" resolved "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz" integrity sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q== -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -string-width@^4.1.0: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7781,6 +7696,20 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + stringify-entities@^4.0.0: version "4.0.4" resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz" @@ -8011,11 +7940,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -"typescript@>= 2.7", typescript@>=4.9.5: - version "5.2.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -8122,7 +8046,7 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unpipe@~1.0.0, unpipe@1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== @@ -8335,7 +8259,7 @@ webpack-sources@^3.2.3: resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -"webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", webpack@^5.0.0, webpack@^5.1.0, webpack@^5.20.0, webpack@^5.88.1, "webpack@>= 4", "webpack@>=4.41.1 || 5.x", webpack@>=5, "webpack@3 || 4 || 5": +webpack@^5.88.1: version "5.89.0" resolved "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz" integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== @@ -8375,7 +8299,7 @@ webpackbar@^5.0.2: pretty-time "^1.1.0" std-env "^3.0.1" -websocket-driver@^0.7.4, websocket-driver@>=0.5.1: +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== @@ -8440,14 +8364,14 @@ write-file-atomic@^3.0.3: typedarray-to-buffer "^3.1.5" ws@^7.3.1: - version "7.5.9" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.13.0: - version "8.14.2" - resolved "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xdg-basedir@^5.0.1, xdg-basedir@^5.1.0: version "5.1.0" From 8463098c57823e378ee4678aaf3447219eb51721 Mon Sep 17 00:00:00 2001 From: aurangzaibumer <35099184+aurangzaibumer@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:49:16 +0500 Subject: [PATCH 3/4] Cherry pick (#3243) | update kujaku dependency (#3282) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update kujaku dependency (#3243) * Mapview initializing fix * Add Mapbox SDK token for ci/cd * Add unit tests ✅ --------- Co-authored-by: Martin Ndegwa --- android/build.gradle.kts | 7 ++-- .../geowidget/screens/GeoWidgetFragment.kt | 42 ++++++++++++------- .../screens/GeoWidgetFragmentTest.kt | 27 +++++++++++- .../screens/GeoWidgetTestActivity.kt | 5 ++- android/gradle/libs.versions.toml | 4 +- android/mapbox.gradle.kts | 32 ++++++++++++++ 6 files changed, 94 insertions(+), 23 deletions(-) create mode 100644 android/mapbox.gradle.kts diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 7a27c04503..3dd868e56f 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -23,7 +23,6 @@ plugins { alias(libs.plugins.org.owasp.dependencycheck) alias(libs.plugins.com.diffplug.spotless) apply false alias(libs.plugins.android.junit5) apply false - } tasks.dokkaHtmlMultiModule { @@ -35,6 +34,8 @@ tasks.dokkaHtmlMultiModule { } } +apply(from = "mapbox.gradle.kts") + allprojects { repositories { gradlePluginPortal() @@ -42,14 +43,12 @@ allprojects { google() mavenCentral() maven(url = "https://oss.sonatype.org/content/repositories/snapshots") + maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots") maven(url = "https://jcenter.bintray.com/") apply(plugin = "org.owasp.dependencycheck") tasks.dependencyCheckAggregate{ dependencyCheck.formats.add("XML") } - configurations.all{ - resolutionStrategy.force ("com.google.android.gms:play-services-location:19.0.1") - } } } diff --git a/android/geowidget/src/main/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragment.kt b/android/geowidget/src/main/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragment.kt index e7c2f59587..ece0226525 100644 --- a/android/geowidget/src/main/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragment.kt +++ b/android/geowidget/src/main/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragment.kt @@ -34,6 +34,7 @@ import com.mapbox.geojson.MultiPoint import com.mapbox.geojson.Point import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.camera.CameraUpdateFactory +import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException import com.mapbox.mapboxsdk.geometry.LatLngBounds import com.mapbox.mapboxsdk.maps.Style import com.mapbox.mapboxsdk.style.expressions.Expression @@ -91,7 +92,13 @@ class GeoWidgetFragment : Fragment() { savedInstanceState: Bundle?, ): View { Mapbox.getInstance(requireContext(), BuildConfig.MAPBOX_SDK_TOKEN) - return setupViews() + val view = setupViews() + mapView.onCreate(savedInstanceState) + return view + } + + fun setKujakuMapview(kujakuMapView: KujakuMapView) { + mapView = kujakuMapView } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -124,24 +131,29 @@ class GeoWidgetFragment : Fragment() { } private fun setUpMapView(): KujakuMapView { - return KujakuMapView(requireActivity()).apply { - id = R.id.kujaku_widget - val builder = Style.Builder().fromUri(context.getString(R.string.style_map_fhir_core)) - getMapAsync { mapboxMap -> - mapboxMap.setStyle(builder) { style -> - geoJsonSource = style.getSourceAs(context.getString(R.string.data_set_quest)) - addIconsLayer(style) - addMapStyle(style) - if (geoJsonSource != null && featureCollection != null) { - geoJsonSource!!.setGeoJson(featureCollection) + return try { + KujakuMapView(requireActivity()).apply { + id = R.id.kujaku_widget + val builder = Style.Builder().fromUri(context.getString(R.string.style_map_fhir_core)) + getMapAsync { mapboxMap -> + mapboxMap.setStyle(builder) { style -> + geoJsonSource = style.getSourceAs(context.getString(R.string.data_set_quest)) + addIconsLayer(style) + addMapStyle(style) + if (geoJsonSource != null && featureCollection != null) { + geoJsonSource!!.setGeoJson(featureCollection) + } } } - } - if (showAddLocationButton) { - setOnAddLocationListener(this) + if (showAddLocationButton) { + setOnAddLocationListener(this) + } + setOnClickLocationListener(this) } - setOnClickLocationListener(this) + } catch (e: MapboxConfigurationException) { + Timber.e(e) + mapView } } diff --git a/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragmentTest.kt b/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragmentTest.kt index f0caf64fc1..7de4415728 100644 --- a/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragmentTest.kt +++ b/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetFragmentTest.kt @@ -17,6 +17,7 @@ package org.smartregister.fhircore.geowidget.screens import android.os.Build +import android.os.Bundle import com.mapbox.geojson.FeatureCollection import com.mapbox.mapboxsdk.style.sources.GeoJsonSource import dagger.hilt.android.testing.HiltAndroidRule @@ -75,7 +76,7 @@ class GeoWidgetFragmentTest { } @Test - fun test_add_and_cancel_location_points() { + fun testAddAndCancelLocationPoints() { // Mock dependencies val mockFeatureCollection = mockk(relaxed = true) val mockGeoJsonSource = mockk(relaxed = true) @@ -89,4 +90,28 @@ class GeoWidgetFragmentTest { // Verify mocks verify { kujakuMapView.addPoint(any(), any()) } } + + @Test + fun testOnCreateViewAddsSavedStateToMapView() { + val activity = Robolectric.buildActivity(GeoWidgetTestActivity::class.java).create().get() + + val geowidgetFragment = GeoWidgetFragment() + + var kujakuMapView = mockk(relaxed = true) + + geowidgetFragment.setKujakuMapview(kujakuMapView) + + activity.supportFragmentManager + .beginTransaction() + .add(android.R.id.content, geowidgetFragment, "") + .commitNow() + + every { kujakuMapView.parent } returns null + + val savedInstanceBundle: Bundle = mockk() + + geowidgetFragment.onCreateView(activity.layoutInflater, null, savedInstanceBundle) + + verify { kujakuMapView.onCreate(savedInstanceBundle) } + } } diff --git a/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetTestActivity.kt b/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetTestActivity.kt index 2a7f244a83..3a0ced7ad2 100644 --- a/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetTestActivity.kt +++ b/android/geowidget/src/test/java/org/smartregister/fhircore/geowidget/screens/GeoWidgetTestActivity.kt @@ -17,6 +17,9 @@ package org.smartregister.fhircore.geowidget.screens import androidx.appcompat.app.AppCompatActivity +import dagger.hilt.android.AndroidEntryPoint +import org.smartregister.fhircore.engine.util.annotation.ExcludeFromJacocoGeneratedReport -/** Created by Ephraim Kigamba - nek.eam@gmail.com on 22-08-2022. */ +@ExcludeFromJacocoGeneratedReport +@AndroidEntryPoint class GeoWidgetTestActivity : AppCompatActivity() diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 5fc75791b8..fcdb8a2e25 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -41,7 +41,7 @@ knowledge = "0.1.0-alpha03-preview5-SNAPSHOT" kotlin = "1.9.22" kotlinx-coroutines = "1.7.3" kotlinx-serialization-json = "1.6.0" -kujaku-library = "0.9.0" +kujaku-library = "0.10.2-SNAPSHOT" ktlint = "0.50.0" leakcanary-android = "2.10" lifecycle= "2.7.0" @@ -148,7 +148,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- ktlint-main = { group = "com.pinterest", name = "ktlint", version.ref = "ktlint"} ktlint-cli-ruleset = { group = "com.pinterest.ktlint", name = "ktlint-cli-ruleset-core", version.ref = "ktlint" } ktlint-rule-engine-core = { group = "com.pinterest.ktlint", name = "ktlint-rule-engine-core", version.ref = "ktlint" } -kujaku-library = { group = "io.ona.kujaku", name = "library", version.ref = "kujaku-library" } +kujaku-library = { group = "io.ona.kujaku", name ="library", version.ref = "kujaku-library" } leakcanary-android = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakcanary-android" } lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" } lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } diff --git a/android/mapbox.gradle.kts b/android/mapbox.gradle.kts new file mode 100644 index 0000000000..cee1cefe39 --- /dev/null +++ b/android/mapbox.gradle.kts @@ -0,0 +1,32 @@ +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.InputStreamReader +import java.util.Properties + +fun readProperties(file: String): Properties { + val properties = Properties() + val localProperties = File(file) + if (localProperties.isFile) { + InputStreamReader(FileInputStream(localProperties), Charsets.UTF_8).use { reader + -> + properties.load(reader) + } + } + else throw FileNotFoundException("\u001B[34mFile $file not found\u001B[0m") + + return properties +} + +val mapboxSdkToken = System.getenv("MAPBOX_SDK_TOKEN") ?: readProperties((project.properties["localPropertiesFile"] ?: "${rootProject.projectDir}/local.properties").toString()).getProperty("MAPBOX_SDK_TOKEN") + +allprojects { + repositories { + maven { + url = uri("https://api.mapbox.com/downloads/v2/releases/maven") + credentials.username = "mapbox" + credentials.password = mapboxSdkToken + authentication.create("basic") + } + } +} \ No newline at end of file From 742fdeb3f7cc55d7f0f428e32fe291525c36c404 Mon Sep 17 00:00:00 2001 From: Elly Kitoto Date: Thu, 20 Jun 2024 16:44:59 +0300 Subject: [PATCH 4/4] Fix app crash when navigation start destination is set to map (#3331) * Fix missing ID for launcher type Signed-off-by: Elly Kitoto * Fix setting start destination on nav graph Signed-off-by: Elly Kitoto * Fix failing tests Signed-off-by: Elly Kitoto * Fix failing ui test Signed-off-by: Elly Kitoto --------- Signed-off-by: Elly Kitoto Co-authored-by: Benjamin Mwalimu --- .../app/ApplicationConfiguration.kt | 6 +- .../app/NavigationStartDestinationConfig.kt | 36 +++++++ .../geowidget/GeoWidgetConfiguration.kt | 1 - .../ui/main/AppMainActivityTest.kt | 51 +++++----- .../quest/navigation/NavigationArg.kt | 11 --- .../ui/launcher/GeoWidgetLauncherFragment.kt | 28 ++---- .../fhircore/quest/ui/main/AppMainActivity.kt | 95 ++++++++++--------- .../questionnaire/QuestionnaireViewModel.kt | 8 +- .../src/main/res/layout/activity_main.xml | 13 +++ .../res/navigation/application_nav_graph.xml | 3 +- android/quest/src/main/res/values/ids.xml | 4 - .../quest/ui/main/AppMainActivityTest.kt | 3 +- .../quest/ui/profile/ProfileFragmentTest.kt | 2 - .../quest/ui/register/RegisterFragmentTest.kt | 2 - 14 files changed, 147 insertions(+), 116 deletions(-) create mode 100644 android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/NavigationStartDestinationConfig.kt create mode 100644 android/quest/src/main/res/layout/activity_main.xml delete mode 100644 android/quest/src/main/res/values/ids.xml diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt index 2270ef7136..ce6ce3763f 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/ApplicationConfiguration.kt @@ -51,7 +51,11 @@ data class ApplicationConfiguration( ), val logGpsLocation: List = emptyList(), val usePractitionerAssignedLocationOnSync: Boolean = true, - val navigationStartDestination: LauncherType = LauncherType.REGISTER, + val navigationStartDestination: NavigationStartDestinationConfig = + NavigationStartDestinationConfig( + launcherType = LauncherType.REGISTER, + id = null, + ), val codingSystems: List = emptyList(), ) : Configuration() diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/NavigationStartDestinationConfig.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/NavigationStartDestinationConfig.kt new file mode 100644 index 0000000000..a65fce6ada --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/app/NavigationStartDestinationConfig.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2024 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.engine.configuration.app + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable +import org.smartregister.fhircore.engine.domain.model.LauncherType + +/** + * This class configures the initial screen the application will be directed to upon launch. The + * application currently supports [LauncherType.REGISTER] and [LauncherType.MAP] screen as the entry + * point. This config defaults to launching a register with the rest of the properties obtained from + * the first NavigationItemConfig of the NavigationConfiguration + */ +@Serializable +@Parcelize +data class NavigationStartDestinationConfig( + val id: String? = null, + val screenTitle: String? = null, + val launcherType: LauncherType = LauncherType.REGISTER, +) : Parcelable diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/geowidget/GeoWidgetConfiguration.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/geowidget/GeoWidgetConfiguration.kt index 80630d48f4..3daadcc499 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/geowidget/GeoWidgetConfiguration.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/geowidget/GeoWidgetConfiguration.kt @@ -30,7 +30,6 @@ data class GeoWidgetConfiguration( override var appId: String, override var configType: String = ConfigType.GeoWidget.name, val id: String, - val profileId: String, val topScreenSection: TopScreenSectionConfig? = null, val registrationQuestionnaire: QuestionnaireConfig, val mapLayers: List = listOf(MapLayerConfig(MapLayer.STREET, true)), diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/AppMainActivityTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/AppMainActivityTest.kt index 09d44d2282..e69e102b66 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/AppMainActivityTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/AppMainActivityTest.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.core.os.bundleOf +import androidx.navigation.findNavController import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule @@ -94,7 +95,7 @@ class AppMainActivityTest { grantPermission() Assert.assertEquals( R.id.registerFragment, - it.navHostFragment.navController.currentDestination?.id, + it.findNavController(R.id.nav_host).currentDestination?.id, ) } composeTestRule.onNodeWithTag(REGISTER_SCREEN_BOX_TAG).assertIsDisplayed() @@ -105,7 +106,7 @@ class AppMainActivityTest { fun navigationToUserSettingFragmentShouldShowUserSettingsScreen() { composeTestRule.activityRule.scenario.onActivity { grantPermission() - it.navHostFragment.navController.navigate(R.id.userSettingFragment) + it.findNavController(R.id.nav_host).navigate(R.id.userSettingFragment) } composeTestRule.onNodeWithTag(USER_SETTING_ROW_LOGOUT).assertExists() @@ -119,21 +120,23 @@ class AppMainActivityTest { composeTestRule.activityRule.scenario.onActivity { grantPermission() - it.navHostFragment.navController.navigate( - R.id.profileFragment, - bundleOf( - NavigationArg.PROFILE_ID to "defaultProfile", - NavigationArg.RESOURCE_CONFIG to resourceConfig, - NavigationArg.PARAMS to - arrayOf( - ActionParameter( - key = "anyId", - paramType = ActionParameterType.PARAMDATA, - value = "anyValue", + it + .findNavController(R.id.nav_host) + .navigate( + R.id.profileFragment, + bundleOf( + NavigationArg.PROFILE_ID to "defaultProfile", + NavigationArg.RESOURCE_CONFIG to resourceConfig, + NavigationArg.PARAMS to + arrayOf( + ActionParameter( + key = "anyId", + paramType = ActionParameterType.PARAMDATA, + value = "anyValue", + ), ), - ), - ), - ) + ), + ) } composeTestRule.onNodeWithTag(PROFILE_TOP_BAR_TEST_TAG).assertIsDisplayed() @@ -144,13 +147,15 @@ class AppMainActivityTest { fun navigationToMeasureReportFragmentShouldShowMeasureReportScreen() { composeTestRule.activityRule.scenario.onActivity { grantPermission() - it.navHostFragment.navController.navigate( - R.id.measureReportFragment, - bundleOf( - NavigationArg.REPORT_ID to "serviceDeliveryMeasureReport", - NavigationArg.RESOURCE_ID to "", - ), - ) + it + .findNavController(R.id.nav_host) + .navigate( + R.id.measureReportFragment, + bundleOf( + NavigationArg.REPORT_ID to "serviceDeliveryMeasureReport", + NavigationArg.RESOURCE_ID to "", + ), + ) } composeTestRule.onNodeWithTag(SCREEN_TITLE).assertIsDisplayed() diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/NavigationArg.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/NavigationArg.kt index 313d3f5cb5..618bf1823b 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/NavigationArg.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/navigation/NavigationArg.kt @@ -24,19 +24,8 @@ object NavigationArg { const val RESOURCE_ID = "resourceId" const val RESOURCE_CONFIG = "resourceConfig" const val MULTI_SELECT_VIEW_CONFIG = "multiSelectViewConfig" - const val CONFIG_ID = "configId" const val GEO_WIDGET_ID = "geoWidgetId" - const val DETAILS_BOTTOM_SHEET_CONFIG = "detailsBottomSheetConfig" const val REPORT_ID = "reportId" const val PARAMS = "params" const val TOOL_BAR_HOME_NAVIGATION = "toolBarHomeNavigation" - const val LAUNCHER_TYPE = "launcherType" - - /** Create route paths */ - fun routePathsOf(vararg navArg: String): String = - "?" + navArg.toList().joinToString("&") { "$it={$it}" } - - /** Bind nav arguments values */ - fun bindArgumentsOf(vararg navArg: Pair): String = - "?" + navArg.joinToString("&") { "${it.first}=${it.second}" } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/launcher/GeoWidgetLauncherFragment.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/launcher/GeoWidgetLauncherFragment.kt index ef31618b30..f87cc90b20 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/launcher/GeoWidgetLauncherFragment.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/launcher/GeoWidgetLauncherFragment.kt @@ -74,32 +74,21 @@ import timber.log.Timber @AndroidEntryPoint class GeoWidgetLauncherFragment : Fragment() { + @Inject lateinit var eventBus: EventBus @Inject lateinit var configurationRegistry: ConfigurationRegistry private lateinit var geoWidgetFragment: GeoWidgetFragment + private lateinit var geoWidgetConfiguration: GeoWidgetConfiguration private val geoWidgetLauncherViewModel by viewModels() - private val args by navArgs() - private val geoWidgetConfiguration: GeoWidgetConfiguration by lazy { - configurationRegistry.retrieveConfiguration( - ConfigType.GeoWidget, - args.geoWidgetId, - emptyMap(), - ) - } + private val navArgs by navArgs() private val appMainViewModel by activityViewModels() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - Timber.i("GeoWidgetLauncherFragment onCreate") - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { - Timber.i("GeoWidgetLauncherFragment onCreateView") buildGeoWidgetFragment() return ComposeView(requireContext()).apply { @@ -158,7 +147,7 @@ class GeoWidgetLauncherFragment : Fragment() { openDrawer = openDrawer, onEvent = geoWidgetLauncherViewModel::onEvent, navController = findNavController(), - toolBarHomeNavigation = args.toolBarHomeNavigation, + toolBarHomeNavigation = navArgs.toolBarHomeNavigation, modifier = Modifier.fillMaxSize(), // Adjust the modifier as needed fragmentManager = childFragmentManager, fragment = fragment, @@ -181,6 +170,11 @@ class GeoWidgetLauncherFragment : Fragment() { } private fun buildGeoWidgetFragment() { + geoWidgetConfiguration = + configurationRegistry.retrieveConfiguration( + configType = ConfigType.GeoWidget, + configId = navArgs.geoWidgetId, + ) geoWidgetFragment = GeoWidgetFragment.builder() .setUseGpsOnAddingLocation(false) @@ -249,8 +243,4 @@ class GeoWidgetLauncherFragment : Fragment() { } } } - - companion object { - const val GEO_WIDGET_FRAGMENT_TAG = "geo-widget-fragment-tag" - } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt index f7b1b91067..1366dfff70 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt @@ -29,10 +29,9 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.compose.material.ExperimentalMaterialApi import androidx.core.os.bundleOf -import androidx.fragment.app.FragmentContainerView import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment -import com.google.android.fhir.FhirEngine import com.google.android.fhir.sync.CurrentSyncJobStatus import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices @@ -46,7 +45,6 @@ import kotlinx.coroutines.runBlocking import org.hl7.fhir.r4.model.IdType import org.hl7.fhir.r4.model.QuestionnaireResponse import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig -import org.smartregister.fhircore.engine.configuration.app.ConfigService import org.smartregister.fhircore.engine.configuration.app.LocationLogOptions import org.smartregister.fhircore.engine.configuration.app.SyncStrategy import org.smartregister.fhircore.engine.configuration.workflow.ActionTrigger @@ -55,10 +53,8 @@ import org.smartregister.fhircore.engine.datastore.syncLocationIdsProtoStore import org.smartregister.fhircore.engine.domain.model.LauncherType import org.smartregister.fhircore.engine.rulesengine.services.LocationCoordinate import org.smartregister.fhircore.engine.sync.OnSyncListener -import org.smartregister.fhircore.engine.sync.SyncBroadcaster import org.smartregister.fhircore.engine.sync.SyncListenerManager import org.smartregister.fhircore.engine.ui.base.BaseMultiLanguageActivity -import org.smartregister.fhircore.engine.util.DefaultDispatcherProvider import org.smartregister.fhircore.engine.util.extension.isDeviceOnline import org.smartregister.fhircore.engine.util.extension.parcelable import org.smartregister.fhircore.engine.util.extension.serializable @@ -78,20 +74,11 @@ import timber.log.Timber @ExperimentalMaterialApi open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, OnSyncListener { - @Inject lateinit var dispatcherProvider: DefaultDispatcherProvider - - @Inject lateinit var configService: ConfigService - @Inject lateinit var syncListenerManager: SyncListenerManager - @Inject lateinit var syncBroadcaster: SyncBroadcaster - - @Inject lateinit var fhirEngine: FhirEngine - @Inject lateinit var protoDataStore: ProtoDataStore @Inject lateinit var eventBus: EventBus - lateinit var navHostFragment: NavHostFragment val appMainViewModel by viewModels() private val sentryNavListener = SentryNavigationListener(enableNavigationBreadcrumbs = true, enableNavigationTracing = true) @@ -106,27 +93,56 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, } } + /** + * When the NavHostFragment is inflated using FragmentContainerView, if you attempt to use + * findNavController in the onCreate() of the Activity, the nav controller cannot be found. This + * is because when the fragment is inflated in the constructor of FragmentContainerView, the + * fragmentManager is in the INITIALIZING state, and therefore the added fragment only goes up to + * initializing. For the nav controller to be properly set, the fragment view needs to be created + * and onViewCreated() needs to be dispatched, which does not happen until the ACTIVITY_CREATED + * state. As a workaround retrieve the navController from the [NavHostFragment] + */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setupLocationServices() - setContentView(FragmentContainerView(this).apply { id = R.id.nav_host }) - val topMenuConfig = appMainViewModel.navigationConfiguration.clientRegisters.first() - val clickAction = topMenuConfig.actions?.find { it.trigger == ActionTrigger.ON_CLICK } - val topMenuConfigId = clickAction?.id ?: topMenuConfig.id - navHostFragment = - NavHostFragment.create( - R.navigation.application_nav_graph, - bundleOf( - NavigationArg.SCREEN_TITLE to topMenuConfig.display, - NavigationArg.REGISTER_ID to topMenuConfigId, - ), - ) + setContentView(R.layout.activity_main) - supportFragmentManager - .beginTransaction() - .replace(R.id.nav_host, navHostFragment) - .setPrimaryNavigationFragment(navHostFragment) - .commit() + val startDestinationConfig = + appMainViewModel.applicationConfiguration.navigationStartDestination + val startDestinationArgs = + when (startDestinationConfig.launcherType) { + LauncherType.REGISTER -> { + val topMenuConfig = appMainViewModel.navigationConfiguration.clientRegisters.first() + val clickAction = topMenuConfig.actions?.find { it.trigger == ActionTrigger.ON_CLICK } + bundleOf( + NavigationArg.SCREEN_TITLE to + if (startDestinationConfig.screenTitle.isNullOrEmpty()) { + topMenuConfig.display + } else startDestinationConfig.screenTitle, + NavigationArg.REGISTER_ID to + if (startDestinationConfig.id.isNullOrEmpty()) { + clickAction?.id ?: topMenuConfig.id + } else startDestinationConfig.id, + ) + } + LauncherType.MAP -> bundleOf(NavigationArg.GEO_WIDGET_ID to startDestinationConfig.id) + } + + // Retrieve the navController directly from the NavHostFragment + val navController = + (supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment).navController + + val graph = + navController.navInflater.inflate(R.navigation.application_nav_graph).apply { + val startDestination = + when (appMainViewModel.applicationConfiguration.navigationStartDestination.launcherType) { + LauncherType.MAP -> R.id.geoWidgetLauncherFragment + LauncherType.REGISTER -> R.id.registerFragment + } + setStartDestination(startDestination) + } + + navController.setGraph(graph, startDestinationArgs) // Register sync listener then run sync in that order syncListenerManager.registerSyncListener(this, lifecycle) @@ -160,25 +176,12 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, override fun onResume() { super.onResume() - // Create NavController after fragment has been attached - navHostFragment.apply { - val graph = - navController.navInflater.inflate(R.navigation.application_nav_graph).apply { - val startDestination = - when (appMainViewModel.applicationConfiguration.navigationStartDestination) { - LauncherType.MAP -> R.id.geoWidgetLauncherFragment - else -> R.id.registerFragment - } - setStartDestination(startDestination) - } - navController.addOnDestinationChangedListener(sentryNavListener) - navController.graph = graph - } + findNavController(R.id.nav_host).addOnDestinationChangedListener(sentryNavListener) } override fun onPause() { super.onPause() - navHostFragment.navController.removeOnDestinationChangedListener(sentryNavListener) + findNavController(R.id.nav_host).removeOnDestinationChangedListener(sentryNavListener) } override suspend fun onSubmitQuestionnaire(activityResult: ActivityResult) { diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt index 593f1981d7..08e9721af8 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt @@ -783,14 +783,14 @@ constructor( questionnaireResponse: QuestionnaireResponse, context: Context, ): Boolean { - val validQuestionnaireResponseItems = ArrayList() - val validQuestionnaireItems = ArrayList() - val questionnaireItemsMap = questionnaire.item.groupBy { it.linkId } + val validQuestionnaireResponseItems = mutableListOf() + val validQuestionnaireItems = mutableListOf() + val questionnaireItemsMap = questionnaire.item.associateBy { it.linkId } // Only validate items that are present on both Questionnaire and the QuestionnaireResponse questionnaireResponse.item.forEach { if (questionnaireItemsMap.containsKey(it.linkId)) { - val questionnaireItem = questionnaireItemsMap.getValue(it.linkId).first() + val questionnaireItem = questionnaireItemsMap.getValue(it.linkId) validQuestionnaireResponseItems.add(it) validQuestionnaireItems.add(questionnaireItem) } diff --git a/android/quest/src/main/res/layout/activity_main.xml b/android/quest/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..7be48fd5c7 --- /dev/null +++ b/android/quest/src/main/res/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/android/quest/src/main/res/navigation/application_nav_graph.xml b/android/quest/src/main/res/navigation/application_nav_graph.xml index f9a74d85a6..cbe1d0e3d8 100644 --- a/android/quest/src/main/res/navigation/application_nav_graph.xml +++ b/android/quest/src/main/res/navigation/application_nav_graph.xml @@ -9,8 +9,9 @@ android:name="org.smartregister.fhircore.quest.ui.launcher.GeoWidgetLauncherFragment"> + app:nullable="false" /> - - - diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt index 1e228a04e1..79848a593a 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt @@ -75,8 +75,7 @@ class AppMainActivityTest : ActivityRobolectricTest() { @Before fun setUp() { hiltRule.inject() - appMainActivity = - spyk(Robolectric.buildActivity(AppMainActivity::class.java).create().resume().get()) + appMainActivity = spyk(Robolectric.buildActivity(AppMainActivity::class.java).create().get()) every { appMainActivity.eventBus } returns eventBus } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt index 769c30111a..207e7b8f6d 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt @@ -20,7 +20,6 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.ui.platform.ComposeView import androidx.core.os.bundleOf import androidx.fragment.app.commitNow -import androidx.navigation.Navigation import androidx.navigation.testing.TestNavHostController import androidx.test.core.app.ApplicationProvider import dagger.hilt.android.testing.BindValue @@ -124,7 +123,6 @@ class ProfileFragmentTest : RobolectricTest() { // Simulate the returned value of loadProfile coEvery { registerRepository.loadProfileData(any(), any(), paramsList = emptyArray()) } returns RepositoryResourceData(resource = Faker.buildPatient()) - Navigation.setViewNavController(mainActivity.navHostFragment.requireView(), navController) mainActivity.supportFragmentManager.run { commitNow { add(profileFragment, ProfileFragment::class.java.simpleName) } executePendingTransactions() diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt index b665d865e6..e3c08fc1ed 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt @@ -20,7 +20,6 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.SnackbarDuration import androidx.core.os.bundleOf import androidx.fragment.app.commitNow -import androidx.navigation.Navigation import androidx.navigation.testing.TestNavHostController import com.google.android.fhir.sync.CurrentSyncJobStatus import com.google.android.fhir.sync.SyncJobStatus @@ -126,7 +125,6 @@ class RegisterFragmentTest : RobolectricTest() { TestNavHostController(mainActivity).apply { setGraph(org.smartregister.fhircore.quest.R.navigation.application_nav_graph) } - Navigation.setViewNavController(mainActivity.navHostFragment.requireView(), navController) mainActivity.supportFragmentManager.run { commitNow { add(registerFragment, RegisterFragment::class.java.simpleName) } executePendingTransactions()