Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,6 @@ class AdherenceRecordRepo(httpClient: HttpClient,
}
}


// TODO: Remove once this method is not longer being used by ScheduleTimelineRep -nbrown 10/17/22
/**
* Get the locally cached [AdherenceRecord]s for the specified [instanceIds].
*/
@Deprecated("Uses old resource based cache, needs replaced with new implementation")
fun getCachedAdherenceRecords(instanceIds: List<String>, studyId: String) : Map<String, List<AdherenceRecord>> {
val list: List<AdherenceRecord> = database.getResourcesByIds(instanceIds, ResourceType.ADHERENCE_RECORD, studyId).mapNotNull { it.loadResource() }
return list.groupBy { it.instanceGuid }
}

/**
* Download and cache locally all [AdherenceRecord]s for the given study.
* For now this only requests records using the current timestamps.
Expand All @@ -117,7 +106,6 @@ class AdherenceRecordRepo(httpClient: HttpClient,
}

suspend fun processAdherenceRecordUpdates(studyId: String) {
processUpdates(studyId)
processUpdatesV2(studyId)
}

Expand All @@ -130,31 +118,21 @@ class AdherenceRecordRepo(httpClient: HttpClient,
}

private fun insertUpdate(studyId: String, adherenceRecord: AdherenceRecord, needSave: Boolean) {
//TODO: Don't overwrite records that have needSave=true -nbrown 10/10/22
val json = Json.encodeToString(adherenceRecord)
//TODO: removing writing to resource table -nbrown 10/17/22
val resource = Resource(
identifier = adherenceRecord.instanceGuid,
secondaryId = adherenceRecord.startedOn.toString(),
type = ResourceType.ADHERENCE_RECORD,
studyId = studyId,
json = json,
lastUpdate = Clock.System.now().toEpochMilliseconds(),
status = ResourceStatus.SUCCESS,
needSave = false)
database.insertUpdateResource(resource)
//Insert into new adherence specific table
dbQuery.insertUpdateAdherenceRecord(
studyId = studyId,
instanceGuid = adherenceRecord.instanceGuid,
startedOn = adherenceRecord.startedOn.toString(),
finishedOn = adherenceRecord.finishedOn?.toString(),
declined = adherenceRecord.declined,
adherenceEventTimestamp = adherenceRecord.eventTimestamp,
adherenceJson = Json.encodeToString(adherenceRecord),
status = ResourceStatus.SUCCESS,
needSave = needSave
)
// Check that update is a local change or will not overwrite local change
if (needSave || dbQuery.getAdherence(adherenceRecord.instanceGuid, adherenceRecord.startedOn.toString()).executeAsOneOrNull()?.needSave != true) {
//Insert into new adherence specific table
dbQuery.insertUpdateAdherenceRecord(
studyId = studyId,
instanceGuid = adherenceRecord.instanceGuid,
startedOn = adherenceRecord.startedOn.toString(),
finishedOn = adherenceRecord.finishedOn?.toString(),
declined = adherenceRecord.declined,
adherenceEventTimestamp = adherenceRecord.eventTimestamp,
adherenceJson = Json.encodeToString(adherenceRecord),
status = ResourceStatus.SUCCESS,
needSave = needSave
)
}

}

Expand All @@ -167,67 +145,10 @@ class AdherenceRecordRepo(httpClient: HttpClient,
Logger.i("Updating adherence record:${adherenceRecord.instanceGuid}, finished:${adherenceRecord.finishedOn}")
insertUpdate(studyId, record, needSave = true)
backgroundScope.launch {
processUpdates(studyId)
processUpdatesV2(studyId)
}
}

/**
* Save any locally cached [AdherenceRecord]s that haven't been uploaded to Bridge server.
* Any failures will remain in a needSave state and be tried again the next time.
*/
//TODO: Remove in future release -nbrown 10/17/22
private suspend fun processUpdates(studyId: String) {
val resourcesToUpload = database.getResourcesNeedSave(ResourceType.ADHERENCE_RECORD, studyId)
if (resourcesToUpload.isEmpty()) return
val chunkedList = resourcesToUpload.chunked(25)
for (resources in chunkedList) {
processUpdates(studyId, resources)
}
}

private suspend fun processUpdates(studyId: String, resourcesToUpload: List<Resource>) {
val records: List<AdherenceRecord> = resourcesToUpload.mapNotNull { it.loadResource() }
if (records.isEmpty()) return
var status = ResourceStatus.FAILED
var needSave = true
try {
scheduleV2Api.updateAdherenceRecords(studyId, records)
status = ResourceStatus.SUCCESS
needSave = false
} catch (throwable: Throwable) {
println(throwable)
when (throwable) {
is ResponseException -> {
when (throwable.response.status) {
HttpStatusCode.Unauthorized -> {
// 401 unauthorized
// User hasn't authenticated or re-auth has failed.
}
HttpStatusCode.Gone -> {
//410 Gone
// The version of the client making the request no longer has access to this service.
// The user must update their app in order to continue using Bridge.
}
HttpStatusCode.PreconditionFailed -> {
//412 Not consented
}
}
}

is UnresolvedAddressException -> {
//Internet connection error
status = ResourceStatus.RETRY
}
}
}
for (resource in resourcesToUpload) {
val toUpdate = resource.copy(status = status, needSave = needSave)
database.insertUpdateResource(toUpdate)
}

}

/**
* Save any locally cached [AdherenceRecord]s that haven't been uploaded to Bridge server.
* Any failures will remain in a needSave state and be tried again the next time.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package org.sagebionetworks.bridge.kmm.shared.repo

import io.ktor.client.engine.config
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.respondError
import io.ktor.client.engine.mock.respondOk
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.datetime.TimeZone
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonPrimitive
import org.sagebionetworks.bridge.kmm.shared.BaseTest
import org.sagebionetworks.bridge.kmm.shared.cache.ResourceDatabaseHelper
import org.sagebionetworks.bridge.kmm.shared.cache.ResourceStatus
import org.sagebionetworks.bridge.kmm.shared.cache.ResourceType
import org.sagebionetworks.bridge.kmm.shared.getJsonReponseHandler
import org.sagebionetworks.bridge.kmm.shared.getTestClient
import org.sagebionetworks.bridge.kmm.shared.models.UploadFile
import org.sagebionetworks.bridge.kmm.shared.models.UploadMetadata
Expand Down Expand Up @@ -44,23 +56,61 @@ class AdherenceRecordRepoTest: BaseTest() {
runTest {
val studyId = "testId"
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
val repo = AdherenceRecordRepo(getTestClient(json), null, ResourceDatabaseHelper(testDatabaseDriver()), scope)
val mockEngine = MockEngine.config {
// 1 - Load adherence call
addHandler (
getJsonReponseHandler(json)
)
// 2 - Process updates call needs to fail so we still have pending local changes
addHandler { respondError(HttpStatusCode.GatewayTimeout) }
// 3 - Second Load adherence call
addHandler (
getJsonReponseHandler(json)
)
reuseHandlers = false
}
val testClient = getTestClient(mockEngine)

val repo = AdherenceRecordRepo(testClient, null, ResourceDatabaseHelper(testDatabaseDriver()), scope)

assertTrue(repo.loadRemoteAdherenceRecords(studyId))
val adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn)
var adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn)
assertNotNull(adherenceRecord)

val recordMap = repo.getCachedAdherenceRecords(listOf(instanceGuid), studyId)
val recordMap = repo.getAllCachedAdherenceRecords(studyId).first()
assertEquals(1, recordMap.size)
val recordList = recordMap.get(instanceGuid)
val recordList = recordMap[instanceGuid]
assertNotNull(recordList)
val record = recordList.get(0)
val record = recordList[0]
assertNotNull(record)
assertEquals(instanceGuid, record.instanceGuid)

val updatedRecord = record.copy(clientData = JsonPrimitive("Test data"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change this to set the clientData as a JsonObject rather than a primitive? Since we need to be able to merge scheduling data that we set when updating the start times with app data that the app uses, this is always going to be an object. Thanks.

repo.database.database.participantScheduleQueries.insertUpdateAdherenceRecord(
studyId = studyId,
instanceGuid = updatedRecord.instanceGuid,
startedOn = updatedRecord.startedOn.toString(),
finishedOn = updatedRecord.finishedOn?.toString(),
declined = updatedRecord.declined,
adherenceEventTimestamp = updatedRecord.eventTimestamp,
adherenceJson = Json.encodeToString(updatedRecord),
status = ResourceStatus.SUCCESS,
needSave = true
)
var dbRecord = repo.database.database.participantScheduleQueries.getAdherence(instanceGuid, startedOn).executeAsOne()
assertTrue(dbRecord.needSave)
adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn)
assertEquals("\"Test data\"", adherenceRecord!!.clientData.toString())

// Trigger a load of data
assertTrue(repo.loadRemoteAdherenceRecords(studyId))
// Check that local changes did not get overwritten
dbRecord = repo.database.database.participantScheduleQueries.getAdherence(instanceGuid, startedOn).executeAsOne()
assertTrue(dbRecord.needSave)
adherenceRecord = repo.getCachedAdherenceRecord(instanceGuid, startedOn)
assertEquals("\"Test data\"", adherenceRecord!!.clientData.toString())

val db = repo.database
val resource = db.getResource(instanceGuid, ResourceType.ADHERENCE_RECORD, studyId)
assertNotNull(resource)
assertFalse(resource.needSave)
db.clearDatabase()
}
}
Expand Down