diff --git a/app/schemas/com.x8bit.bitwarden.data.platform.datasource.disk.database.PlatformDatabase/2.json b/app/schemas/com.x8bit.bitwarden.data.platform.datasource.disk.database.PlatformDatabase/2.json new file mode 100644 index 0000000000..e8175ad2b8 --- /dev/null +++ b/app/schemas/com.x8bit.bitwarden.data.platform.datasource.disk.database.PlatformDatabase/2.json @@ -0,0 +1,70 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "2835802f9de260f6f5109c81081e9b46", + "entities": [ + { + "tableName": "organization_events", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_id` TEXT NOT NULL, `organization_event_type` TEXT NOT NULL, `cipher_id` TEXT, `date` INTEGER NOT NULL, `organization_id` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "organizationEventType", + "columnName": "organization_event_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cipherId", + "columnName": "cipher_id", + "affinity": "TEXT" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "organizationId", + "columnName": "organization_id", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_organization_events_user_id", + "unique": false, + "columnNames": [ + "user_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_organization_events_user_id` ON `${TABLE_NAME}` (`user_id`)" + } + ] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2835802f9de260f6f5109c81081e9b46')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceImpl.kt index e77b653898..d4e856acfc 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceImpl.kt @@ -30,6 +30,7 @@ class EventDiskSourceImpl( }, cipherId = event.cipherId, date = event.date, + organizationId = event.organizationId, ), ) } @@ -48,6 +49,7 @@ class EventDiskSourceImpl( }, cipherId = it.cipherId, date = it.date, + organizationId = it.organizationId, ) } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt index 712bb578fb..810de0bb2c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.database +import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters @@ -14,8 +15,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTyp entities = [ OrganizationEventEntity::class, ], - version = 1, + version = 2, exportSchema = true, + autoMigrations = [ + AutoMigration(from = 1, to = 2), + ], ) @TypeConverters(ZonedDateTimeTypeConverter::class) abstract class PlatformDatabase : RoomDatabase() { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt index af8684369e..16d09cd4ca 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt @@ -25,4 +25,7 @@ data class OrganizationEventEntity( @ColumnInfo(name = "date") val date: ZonedDateTime, + + @ColumnInfo(name = "organization_id") + val organizationId: String?, ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerImpl.kt index 603342c159..314a669710 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerImpl.kt @@ -79,6 +79,7 @@ class OrganizationEventManagerImpl( type = event.type, cipherId = event.cipherId, date = ZonedDateTime.now(clock), + organizationId = event.organizationId, ), ) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/OrganizationEvent.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/OrganizationEvent.kt index 4c40957771..4676a5162a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/OrganizationEvent.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/OrganizationEvent.kt @@ -16,11 +16,17 @@ sealed class OrganizationEvent { */ abstract val cipherId: String? + /** + * The optional organization ID. + */ + abstract val organizationId: String? + /** * Tracks when a value is successfully auto-filled */ data class CipherClientAutoFilled( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_AUTO_FILLED @@ -31,6 +37,7 @@ sealed class OrganizationEvent { */ data class CipherClientCopiedCardCode( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_COPIED_CARD_CODE @@ -41,6 +48,7 @@ sealed class OrganizationEvent { */ data class CipherClientCopiedHiddenField( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_COPIED_HIDDEN_FIELD @@ -51,6 +59,7 @@ sealed class OrganizationEvent { */ data class CipherClientCopiedPassword( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_COPIED_PASSWORD @@ -61,6 +70,7 @@ sealed class OrganizationEvent { */ data class CipherClientToggledCardCodeVisible( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_CARD_CODE_VISIBLE @@ -71,6 +81,7 @@ sealed class OrganizationEvent { */ data class CipherClientToggledCardNumberVisible( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_CARD_NUMBER_VISIBLE @@ -81,6 +92,7 @@ sealed class OrganizationEvent { */ data class CipherClientToggledHiddenFieldVisible( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_HIDDEN_FIELD_VISIBLE @@ -91,6 +103,7 @@ sealed class OrganizationEvent { */ data class CipherClientToggledPasswordVisible( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_PASSWORD_VISIBLE @@ -101,6 +114,7 @@ sealed class OrganizationEvent { */ data class CipherClientViewed( override val cipherId: String, + override val organizationId: String? = null, ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.CIPHER_CLIENT_VIEWED @@ -111,6 +125,7 @@ sealed class OrganizationEvent { */ data object UserClientExportedVault : OrganizationEvent() { override val cipherId: String? = null + override val organizationId: String? = null override val type: OrganizationEventType get() = OrganizationEventType.USER_CLIENT_EXPORTED_VAULT } @@ -119,8 +134,10 @@ sealed class OrganizationEvent { * Tracks when a user's personal ciphers have been migrated to their organization's My Items * folder as required by the organization's personal vault ownership policy. */ - data object ItemOrganizationAccepted : OrganizationEvent() { - override val cipherId: String? = null + data class ItemOrganizationAccepted( + override val cipherId: String? = null, + override val organizationId: String, + ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.ORGANIZATION_ITEM_ORGANIZATION_ACCEPTED } @@ -129,8 +146,10 @@ sealed class OrganizationEvent { * Tracks when a user chooses to leave an organization instead of migrating their personal * ciphers to their organization's My Items folder. */ - data object ItemOrganizationDeclined : OrganizationEvent() { - override val cipherId: String? = null + data class ItemOrganizationDeclined( + override val cipherId: String? = null, + override val organizationId: String, + ) : OrganizationEvent() { override val type: OrganizationEventType get() = OrganizationEventType.ORGANIZATION_ITEM_ORGANIZATION_DECLINED } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModel.kt index 0cd54702b3..5c71f56ff5 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModel.kt @@ -106,7 +106,9 @@ class LeaveOrganizationViewModel @Inject constructor( ), ) organizationEventManager.trackEvent( - event = OrganizationEvent.ItemOrganizationDeclined, + event = OrganizationEvent.ItemOrganizationDeclined( + organizationId = state.organizationId, + ), ) mutableStateFlow.update { it.copy(dialogState = null) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt index 10a1772c5f..4ec3261aec 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModel.kt @@ -139,7 +139,9 @@ class MigrateToMyItemsViewModel @Inject constructor( when (val result = action.result) { is MigratePersonalVaultResult.Success -> { organizationEventManager.trackEvent( - event = OrganizationEvent.ItemOrganizationAccepted, + event = OrganizationEvent.ItemOrganizationAccepted( + organizationId = state.organizationId, + ), ) clearDialog() sendEvent(MigrateToMyItemsEvent.NavigateToVault) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceTest.kt index 3088391831..51b4545e27 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/EventDiskSourceTest.kt @@ -39,6 +39,7 @@ class EventDiskSourceTest { type = OrganizationEventType.CIPHER_DELETED, cipherId = "cipherId-1", date = ZonedDateTime.now(fixedClock), + organizationId = null, ) eventDiskSource.addOrganizationEvent( @@ -54,6 +55,7 @@ class EventDiskSourceTest { organizationEventType = "1102", cipherId = "cipherId-1", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), ), fakeOrganizationEventDao.storedEvents, @@ -73,6 +75,7 @@ class EventDiskSourceTest { organizationEventType = "1102", cipherId = "cipherId-1", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), OrganizationEventEntity( id = 2, @@ -80,6 +83,7 @@ class EventDiskSourceTest { organizationEventType = "1102", cipherId = "cipherId-2", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), ), ) @@ -94,6 +98,7 @@ class EventDiskSourceTest { organizationEventType = "1102", cipherId = "cipherId-2", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), ), fakeOrganizationEventDao.storedEvents, @@ -113,6 +118,7 @@ class EventDiskSourceTest { organizationEventType = "1102", cipherId = "cipherId-1", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), OrganizationEventEntity( id = 2, @@ -120,6 +126,7 @@ class EventDiskSourceTest { organizationEventType = "1102", cipherId = "cipherId-2", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), ), ) @@ -132,6 +139,7 @@ class EventDiskSourceTest { type = OrganizationEventType.CIPHER_DELETED, cipherId = "cipherId-1", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), ), result, diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerTest.kt index df73f394c1..e36798de80 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/event/OrganizationEventManagerTest.kt @@ -74,6 +74,7 @@ class OrganizationEventManagerTest { type = OrganizationEventType.CIPHER_UPDATED, cipherId = CIPHER_ID, date = ZonedDateTime.now(fixedClock), + organizationId = null, ) val events = listOf(organizationEvent) coEvery { eventDiskSource.getOrganizationEvents(userId = USER_ID) } returns events @@ -105,6 +106,7 @@ class OrganizationEventManagerTest { type = OrganizationEventType.CIPHER_UPDATED, cipherId = CIPHER_ID, date = ZonedDateTime.now(fixedClock), + organizationId = null, ) val events = listOf(organizationEvent) coEvery { eventDiskSource.getOrganizationEvents(userId = USER_ID) } returns events @@ -209,6 +211,7 @@ class OrganizationEventManagerTest { type = OrganizationEventType.CIPHER_CLIENT_AUTO_FILLED, cipherId = CIPHER_ID, date = ZonedDateTime.now(fixedClock), + organizationId = null, ), ) } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModelTest.kt index 674ad09fb8..f388948b3a 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/leaveorganization/LeaveOrganizationViewModelTest.kt @@ -138,7 +138,9 @@ class LeaveOrganizationViewModelTest : BaseViewModelTest() { ), ) mockOrganizationEventManager.trackEvent( - event = OrganizationEvent.ItemOrganizationDeclined, + event = OrganizationEvent.ItemOrganizationDeclined( + organizationId = ORGANIZATION_ID, + ), ) mockVaultMigrationManager.clearMigrationState() } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt index 529b5800f3..b5da3ba675 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/migratetomyitems/MigrateToMyItemsViewModelTest.kt @@ -158,7 +158,9 @@ class MigrateToMyItemsViewModelTest : BaseViewModelTest() { verify { mockOrganizationEventManager.trackEvent( - event = OrganizationEvent.ItemOrganizationAccepted, + event = OrganizationEvent.ItemOrganizationAccepted( + organizationId = ORGANIZATION_ID, + ), ) } } diff --git a/network/src/main/kotlin/com/bitwarden/network/model/OrganizationEventJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/OrganizationEventJson.kt index 6e4ae74907..cf2e113170 100644 --- a/network/src/main/kotlin/com/bitwarden/network/model/OrganizationEventJson.kt +++ b/network/src/main/kotlin/com/bitwarden/network/model/OrganizationEventJson.kt @@ -13,4 +13,5 @@ data class OrganizationEventJson( @SerialName("type") val type: OrganizationEventType, @SerialName("cipherId") val cipherId: String?, @SerialName("date") @Contextual val date: ZonedDateTime, + @SerialName("organizationId") val organizationId: String?, ) diff --git a/network/src/test/kotlin/com/bitwarden/network/service/EventServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/EventServiceTest.kt index 67e2282011..5453835f3b 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/EventServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/EventServiceTest.kt @@ -35,6 +35,7 @@ class EventServiceTest : BaseServiceTest() { type = OrganizationEventType.CIPHER_CREATED, cipherId = "cipher-id", date = ZonedDateTime.now(fixedClock), + organizationId = null, ), ), )