RAMP customizations for v1#28
Conversation
…g biometric search, always showing list if empty matches, dropping out to search form if SID refusal or error.
…nabled in the datastore management config
… where enabled via datastore management config; full width landscape view for the tables.
RAMP base branch: ramp-main
There was a problem hiding this comment.
Pull request overview
This PR introduces the first “RAMPcapture” customization layer on top of the ramp-main branch, focusing on non-biometric RAMP features while also refining the Simprints biometric identification UX. The main additions are datastore-driven “history” visualizations (ACF-1 charts and ACF-39/40/41 tables) and new navigation entries to surface them in event capture and TEI dashboards.
Changes:
- Add datastore-backed RAMP configuration (
simprints/ramp) plus metadata-sync download and caching. - Add form field “history chart” rendering and keep chart’s current value in sync while editing.
- Add “History” table view for configured program stages/enrollments, including navigation + landscape full-width behavior; refine biometric identification launch/no-match handling.
Reviewed changes
Copilot reviewed 73 out of 73 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tracker/src/commonMain/kotlin/org/dhis2/tracker/TEIDashboardItems.kt | Adds a new TEI dashboard navigation item for the RAMP history table. |
| README.md | Rebrands README to RAMPcapture and updates change references. |
| form/src/test/java/org/dhis2/form/ui/FormViewModelTest.kt | Adds coverage for updating chart-backed fields during text changes. |
| form/src/test/java/org/dhis2/form/simprints/ramp/data/GetFormHistoryChartUseCaseTest.kt | New unit test for chart use case behavior. |
| form/src/test/java/org/dhis2/form/simprints/ramp/data/FormHistoryChartRepositoryTest.kt | New tests validating chart data extraction and caching logic. |
| form/src/test/java/org/dhis2/form/data/RulesUtilsProviderImplTest.kt | Adds test ensuring ASSIGN persists values for unrendered fields. |
| form/src/test/java/org/dhis2/form/data/EventRepositoryTest.kt | Wires mocked chart use case into EventRepository tests. |
| form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt | Renders history chart under eligible fields. |
| form/src/main/java/org/dhis2/form/ui/FormViewModel.kt | Updates chart-backed field items in-place during TEXT_CHANGING. |
| form/src/main/java/org/dhis2/form/simprints/ramp/ui/FormHistoryChartView.kt | New Compose/AndroidView chart renderer using analytics charting. |
| form/src/main/java/org/dhis2/form/simprints/ramp/model/FormHistoryChart.kt | New model for field history chart data and “current value” updates. |
| form/src/main/java/org/dhis2/form/simprints/ramp/data/GetFormHistoryChartUseCase.kt | New use case to build chart data from datastore config. |
| form/src/main/java/org/dhis2/form/simprints/ramp/data/FormHistoryChartRepository.kt | New repository building chart series from follow-up visit events. |
| form/src/main/java/org/dhis2/form/model/SectionUiModelImpl.kt | Extends FieldUiModel contract with optional history chart property. |
| form/src/main/java/org/dhis2/form/model/FieldUiModelImpl.kt | Stores/updates history chart alongside field value updates. |
| form/src/main/java/org/dhis2/form/model/FieldUiModel.kt | Adds simprintsRampHistoryChart and setter to the model interface. |
| form/src/main/java/org/dhis2/form/di/Injector.kt | Wires chart use case + repositories into EventRepository creation. |
| form/src/main/java/org/dhis2/form/data/RulesUtilsProviderImpl.kt | Fixes ASSIGN fallback branch execution by using run { ... }. |
| form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt | Changes updateValueOnList to return the updated FieldUiModel. |
| form/src/main/java/org/dhis2/form/data/FormRepository.kt | Updates repository interface for updateValueOnList return type. |
| form/src/main/java/org/dhis2/form/data/EventRepository.kt | Attaches chart data to fields as they are created/updated. |
| form/build.gradle.kts | Adds analytics charting module dependency for new chart view. |
| commons/src/test/kotlin/org/dhis2/commons/simprints/utils/SimprintsIntentUtilsTest.kt | Adds tests for identification-result detection helper. |
| commons/src/test/kotlin/org/dhis2/commons/simprints/ramp/repository/RampDatastoreRepositoryTest.kt | New tests for datastore config parsing/sync behavior. |
| commons/src/main/java/org/dhis2/commons/simprints/utils/SimprintsIntentUtils.kt | Adds helper to detect identification result extra presence. |
| commons/src/main/java/org/dhis2/commons/simprints/ramp/repository/RampDatastoreRepository.kt | New datastore repo to load/parse/cache/sync simprints/ramp config. |
| commons/src/main/java/org/dhis2/commons/simprints/ramp/model/RampDatastoreConfig.kt | New config schema for chart/table feature enablement. |
| app/src/test/java/org/dhis2/usescases/teiDashboard/TeiDashboardPageConfiguratorTest.kt | Adds tests for history-tab visibility in TEI dashboard. |
| app/src/test/java/org/dhis2/usescases/teiDashboard/DashboardRepositoryImplTest.kt | Adds test ensuring history-table check failures return false. |
| app/src/test/java/org/dhis2/usescases/searchTrackEntity/SearchTEIViewModelTest.kt | Updates/extends tests for biometric search/no-match behavior and launch flow. |
| app/src/test/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfiguratorTest.kt | New tests for event-capture history-tab visibility. |
| app/src/test/java/org/dhis2/simprints/SimprintsMapBiometricSearchResultUseCaseTest.kt | New tests for mapping biometric identification activity results. |
| app/src/test/java/org/dhis2/simprints/ramp/ui/EventHistoryTableViewModelTest.kt | New tests for history table ViewModel UI states. |
| app/src/test/java/org/dhis2/simprints/ramp/data/GetEventHistoryTableUseCaseTest.kt | New tests for history table use case success/failure wrapping. |
| app/src/test/java/org/dhis2/simprints/ramp/data/EventHistoryTableRepositoryTest.kt | New tests for table generation, duplicates, and dashboard context. |
| app/src/test/java/org/dhis2/data/services/SyncPresenterTest.kt | Adds test verifying RAMP datastore sync is triggered during metadata sync. |
| app/src/main/res/values/strings.xml | Adds strings for new History nav and table labels; removes old no-matches message. |
| app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardPageConfigurator.kt | Shows History tab based on configured RAMP history-table availability. |
| app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt | Adds History fragment navigation and landscape full-width behavior. |
| app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardComponent.java | Adds Dagger subcomponent for history table feature injection. |
| app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt | Adds History nav item when enabled by page configurator. |
| app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardRepositoryImpl.kt | Implements config-backed check for enrollment history table availability. |
| app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardRepository.kt | Adds repository API for checking RAMP history table availability. |
| app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEModule.java | Adds DI for new biometric result mapping use case and mapper. |
| app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTeiViewModelFactory.kt | Injects biometric result mapping use case into SearchTEIViewModel. |
| app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEIViewModel.kt | Implements biometric auto-launch, no-match empty result handling, and result mapping integration. |
| app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEActivity.kt | Uses new onSearchFormRequested() flow instead of forcing search screen. |
| app/src/main/java/org/dhis2/usescases/searchTrackEntity/searchparameters/SearchParametersScreen.kt | Moves result mapping out of UI; supports ViewModel-driven biometric launch. |
| app/src/main/java/org/dhis2/usescases/searchTrackEntity/mapView/SearchTEMap.kt | Uses new onSearchFormRequested() behavior from map UI. |
| app/src/main/java/org/dhis2/usescases/searchTrackEntity/listView/SearchTEList.kt | Uses new onSearchFormRequested() behavior from list UI. |
| app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt | Enables History tab via repository capability check. |
| app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java | Adds config-backed check for program stage history table presence. |
| app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt | Adds History nav item + forces Data Entry visibility for landscape table fullscreen. |
| app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePagerAdapter.kt | Adds History table page, maps it to navigation, and improves index safety. |
| app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.kt | Extends contract for History availability + presenter behavior toggle. |
| app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureComponent.java | Adds Dagger subcomponent for history table injection. |
| app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt | Adds History navigation handling and landscape fullscreen layout switching. |
| app/src/main/java/org/dhis2/simprints/SimprintsMapBiometricSearchResultUseCase.kt | New use case to map intent results and persist session IDs when appropriate. |
| app/src/main/java/org/dhis2/simprints/ramp/ui/EventHistoryTableViewModelFactory.kt | New ViewModel factory for History table screen. |
| app/src/main/java/org/dhis2/simprints/ramp/ui/EventHistoryTableViewModel.kt | New ViewModel driving table loading and UI state transitions. |
| app/src/main/java/org/dhis2/simprints/ramp/ui/EventHistoryTableScreen.kt | New Compose UI for rendering the History table/empty/error/loading states. |
| app/src/main/java/org/dhis2/simprints/ramp/ui/EventHistoryTableFragment.kt | New fragment hosting the table Compose UI with DI-based setup. |
| app/src/main/java/org/dhis2/simprints/ramp/model/EventHistoryTableModels.kt | New models/UI state for History tables. |
| app/src/main/java/org/dhis2/simprints/ramp/di/EventHistoryTableModule.kt | New Dagger module providing repository/use case/ViewModel factory. |
| app/src/main/java/org/dhis2/simprints/ramp/di/EventHistoryTableComponent.kt | New Dagger subcomponent for fragment injection. |
| app/src/main/java/org/dhis2/simprints/ramp/data/GetEventHistoryTableUseCase.kt | New use case wrapping repository table building in Result. |
| app/src/main/java/org/dhis2/simprints/ramp/data/EventHistoryTableRepository.kt | New repository building History table model from D2 + datastore config. |
| app/src/main/java/org/dhis2/data/service/SyncPresenterImpl.kt | Syncs RAMP datastore namespace after metadata download. |
| app/src/main/java/org/dhis2/data/service/SyncMetadataWorkerModule.kt | Injects RampDatastoreRepository into SyncPresenterImpl. |
| app/src/main/java/org/dhis2/data/service/SyncInitWorkerModule.kt | Injects RampDatastoreRepository into SyncPresenterImpl. |
| app/src/main/java/org/dhis2/data/service/SyncGranularRxModule.kt | Injects RampDatastoreRepository into SyncPresenterImpl. |
| app/src/main/java/org/dhis2/data/service/SyncDataWorkerModule.kt | Injects RampDatastoreRepository into SyncPresenterImpl. |
| .github/workflows/rampcapture-github-release-signed-apk.yml | Adds workflow to build and publish signed APK releases on ramp-main. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .download(), | ||
| ), | ||
| ).andThen( | ||
| Completable.fromAction { simprintsRampDatastoreRepository.sync() }, |
There was a problem hiding this comment.
This is why I really don't miss RxJava haha. So I'm pretty sure if this crashes out it will bring down the whole sync completable. It needs and on error complete:
Completable
.fromAction { simprintsRampDatastoreRepository.sync() }
.doOnError { Timber.e(it, "Error syncing Simprints RAMP datastore") }
.onErrorComplete(),
Also the matching test for this is the happy path only we should have a regression test:
@Test
fun `syncMetadata completes when RAMP datastore sync throws`() {
// arrange happy-path mocks for metadata/maps/fileResource downloads...
whenever(simprintsRampDatastoreRepository.sync())
.thenThrow(RuntimeException("network down"))
// act + assert: should not throw
presenter.syncMetadata { /* progress */ }
verify(simprintsRampDatastoreRepository).sync()
}
^ Or something like that.
There was a problem hiding this comment.
I see that some (not all) sync errors are "swallowed" by onErrorComplete in the upstream code above this section, but I think it's more correct not to do so here: a failure to sync simprints->ramp datastore is still a configuration sync failure. I've added a log line, and a test case for this.
If an Exception is thrown here, it will be caught in the SyncMetadataWorker, and that will also propagate to the UI as a sync error message if it's a manual sync. I've checked by putting a delay at the start of simprintsRampDatastoreRepository.sync() during which I disconnected the network, and by putting a RuntimeException there in another build as well - the worker does catch these. Also the RAMP sync is an ordinary d2 call, so the reliability (against something like OOM) is just the same as in the rest of the codebase.
| private fun handleOnTextChangeAction(action: RowAction): StoreResult { | ||
| repository.updateValueOnList(action.id, action.value, action.valueType) | ||
| updateSimprintsRampHistoryChart( | ||
| repository.updateValueOnList(action.id, action.value, action.valueType), |
There was a problem hiding this comment.
This will make every item in the _items recompose on each key stroke (because of postValue). Have you confirmed this isn't janky? Not sure how many items will be in there.
There was a problem hiding this comment.
This was for the graph updating as-user-types, and it performed well, but indeed the recomposes ended up broader than needed. Reimplemented this a bit and added the test case against those items emission, thanks for noticing. UX remains the same.
| val isScrollingDown = MutableLiveData(false) | ||
| val simprintsBiometricSearchNavigation: Flow<Unit> = | ||
| simprintsSearchViewModel.simprintsBiometricSearchNavigation | ||
| private val _simprintsBiometricIdentificationLaunch = Channel<Unit>(Channel.BUFFERED) |
There was a problem hiding this comment.
I wouldn't make this BUFFERED, because it means that it will queue and then make items hot again unexpectedly (which when doing UI stuff is bad). "Channel.BUFFERED retains events across screen lifecycles"
Maybe use a SharedFlow(replay = 0) here because if it fires and no one is listening it goes away. Otherwise someone could tap twice, or tap, hit back, and when the lifecycle starts again the intent fires unexpectedly.
|
|
||
| override fun displayNotes(): Boolean = true | ||
|
|
||
| override fun displayTableView(): Boolean = eventCaptureRepository.hasSimprintsRampProgramStageHistoryTable() |
There was a problem hiding this comment.
Double check calls to this functions aren't running on the main thread, because gson init and sdk reads are pretty heavy.
There was a problem hiding this comment.
This line (for the Table tab visibility only) does execute on main, but just as the functions above do for the sibling tabs. Making this suspend would cascade into upstream code changes, probably disproportionally to the benefit gained. My call is: we keep this fitting into the existing pattern here, rather than increasing the fork diff size for maintenance just for purity of this call. I've still fixed the repeated contructions of the repo & Gson.
The function that actually contructs the table is already called in IO thread so we are good at the actual heavy part.
… errors, test case.
…tory pre-initialized
This is the v1 of RAMPcapture, a further customization to SimCapture, mostly with non-biometric features.
The base branch is
ramp-main.Changes: