Skip to content

Commit

Permalink
Add Launch and Submission Timestamps to QR (google#2672)
Browse files Browse the repository at this point in the history
* Implementation for launch and submission timestamps addition

* Update launch time stamp value to latest

* PR feedback changes

* Refactor QuestionnaireResponse assertion logic

* Run spotlessApply

---------

Co-authored-by: Simon Kiarie <[email protected]>
Co-authored-by: Jing Tang <[email protected]>
Co-authored-by: Simon Njoroge <[email protected]>
Co-authored-by: Benjamin Mwalimu <[email protected]>
  • Loading branch information
5 people authored Dec 1, 2024
1 parent 92ca237 commit f42e804
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.google.android.fhir.datacapture.extensions.isHelpCode
import com.google.android.fhir.datacapture.extensions.isHidden
import com.google.android.fhir.datacapture.extensions.isPaginated
import com.google.android.fhir.datacapture.extensions.isRepeatedGroup
import com.google.android.fhir.datacapture.extensions.launchTimestamp
import com.google.android.fhir.datacapture.extensions.localizedTextSpanned
import com.google.android.fhir.datacapture.extensions.maxValue
import com.google.android.fhir.datacapture.extensions.maxValueCqfCalculatedValueExpression
Expand All @@ -64,6 +65,7 @@ import com.google.android.fhir.datacapture.validation.Valid
import com.google.android.fhir.datacapture.validation.ValidationResult
import com.google.android.fhir.datacapture.views.QuestionTextConfiguration
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import java.util.Date
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -74,6 +76,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.withIndex
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent
import org.hl7.fhir.r4.model.QuestionnaireResponse
Expand Down Expand Up @@ -160,6 +163,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
.forEach { questionnaireResponse.addItem(it.createQuestionnaireResponseItem()) }
}
}
// Add extension for questionnaire launch time stamp
questionnaireResponse.launchTimestamp = DateTimeType(Date())
questionnaireResponse.packRepeatedGroups(questionnaire)
}

Expand Down Expand Up @@ -475,6 +480,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
)
.map { it.copy() }
unpackRepeatedGroups(this@QuestionnaireViewModel.questionnaire)
// Use authored as a submission time stamp
authored = Date()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@

package com.google.android.fhir.datacapture.extensions

import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse

internal const val EXTENSION_LAST_LAUNCHED_TIMESTAMP: String =
"http://github.com/google-android/questionnaire-lastLaunched-timestamp"

/** Pre-order list of all questionnaire response items in the questionnaire. */
val QuestionnaireResponse.allItems: List<QuestionnaireResponse.QuestionnaireResponseItemComponent>
get() = item.flatMap { it.descendant }
Expand Down Expand Up @@ -146,3 +151,20 @@ private fun unpackRepeatedGroups(
listOf(questionnaireResponseItem)
}
}

/**
* Adds a launch timestamp extension to the Questionnaire Response. If the extension @see
* EXTENSION_LAUNCH_TIMESTAMP already exists, it updates its value; otherwise, it adds a new one.
*/
internal var QuestionnaireResponse.launchTimestamp: DateTimeType?
get() {
val extension = this.extension.firstOrNull { it.url == EXTENSION_LAST_LAUNCHED_TIMESTAMP }
return extension?.value as? DateTimeType
}
set(value) {
extension.find { it.url == EXTENSION_LAST_LAUNCHED_TIMESTAMP }?.setValue(value)
?: run {
// Add a new extension if none exists
extension.add(Extension(EXTENSION_LAST_LAUNCHED_TIMESTAMP, value))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA
import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_RESPONSE_JSON_STRING
import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_QUESTIONNAIRE_RESPONSE_JSON_URI
import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.EXTRA_SHOW_REVIEW_PAGE_FIRST
import com.google.android.fhir.datacapture.extensions.EXTENSION_LAST_LAUNCHED_TIMESTAMP
import com.google.android.fhir.datacapture.testing.DataCaptureTestApplication
import com.google.common.truth.Truth.assertThat
import java.io.File
Expand Down Expand Up @@ -89,7 +90,7 @@ class QuestionnaireViewModelParameterizedTest(
val viewModel = createQuestionnaireViewModel(questionnaire)

runTest {
assertResourceEquals(
assertQuestionnaireResponseEqualsIgnoringTimestamps(
viewModel.getQuestionnaireResponse(),
QuestionnaireResponse().apply {
this.questionnaire = "http://www.sample-org/FHIR/Resources/Questionnaire/a-questionnaire"
Expand Down Expand Up @@ -135,7 +136,12 @@ class QuestionnaireViewModelParameterizedTest(

val viewModel = createQuestionnaireViewModel(questionnaire, questionnaireResponse)

runTest { assertResourceEquals(viewModel.getQuestionnaireResponse(), questionnaireResponse) }
runTest {
assertQuestionnaireResponseEqualsIgnoringTimestamps(
viewModel.getQuestionnaireResponse(),
questionnaireResponse,
)
}
}

private fun createQuestionnaireViewModel(
Expand Down Expand Up @@ -187,6 +193,28 @@ class QuestionnaireViewModelParameterizedTest(
.isEqualTo(printer.encodeResourceToString(expected))
}

/**
* Asserts that the `expected` and the `actual` Questionnaire Responses are equal ignoring the
* stamp values
*/
fun assertQuestionnaireResponseEqualsIgnoringTimestamps(
actual: QuestionnaireResponse,
expected: QuestionnaireResponse,
) {
val actualResponseWithoutTimestamp =
actual.copy().apply {
extension.removeIf { ext -> ext.url == EXTENSION_LAST_LAUNCHED_TIMESTAMP }
authored = null
}
val expectedResponseWithoutTimestamp =
expected.copy().apply {
extension.removeIf { ext -> ext.url == EXTENSION_LAST_LAUNCHED_TIMESTAMP }
authored = null
}
assertThat(printer.encodeResourceToString(actualResponseWithoutTimestamp))
.isEqualTo(printer.encodeResourceToString(expectedResponseWithoutTimestamp))
}

@JvmStatic
@Parameters
fun parameters() =
Expand Down
Loading

0 comments on commit f42e804

Please sign in to comment.