Skip to content

Commit

Permalink
Merge pull request #12 from 0pen-dash/adrian/history-persistance
Browse files Browse the repository at this point in the history
history records / database
  • Loading branch information
bartsopers committed Feb 28, 2021
2 parents f8ca759 + 6dce871 commit 42279e1
Show file tree
Hide file tree
Showing 15 changed files with 415 additions and 4 deletions.
13 changes: 10 additions & 3 deletions gradle/test_dependencies.gradle
Expand Up @@ -13,9 +13,16 @@ dependencies {
testImplementation "org.skyscreamer:jsonassert:1.5.0"
testImplementation "org.hamcrest:hamcrest-all:1.3"

androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha04'
androidTestImplementation "androidx.test.ext:junit:$androidx_junit"
androidTestImplementation "androidx.test:rules:$androidx_rules"

// newer integration test libraries might not work
// https://stackoverflow.com/questions/64700104/attribute-androidforcequeryable-not-found-in-android-studio-when-running-espres
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'

androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'


androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
Expand Down
7 changes: 7 additions & 0 deletions omnipod-dash/build.gradle
Expand Up @@ -17,4 +17,11 @@ android {
dependencies {
implementation project(':core')
implementation project(':omnipod-common')

implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-rxjava2:$room_version"
kapt "androidx.room:room-compiler:$room_version"

implementation 'com.github.guepardoapps:kulid:1.1.2.0'

}
@@ -0,0 +1,72 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history

import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.github.guepardoapps.kulid.ULID
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper.HistoryMapper
import io.reactivex.schedulers.Schedulers
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class DashHistoryTest {

private lateinit var dao: HistoryRecordDao
private lateinit var database: DashHistoryDatabase
private lateinit var dashHistory: DashHistory

@get:Rule
val schedulerRule = RxSchedulerRule(Schedulers.trampoline())

@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(
context, DashHistoryDatabase::class.java).build()
dao = database.historyRecordDao()
dashHistory = DashHistory(dao, HistoryMapper())
}

@Test
fun testInsertionAndConverters() {
dashHistory.getRecords().test().apply {
assertValue { it.isEmpty() }
}

dashHistory.createRecord(commandType = OmnipodCommandType.CANCEL_BOLUS, 0L).test().apply {
assertValue { ULID.isValid(it) }
}

dashHistory.getRecords().test().apply {
assertValue { it.size == 1 }
}
}

@Test
fun testExceptionOnBolusWithoutRecord() {
dashHistory.getRecords().test().apply {
assertValue { it.isEmpty() }
}

dashHistory.createRecord(commandType = OmnipodCommandType.SET_BOLUS, 0L).test().apply {
assertError(IllegalArgumentException::class.java)
}

dashHistory.getRecords().test().apply {
assertValue { it.isEmpty() }
}
}

@After
fun tearDown() {
database.close()
}
}
@@ -0,0 +1,32 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history

import io.reactivex.Scheduler
import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.plugins.RxJavaPlugins
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

// TODO: move to core before the big merge
class RxSchedulerRule(val scheduler: Scheduler) : TestRule {

override fun apply(base: Statement, description: Description) =
object : Statement() {
override fun evaluate() {
RxAndroidPlugins.reset()
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
RxJavaPlugins.reset()
RxJavaPlugins.setIoSchedulerHandler { scheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
RxJavaPlugins.setComputationSchedulerHandler { scheduler }

try {
base.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}

}
}
}
@@ -0,0 +1,33 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.dagger

import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.Reusable
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.DashHistory
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper.HistoryMapper
import javax.inject.Singleton

@Module
class OmnipodDashHistoryModule {

@Provides
@Singleton
internal fun provideDatabase(context: Context): DashHistoryDatabase = DashHistoryDatabase.build(context)

@Provides
@Singleton
internal fun provideHistoryRecordDao(dashHistoryDatabase: DashHistoryDatabase): HistoryRecordDao = dashHistoryDatabase.historyRecordDao()

@Provides
@Reusable // no state, let system decide when to reuse or create new.
internal fun provideHistoryMapper() = HistoryMapper()

@Provides
@Singleton
internal fun provideDashHistory(dao: HistoryRecordDao, historyMapper: HistoryMapper) =
DashHistory(dao, historyMapper)

}
Expand Up @@ -16,7 +16,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.OmnipodDashOvervi
import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.activation.DashPodActivationWizardActivity
import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.deactivation.DashPodDeactivationWizardActivity

@Module
@Module(includes = [OmnipodDashHistoryModule::class])
@Suppress("unused")
abstract class OmnipodDashModule {
// ACTIVITIES
Expand Down
@@ -0,0 +1,69 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history

import com.github.guepardoapps.kulid.ULID
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_BOLUS
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_TEMPORARY_BASAL
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusRecord
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.HistoryRecord
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.TempBasalRecord
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordEntity
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.mapper.HistoryMapper
import io.reactivex.Completable
import io.reactivex.Single
import java.lang.System.currentTimeMillis
import javax.inject.Inject

class DashHistory @Inject constructor(
private val dao: HistoryRecordDao,
private val historyMapper: HistoryMapper
) {

fun markSuccess(id: String, date: Long): Completable = dao.markResolved(id, ResolvedResult.SUCCESS, currentTimeMillis())

fun markFailure(id: String, date: Long): Completable = dao.markResolved(id, ResolvedResult.FAILURE, currentTimeMillis())

fun createRecord(
commandType: OmnipodCommandType,
date: Long,
initialResult: InitialResult = InitialResult.UNCONFIRMED,
tempBasalRecord: TempBasalRecord? = null,
bolusRecord: BolusRecord? = null,
resolveResult: ResolvedResult? = null,
resolvedAt: Long? = null
): Single<String> {
val id = ULID.random()

when {
commandType == SET_BOLUS && bolusRecord == null ->
Single.error(IllegalArgumentException("bolusRecord missing on SET_BOLUS"))
commandType == SET_TEMPORARY_BASAL && tempBasalRecord == null ->
Single.error<String>(IllegalArgumentException("tempBasalRecord missing on SET_TEMPORARY_BASAL"))
else -> null
}?.let { return it }


return dao.save(
HistoryRecordEntity(
id = id,
date = date,
createdAt = currentTimeMillis(),
commandType = commandType,
tempBasalRecord = tempBasalRecord,
bolusRecord = bolusRecord,
initialResult = initialResult,
resolvedResult = resolveResult,
resolvedAt = resolvedAt,
)
).toSingle { id }
}

fun getRecords(): Single<List<HistoryRecord>> =
dao.all().map { list -> list.map(historyMapper::entityToDomain) }

fun getRecordsAfter(time: Long): Single<List<HistoryRecordEntity>> = dao.allSince(time)

}
@@ -0,0 +1,14 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data

import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType

data class HistoryRecord(
val id: String, // ULID
val createdAt: Long, // creation date of the record
val date: Long, // when event actually happened
val commandType: OmnipodCommandType,
val initialResult: InitialResult,
val record: Record?,
val resolvedResult: ResolvedResult?,
val resolvedAt: Long?
)
@@ -0,0 +1,11 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data

sealed class Record

data class BolusRecord(val amout: Double, val bolusType: BolusType): Record()

data class TempBasalRecord(val duration: Long, val rate: Double): Record()

enum class BolusType {
DEFAULT, SMB
}
@@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data

enum class InitialResult {
SUCCESS, FAILURE, UNCONFIRMED
}

enum class ResolvedResult {
SUCCESS, FAILURE
}
@@ -0,0 +1,34 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database

import androidx.room.TypeConverter
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult

class Converters {

@TypeConverter
fun toBolusType(s: String) = enumValueOf<BolusType>(s)

@TypeConverter
fun fromBolusType(bolusType: BolusType) = bolusType.name

@TypeConverter
fun toInitialResult(s: String) = enumValueOf<InitialResult>(s)

@TypeConverter
fun fromInitialResult(initialResult: InitialResult) = initialResult.name

@TypeConverter
fun toResolvedResult(s: String?) = s?.let { enumValueOf<ResolvedResult>(it) }

@TypeConverter
fun fromResolvedResult(resolvedResult: ResolvedResult?) = resolvedResult?.name

@TypeConverter
fun toOmnipodCommandType(s: String) = enumValueOf<OmnipodCommandType>(s)

@TypeConverter
fun fromOmnipodCommandType(omnipodCommandType: OmnipodCommandType) = omnipodCommandType.name
}
@@ -0,0 +1,30 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration

@Database(
entities = [HistoryRecordEntity::class],
exportSchema = false,
version = DashHistoryDatabase.VERSION
)
@TypeConverters(Converters::class)
abstract class DashHistoryDatabase : RoomDatabase() {

abstract fun historyRecordDao() : HistoryRecordDao

companion object {

const val VERSION = 1

fun build(context: Context) =
Room.databaseBuilder(context.applicationContext, DashHistoryDatabase::class.java, "omnipod_dash_history_database.db")
.fallbackToDestructiveMigration()
.build()
}

}

0 comments on commit 42279e1

Please sign in to comment.