Skip to content

RAMP customizations for v1#28

Merged
alex-vt merged 12 commits into
ramp-mainfrom
ramp-customizations-v1
Jun 3, 2026
Merged

RAMP customizations for v1#28
alex-vt merged 12 commits into
ramp-mainfrom
ramp-customizations-v1

Conversation

@alex-vt
Copy link
Copy Markdown
Collaborator

@alex-vt alex-vt commented Jun 2, 2026

This is the v1 of RAMPcapture, a further customization to SimCapture, mostly with non-biometric features.

The base branch is ramp-main.

Changes:

  • Biometrics behavior adjustments: biometric search is the default/automatic search in eligible programs; a biometric search error or refusal cause an automatic redirect to non-biometric sequential search (a regular search parameters screen opens).
  • ACF-1 history charts under eligible fields (as configured via Datastore Management)
  • ACF-39,40,41 history table tabs in eligible program enrolments and program stages (as configured via Datastore Management; sharing the same namespace and visit numbering logic with ACF-1)

alex-vt added 5 commits June 1, 2026 17:34
…g biometric search, always showing list if empty matches, dropping out to search form if SID refusal or error.
… where enabled via datastore management config; full width landscape view for the tables.
RAMP base branch: ramp-main
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

@TristramN TristramN left a comment

Choose a reason for hiding this comment

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

Thanks @alex-vt! Left a few comments, as before I've approved so you can keep moving but grab me if you want me to re-check anything!

.download(),
),
).andThen(
Completable.fromAction { simprintsRampDatastoreRepository.sync() },
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Double check calls to this functions aren't running on the main thread, because gson init and sdk reads are pretty heavy.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

@alex-vt alex-vt merged commit 8a576a6 into ramp-main Jun 3, 2026
@alex-vt alex-vt deleted the ramp-customizations-v1 branch June 3, 2026 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants