Skip to content

Commit

Permalink
[MWCore] Tracing updates (#3017)
Browse files Browse the repository at this point in the history
* Update QuestionnaireViewModel.kt

* clean up settings page

* update tracing profile

* clean up logs

* fix task order tag filter

* fix tests

* update tests

* updates

* Update deps.gradle

* add extra tests
  • Loading branch information
sevenreup authored Jan 31, 2024
1 parent 96d01d0 commit f151a02
Show file tree
Hide file tree
Showing 15 changed files with 244 additions and 85 deletions.
2 changes: 1 addition & 1 deletion android/deps.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ versions.truth = '1.0.1'
versions.work = '2.7.1'
versions.json_tools = '1.13'
versions.kotlin_coveralls = '2.12.2'
versions.jacoco_tool = '0.8.7'
versions.jacoco_tool = '0.8.11'
versions.ktlint = '0.41.0'
versions.joda_time = '2.10.5'
versions.timber = '4.7.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ constructor(
const val COUNT = "count"
const val DEFAULT_COUNT = "100"
const val DEFAULT_TASK_FILTER_TAG_META_CODING_SYSTEM = "https://d-tree.org/fhir/task-filter-tag"
const val DEFAULT_TASK_ORDER_FILTER_TAG_META_CODING_SYSTEM = "https://d-tree.org"
const val TYPE_REFERENCE_DELIMITER = "/"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import kotlinx.serialization.Serializable
import org.smartregister.fhircore.engine.configuration.Configuration
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry.Companion.DEFAULT_TASK_FILTER_TAG_META_CODING_SYSTEM
import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry.Companion.DEFAULT_TASK_ORDER_FILTER_TAG_META_CODING_SYSTEM

@Serializable
data class ApplicationConfiguration(
Expand All @@ -31,8 +32,10 @@ data class ApplicationConfiguration(
var scheduleDefaultPlanWorker: Boolean = true,
var applicationName: String = "",
var appLogoIconResourceFile: String = "ic_default_logo",
var patientTypeFilterTagViaMetaCodingSystem: String = "",
var count: String = ConfigurationRegistry.DEFAULT_COUNT,
var patientTypeFilterTagViaMetaCodingSystem: String = "",
var taskOrderFilterTagViaMetaCodingSystem: String =
DEFAULT_TASK_ORDER_FILTER_TAG_META_CODING_SYSTEM,
var taskFilterTagViaMetaCodingSystem: String = DEFAULT_TASK_FILTER_TAG_META_CODING_SYSTEM
) : Configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,6 @@ constructor(
patient.meta.tag.none { it.code.equals(HAPI_MDM_TAG, true) } &&
patient.belongsTo(code)

init {
Timber.e(code)
}
fun hivPatientIdentifier(patient: Patient): String =
// would either be an ART or HCC number
patient.extractOfficialIdentifier() ?: ResourceValue.BLANK
Expand Down Expand Up @@ -150,7 +147,7 @@ constructor(

override suspend fun loadProfileData(appFeatureName: String?, resourceId: String): ProfileData {
val patient = defaultRepository.loadResource<Patient>(resourceId)!!
val metaCodingSystemTag = getApplicationConfiguration().patientTypeFilterTagViaMetaCodingSystem
val configuration = getApplicationConfiguration()

return ProfileData.HivProfileData(
logicalId = patient.logicalId,
Expand All @@ -168,13 +165,17 @@ constructor(
phoneContacts = patient.extractTelecom(),
chwAssigned = patient.generalPractitionerFirstRep,
showIdentifierInProfile = true,
healthStatus = patient.extractHealthStatusFromMeta(metaCodingSystemTag),
healthStatus =
patient.extractHealthStatusFromMeta(configuration.patientTypeFilterTagViaMetaCodingSystem),
tasks =
patient
.activeTasks()
.sortedWith(
compareBy<Task>(
{ it.clinicVisitOrder(metaCodingSystemTag) ?: Double.MAX_VALUE },
compareBy(
{
it.clinicVisitOrder(configuration.taskOrderFilterTagViaMetaCodingSystem)
?: Double.MAX_VALUE
},
// tasks with no clinicVisitOrder, would be sorted with Task#description
{ it.description }
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2021 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.domain.util

sealed class DataLoadState<out T> {
object Loading : DataLoadState<Nothing>()
data class Success<T>(val data: T) : DataLoadState<T>()
data class Error(val exception: Exception) : DataLoadState<Nothing>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ constructor(
val resources = getPopulationResources(intent, questionnaire.logicalId)
val questResponse = ResourceMapper.populate(questionnaire, *resources)
questResponse.contained = resources.toList()
questResponse.questionnaire = "${questionnaire.resourceType}/${questionnaire.logicalId}"
return questResponse
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.Chip
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
Expand All @@ -39,60 +40,71 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.smartregister.fhircore.engine.domain.util.DataLoadState
import org.smartregister.fhircore.engine.ui.theme.BlueTextColor
import org.smartregister.fhircore.engine.ui.theme.LighterBlue
import org.smartregister.fhircore.engine.util.annotation.ExcludeFromJacocoGeneratedReport

@ExcludeFromJacocoGeneratedReport
@Composable
fun InfoCard(viewModel: SettingsViewModel) {
val data by viewModel.data.observeAsState()
val state by viewModel.profileData.observeAsState()

if (data != null) {
val username = data!!.userName
if (!username.isNullOrEmpty()) {
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp)) {
Box(
modifier = Modifier.clip(CircleShape).background(color = LighterBlue).size(80.dp),
contentAlignment = Alignment.Center
) {
when (state) {
is DataLoadState.Loading ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) { CircularProgressIndicator(Modifier.testTag("ProgressBarItem")) }
is DataLoadState.Error -> Column { Text(text = "Something went wrong while fetching data..") }
is DataLoadState.Success -> {
val data = (state as DataLoadState.Success<ProfileData>).data
val username = data.userName
if (username.isNotEmpty()) {
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp)) {
Box(
modifier = Modifier.clip(CircleShape).background(color = LighterBlue).size(80.dp),
contentAlignment = Alignment.Center
) {
Text(
text = username.first().uppercase(),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 28.sp,
color = BlueTextColor
)
}
Text(
text = username.first().uppercase(),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 28.sp,
color = BlueTextColor
text = username.capitalize(Locale.current),
fontSize = 22.sp,
modifier = Modifier.padding(vertical = 22.dp),
fontWeight = FontWeight.Bold
)
}
Text(
text = username.capitalize(Locale.current),
fontSize = 22.sp,
modifier = Modifier.padding(vertical = 22.dp),
fontWeight = FontWeight.Bold
)
}
}
Card(Modifier.padding(6.dp)) {
Column(Modifier.padding(10.dp)) {
generateData(data!!).forEach {
Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
Text(text = it.key, style = MaterialTheme.typography.h6)
Box(modifier = Modifier.height(8.dp))
it.value.map { FieldCard(it) }
Card(Modifier.padding(6.dp)) {
Column(Modifier.padding(10.dp)) {
generateData(data).forEach {
Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
Text(text = it.key, style = MaterialTheme.typography.h6)
Box(modifier = Modifier.height(8.dp))
it.value.map { FieldCard(it) }
}
}
}
}
}
else -> {}
}
}

@ExcludeFromJacocoGeneratedReport
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FieldCard(fieldData: FieldData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.smartregister.fhircore.engine.data.local.DefaultRepository
import org.smartregister.fhircore.engine.data.remote.auth.KeycloakService
import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceService
import org.smartregister.fhircore.engine.domain.model.Language
import org.smartregister.fhircore.engine.domain.util.DataLoadState
import org.smartregister.fhircore.engine.sync.SyncBroadcaster
import org.smartregister.fhircore.engine.ui.login.LoginActivity
import org.smartregister.fhircore.engine.util.LOGGED_IN_PRACTITIONER
Expand Down Expand Up @@ -66,55 +67,69 @@ constructor(

val language = MutableLiveData<Language?>(null)

val data = MutableLiveData<ProfileData>()
val profileData = MutableLiveData<DataLoadState<ProfileData>>()

init {
viewModelScope.launch @ExcludeFromJacocoGeneratedReport { fetchData() }
}

private suspend fun fetchData() {
var practitionerName: String? = null
sharedPreferences.read(key = SharedPreferenceKey.PRACTITIONER_ID.name, defaultValue = null)
?.let {
val practitioner = fhirEngine.get(ResourceType.Practitioner, it) as Practitioner
practitionerName = practitioner.nameFirstRep.nameAsSingleString
}

val organizationIds =
sharedPreferences.read<List<String>>(
key = ResourceType.Organization.name,
decodeWithGson = true
)
?.map {
val resource = (fhirEngine.get(ResourceType.Organization, it) as Organization)
FieldData(resource.logicalId, resource.name)
}

val locationIds =
sharedPreferences.read<List<String>>(key = ResourceType.Location.name, decodeWithGson = true)
?.map {
val resource = (fhirEngine.get(ResourceType.Location, it) as Location)
FieldData(resource.logicalId, resource.name)
try {
profileData.value = DataLoadState.Loading

var practitionerName: String? = null
sharedPreferences.read(key = SharedPreferenceKey.PRACTITIONER_ID.name, defaultValue = null)
?.let {
val practitioner = fhirEngine.get(ResourceType.Practitioner, it) as Practitioner
practitionerName = practitioner.nameFirstRep.nameAsSingleString
}

val careTeamIds =
sharedPreferences.read<List<String>>(key = ResourceType.CareTeam.name, decodeWithGson = true)
?.map {
val resource = (fhirEngine.get(ResourceType.CareTeam, it) as CareTeam)
FieldData(resource.logicalId, resource.name)
}

val isValid = organizationIds != null || locationIds != null || careTeamIds != null

data.value =
ProfileData(
userName = practitionerName ?: "",
organisations = organizationIds ?: listOf(),
locations = locationIds ?: listOf(),
careTeams = careTeamIds ?: listOf(),
isUserValid = isValid,
practitionerDetails = null
)
val organizationIds =
sharedPreferences.read<List<String>>(
key = ResourceType.Organization.name,
decodeWithGson = true
)
?.map {
val resource = (fhirEngine.get(ResourceType.Organization, it) as Organization)
FieldData(resource.logicalId, resource.name)
}

val locationIds =
sharedPreferences.read<List<String>>(
key = ResourceType.Location.name,
decodeWithGson = true
)
?.map {
val resource = (fhirEngine.get(ResourceType.Location, it) as Location)
FieldData(resource.logicalId, resource.name)
}

val careTeamIds =
sharedPreferences.read<List<String>>(
key = ResourceType.CareTeam.name,
decodeWithGson = true
)
?.map {
val resource = (fhirEngine.get(ResourceType.CareTeam, it) as CareTeam)
FieldData(resource.logicalId, resource.name)
}

val isValid = organizationIds != null || locationIds != null || careTeamIds != null

profileData.value =
DataLoadState.Success(
ProfileData(
userName = practitionerName ?: "",
organisations = organizationIds ?: listOf(),
locations = locationIds ?: listOf(),
careTeams = careTeamIds ?: listOf(),
isUserValid = isValid,
practitionerDetails = null
)
)
} catch (e: Exception) {
profileData.value = DataLoadState.Error(e)
}
}

fun runSync() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import java.util.Calendar
import java.util.Date
import javax.inject.Inject
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
Expand Down Expand Up @@ -360,6 +361,22 @@ internal class HivRegisterDaoTest : RobolectricTest() {
assertEquals(HealthStatus.EXPOSED_INFANT, hivProfileData.healthStatus)
assertEquals(Enumerations.AdministrativeGender.MALE, hivProfileData.gender)
}
@Test
fun testProfileTaskList() {
val data = runBlocking {
hivRegisterDao.loadProfileData(appFeatureName = "HIV", resourceId = "1")
}
assertNotNull(data)
val hivProfileData = data as ProfileData.HivProfileData
val order = hivProfileData.tasks.none { it.clinicVisitOrder("https://d-tree.org") == null }
Assert.assertEquals(hivProfileData.tasks.isEmpty(), false)
val sorted = hivProfileData.tasks.sortedWith(compareBy { it.description }).isEmpty()
val sortedIds = hivProfileData.tasks.map { it.id }

assertFalse(sorted)
assertEquals(order, true)
assertEquals(listOf("2", "1"), sortedIds)
}

@Test
fun `loadGuardiansRegisterData returns from relatedPersons`() = runTest {
Expand Down
Loading

0 comments on commit f151a02

Please sign in to comment.