Add analytics for Room Moderation. (#2571)

* Add an option to use analytics locally.

* Add analytics for Room Moderation.

* Update tests.

* Include the role in the event where appropriate.

* Update the AnalyticsEvents package.
This commit is contained in:
Doug
2024-03-14 15:55:37 +00:00
committed by GitHub
parent d2a9db874d
commit c68ec2c382
26 changed files with 185 additions and 60 deletions
+5 -1
View File
@@ -524,6 +524,7 @@
8358D145F9BF94F412BEDCA8 /* RoomRolesAndPermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */; }; 8358D145F9BF94F412BEDCA8 /* RoomRolesAndPermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE7969EBCAF078813E18EA1 /* RoomRolesAndPermissionsScreenModels.swift */; };
835B7AD20407F766C747BEC5 /* RoomPollsHistoryScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D147EB979902DBBE452EADC /* RoomPollsHistoryScreenUITests.swift */; }; 835B7AD20407F766C747BEC5 /* RoomPollsHistoryScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D147EB979902DBBE452EADC /* RoomPollsHistoryScreenUITests.swift */; };
83A4DAB181C56987C3E804FF /* MapTilerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */; }; 83A4DAB181C56987C3E804FF /* MapTilerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */; };
83B17A44D3E7E6DF22D9A2A4 /* RoomModerationRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */; };
84226AD2E1F1FBC965F3B09E /* UnitTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E19C4645D3F5F9FB02355 /* UnitTestsAppCoordinator.swift */; }; 84226AD2E1F1FBC965F3B09E /* UnitTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E19C4645D3F5F9FB02355 /* UnitTestsAppCoordinator.swift */; };
8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */; }; 8478992479B296C45150208F /* AppLockScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */; };
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */; }; 847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1D4A6D451F43A03CACD01D /* PINTextField.swift */; };
@@ -1141,6 +1142,7 @@
0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; }; 0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; };
0A634D8DD1E10D858CF7995D /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = "<group>"; }; 0A634D8DD1E10D858CF7995D /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = "<group>"; };
0AE449DFBA7CC863EEB2FD2A /* FormattingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingToolbar.swift; sourceTree = "<group>"; }; 0AE449DFBA7CC863EEB2FD2A /* FormattingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingToolbar.swift; sourceTree = "<group>"; };
0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomModerationRole.swift; sourceTree = "<group>"; };
0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = "<group>"; }; 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = "<group>"; };
0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModel.swift; sourceTree = "<group>"; }; 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModel.swift; sourceTree = "<group>"; };
0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSenderAvatarView.swift; sourceTree = "<group>"; }; 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSenderAvatarView.swift; sourceTree = "<group>"; };
@@ -2789,6 +2791,7 @@
children = ( children = (
B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */, B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */,
CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */, CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */,
0B32BBA8887BD7A5C4ECF16F /* RoomModerationRole.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -6060,6 +6063,7 @@
F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */, F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */,
C4078364FD9FA00EA9D00A15 /* RoomMembersListScreenViewModelProtocol.swift in Sources */, C4078364FD9FA00EA9D00A15 /* RoomMembersListScreenViewModelProtocol.swift in Sources */,
D5E771132BB36240DE38102F /* RoomMessageEventStringBuilder.swift in Sources */, D5E771132BB36240DE38102F /* RoomMessageEventStringBuilder.swift in Sources */,
83B17A44D3E7E6DF22D9A2A4 /* RoomModerationRole.swift in Sources */,
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */, C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */,
0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */, 0180C44B997EDA8D21F883AC /* RoomNotificationSettingsCustomSectionView.swift in Sources */,
CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */, CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */,
@@ -7124,7 +7128,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-analytics-events"; repositoryURL = "https://github.com/matrix-org/matrix-analytics-events";
requirement = { requirement = {
kind = upToNextMinorVersion; kind = upToNextMinorVersion;
minimumVersion = 0.11.0; minimumVersion = 0.13.0;
}; };
}; };
C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */ = { C13F55E4518415CB4C278E73 /* XCRemoteSwiftPackageReference "DTCoreText" */ = {
@@ -121,8 +121,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-analytics-events", "location" : "https://github.com/matrix-org/matrix-analytics-events",
"state" : { "state" : {
"revision" : "354562b5cabf2b9aec6cbb12e3a614490b3cc6e8", "revision" : "ccc4af6aa00987abe7135fa0b7cea97c8cfb3d26",
"version" : "0.11.0" "version" : "0.13.0"
} }
}, },
{ {
@@ -592,10 +592,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
fatalError() fatalError()
} }
let params = RoomMembersListScreenCoordinatorParameters(mediaProvider: userSession.mediaProvider, let parameters = RoomMembersListScreenCoordinatorParameters(mediaProvider: userSession.mediaProvider,
roomProxy: roomProxy, roomProxy: roomProxy,
appSettings: appSettings) userIndicatorController: userIndicatorController,
let coordinator = RoomMembersListScreenCoordinator(parameters: params) appSettings: appSettings,
analytics: analytics)
let coordinator = RoomMembersListScreenCoordinator(parameters: parameters)
coordinator.actions coordinator.actions
.sink { [weak self] action in .sink { [weak self] action in
@@ -1176,7 +1178,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
let parameters = RoomRolesAndPermissionsFlowCoordinatorParameters(roomProxy: roomProxy, let parameters = RoomRolesAndPermissionsFlowCoordinatorParameters(roomProxy: roomProxy,
navigationStackCoordinator: navigationStackCoordinator, navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: userIndicatorController) userIndicatorController: userIndicatorController,
analytics: analytics)
let coordinator = RoomRolesAndPermissionsFlowCoordinator(parameters: parameters) let coordinator = RoomRolesAndPermissionsFlowCoordinator(parameters: parameters)
coordinator.actionsPublisher.sink { [weak self] action in coordinator.actionsPublisher.sink { [weak self] action in
switch action { switch action {
@@ -27,12 +27,14 @@ struct RoomRolesAndPermissionsFlowCoordinatorParameters {
let roomProxy: RoomProxyProtocol let roomProxy: RoomProxyProtocol
let navigationStackCoordinator: NavigationStackCoordinator let navigationStackCoordinator: NavigationStackCoordinator
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
} }
class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol { class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
private let roomProxy: RoomProxyProtocol private let roomProxy: RoomProxyProtocol
private let navigationStackCoordinator: NavigationStackCoordinator private let navigationStackCoordinator: NavigationStackCoordinator
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
enum State: StateType { enum State: StateType {
/// The state machine hasn't started. /// The state machine hasn't started.
@@ -74,6 +76,7 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
roomProxy = parameters.roomProxy roomProxy = parameters.roomProxy
navigationStackCoordinator = parameters.navigationStackCoordinator navigationStackCoordinator = parameters.navigationStackCoordinator
userIndicatorController = parameters.userIndicatorController userIndicatorController = parameters.userIndicatorController
analytics = parameters.analytics
stateMachine = .init(state: .initial) stateMachine = .init(state: .initial)
configureStateMachine() configureStateMachine()
@@ -132,7 +135,9 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
} }
private func presentRolesAndPermissionsScreen() { private func presentRolesAndPermissionsScreen() {
let parameters = RoomRolesAndPermissionsScreenCoordinatorParameters(roomProxy: roomProxy, userIndicatorController: userIndicatorController) let parameters = RoomRolesAndPermissionsScreenCoordinatorParameters(roomProxy: roomProxy,
userIndicatorController: userIndicatorController,
analytics: analytics)
let coordinator = RoomRolesAndPermissionsScreenCoordinator(parameters: parameters) let coordinator = RoomRolesAndPermissionsScreenCoordinator(parameters: parameters)
coordinator.actionsPublisher.sink { [stateMachine] action in coordinator.actionsPublisher.sink { [stateMachine] action in
switch action { switch action {
@@ -159,7 +164,8 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
let parameters = RoomChangeRolesScreenCoordinatorParameters(mode: mode, let parameters = RoomChangeRolesScreenCoordinatorParameters(mode: mode,
roomProxy: roomProxy, roomProxy: roomProxy,
userIndicatorController: userIndicatorController) userIndicatorController: userIndicatorController,
analytics: analytics)
let coordinator = RoomChangeRolesScreenCoordinator(parameters: parameters) let coordinator = RoomChangeRolesScreenCoordinator(parameters: parameters)
coordinator.actionsPublisher.sink { [weak self] action in coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return } guard let self else { return }
@@ -180,7 +186,8 @@ class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
let parameters = RoomChangePermissionsScreenCoordinatorParameters(permissions: permissions, let parameters = RoomChangePermissionsScreenCoordinatorParameters(permissions: permissions,
permissionsGroup: group, permissionsGroup: group,
roomProxy: roomProxy, roomProxy: roomProxy,
userIndicatorController: userIndicatorController) userIndicatorController: userIndicatorController,
analytics: analytics)
let coordinator = RoomChangePermissionsScreenCoordinator(parameters: parameters) let coordinator = RoomChangePermissionsScreenCoordinator(parameters: parameters)
coordinator.actionsPublisher.sink { [weak self] action in coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return } guard let self else { return }
@@ -24,6 +24,7 @@ struct RoomChangePermissionsScreenCoordinatorParameters {
let permissionsGroup: RoomRolesAndPermissionsScreenPermissionsGroup let permissionsGroup: RoomRolesAndPermissionsScreenPermissionsGroup
let roomProxy: RoomProxyProtocol let roomProxy: RoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
} }
enum RoomChangePermissionsScreenCoordinatorAction { enum RoomChangePermissionsScreenCoordinatorAction {
@@ -46,7 +47,8 @@ final class RoomChangePermissionsScreenCoordinator: CoordinatorProtocol {
viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: parameters.permissions, viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: parameters.permissions,
group: parameters.permissionsGroup, group: parameters.permissionsGroup,
roomProxy: parameters.roomProxy, roomProxy: parameters.roomProxy,
userIndicatorController: parameters.userIndicatorController) userIndicatorController: parameters.userIndicatorController,
analytics: parameters.analytics)
} }
func start() { func start() {
@@ -23,8 +23,9 @@ typealias RoomChangePermissionsScreenViewModelType = StateStoreViewModel<RoomCha
class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModelType, RoomChangePermissionsScreenViewModelProtocol { class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModelType, RoomChangePermissionsScreenViewModelProtocol {
private let roomProxy: RoomProxyProtocol private let roomProxy: RoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private var actionsSubject: PassthroughSubject<RoomChangePermissionsScreenViewModelAction, Never> = .init() private let analytics: AnalyticsService
private var actionsSubject: PassthroughSubject<RoomChangePermissionsScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<RoomChangePermissionsScreenViewModelAction, Never> { var actionsPublisher: AnyPublisher<RoomChangePermissionsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
@@ -32,9 +33,11 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel
init(currentPermissions: RoomPermissions, init(currentPermissions: RoomPermissions,
group: RoomRolesAndPermissionsScreenPermissionsGroup, group: RoomRolesAndPermissionsScreenPermissionsGroup,
roomProxy: RoomProxyProtocol, roomProxy: RoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol) { userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService) {
self.roomProxy = roomProxy self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.analytics = analytics
super.init(initialViewState: .init(currentPermissions: currentPermissions, group: group)) super.init(initialViewState: .init(currentPermissions: currentPermissions, group: group))
} }
@@ -64,13 +67,15 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel
} }
var changes = RoomPowerLevelChanges() var changes = RoomPowerLevelChanges()
for setting in state.bindings.settings { let changedSettings = state.bindings.settings.filter { state.currentPermissions[keyPath: $0.keyPath] != $0.value }
for setting in changedSettings {
changes[keyPath: setting.rustKeyPath] = setting.value.rustPowerLevel changes[keyPath: setting.rustKeyPath] = setting.value.rustPowerLevel
} }
switch await roomProxy.applyPowerLevelChanges(changes) { switch await roomProxy.applyPowerLevelChanges(changes) {
case .success: case .success:
MXLog.info("Success") MXLog.info("Success")
trackChanges(changedSettings)
actionsSubject.send(.complete) actionsSubject.send(.complete)
case .failure: case .failure:
context.alertInfo = AlertInfo(id: .generic) context.alertInfo = AlertInfo(id: .generic)
@@ -92,4 +97,22 @@ class RoomChangePermissionsScreenViewModel: RoomChangePermissionsScreenViewModel
private func hideLoadingIndicator() { private func hideLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.indicatorID) userIndicatorController.retractIndicatorWithId(Self.indicatorID)
} }
// MARK: Analytics
private func trackChanges(_ settings: [RoomPermissionsSetting]) {
for setting in settings {
switch setting.keyPath {
case \.ban: analytics.trackRoomModeration(action: .ChangePermissionsBanMembers, role: setting.value)
case \.invite: analytics.trackRoomModeration(action: .ChangePermissionsInviteUsers, role: setting.value)
case \.kick: analytics.trackRoomModeration(action: .ChangePermissionsKickMembers, role: setting.value)
case \.redact: analytics.trackRoomModeration(action: .ChangePermissionsRedactMessages, role: setting.value)
case \.eventsDefault: analytics.trackRoomModeration(action: .ChangePermissionsSendMessages, role: setting.value)
case \.roomName: analytics.trackRoomModeration(action: .ChangePermissionsRoomName, role: setting.value)
case \.roomAvatar: analytics.trackRoomModeration(action: .ChangePermissionsRoomAvatar, role: setting.value)
case \.roomTopic: analytics.trackRoomModeration(action: .ChangePermissionsRoomTopic, role: setting.value)
default: MXLog.warning("Unexpected change: \(setting.keyPath).")
}
}
}
} }
@@ -78,6 +78,7 @@ struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview {
RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock), RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock),
group: group, group: group,
roomProxy: RoomProxyMock(with: .init()), roomProxy: RoomProxyMock(with: .init()),
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
} }
} }
@@ -23,6 +23,7 @@ struct RoomChangeRolesScreenCoordinatorParameters {
let mode: RoomMemberDetails.Role let mode: RoomMemberDetails.Role
let roomProxy: RoomProxyProtocol let roomProxy: RoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
} }
enum RoomChangeRolesScreenCoordinatorAction { enum RoomChangeRolesScreenCoordinatorAction {
@@ -45,7 +46,8 @@ final class RoomChangeRolesScreenCoordinator: CoordinatorProtocol {
viewModel = RoomChangeRolesScreenViewModel(mode: parameters.mode, viewModel = RoomChangeRolesScreenViewModel(mode: parameters.mode,
roomProxy: parameters.roomProxy, roomProxy: parameters.roomProxy,
userIndicatorController: parameters.userIndicatorController) userIndicatorController: parameters.userIndicatorController,
analytics: parameters.analytics)
} }
func start() { func start() {
@@ -22,17 +22,22 @@ typealias RoomChangeRolesScreenViewModelType = StateStoreViewModel<RoomChangeRol
class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomChangeRolesScreenViewModelProtocol { class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomChangeRolesScreenViewModelProtocol {
private let roomProxy: RoomProxyProtocol private let roomProxy: RoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private let actionsSubject: PassthroughSubject<RoomChangeRolesScreenViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<RoomChangeRolesScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<RoomChangeRolesScreenViewModelAction, Never> { var actionsPublisher: AnyPublisher<RoomChangeRolesScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init(mode: RoomMemberDetails.Role, roomProxy: RoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) { init(mode: RoomMemberDetails.Role,
roomProxy: RoomProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol,
analytics: AnalyticsService) {
guard mode != .user else { fatalError("Invalid screen configuration: \(mode)") } guard mode != .user else { fatalError("Invalid screen configuration: \(mode)") }
self.roomProxy = roomProxy self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.analytics = analytics
super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode, super.init(initialViewState: RoomChangeRolesScreenViewState(mode: mode,
members: [], members: [],
@@ -134,6 +139,9 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
_ = await infoTask.value _ = await infoTask.value
await roomProxy.updateMembers() await roomProxy.updateMembers()
trackChanges(promotionCount: promotingUpdates.count,
demotionCount: demotingUpdates.count)
actionsSubject.send(.complete) actionsSubject.send(.complete)
case .failure: case .failure:
context.alertInfo = AlertInfo(id: .error) context.alertInfo = AlertInfo(id: .error)
@@ -154,4 +162,16 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
private func hideSavingIndicator() { private func hideSavingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.indicatorID) userIndicatorController.retractIndicatorWithId(Self.indicatorID)
} }
// MARK: Analytics
private func trackChanges(promotionCount: Int, demotionCount: Int) {
for _ in 0..<promotionCount {
analytics.trackRoomModeration(action: .ChangeMemberRole, role: state.mode)
}
for _ in 0..<demotionCount {
analytics.trackRoomModeration(action: .ChangeMemberRole, role: .user)
}
}
} }
@@ -147,6 +147,7 @@ struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
static func makeViewModel(mode: RoomMemberDetails.Role) -> RoomChangeRolesScreenViewModel { static func makeViewModel(mode: RoomMemberDetails.Role) -> RoomChangeRolesScreenViewModel {
RoomChangeRolesScreenViewModel(mode: mode, RoomChangeRolesScreenViewModel(mode: mode,
roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)), roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)),
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
} }
} }
@@ -20,7 +20,9 @@ import SwiftUI
struct RoomMembersListScreenCoordinatorParameters { struct RoomMembersListScreenCoordinatorParameters {
let mediaProvider: MediaProviderProtocol let mediaProvider: MediaProviderProtocol
let roomProxy: RoomProxyProtocol let roomProxy: RoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings let appSettings: AppSettings
let analytics: AnalyticsService
} }
enum RoomMembersListScreenCoordinatorAction { enum RoomMembersListScreenCoordinatorAction {
@@ -41,8 +43,9 @@ final class RoomMembersListScreenCoordinator: CoordinatorProtocol {
init(parameters: RoomMembersListScreenCoordinatorParameters) { init(parameters: RoomMembersListScreenCoordinatorParameters) {
viewModel = RoomMembersListScreenViewModel(roomProxy: parameters.roomProxy, viewModel = RoomMembersListScreenViewModel(roomProxy: parameters.roomProxy,
mediaProvider: parameters.mediaProvider, mediaProvider: parameters.mediaProvider,
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSettings) appSettings: parameters.appSettings,
analytics: parameters.analytics)
} }
func start() { func start() {
@@ -23,6 +23,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
private let roomProxy: RoomProxyProtocol private let roomProxy: RoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let appSettings: AppSettings private let appSettings: AppSettings
private let analytics: AnalyticsService
private var members: [RoomMemberProxyProtocol] = [] private var members: [RoomMemberProxyProtocol] = []
@@ -36,10 +37,12 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
roomProxy: RoomProxyProtocol, roomProxy: RoomProxyProtocol,
mediaProvider: MediaProviderProtocol, mediaProvider: MediaProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings) { appSettings: AppSettings,
analytics: AnalyticsService) {
self.roomProxy = roomProxy self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.appSettings = appSettings self.appSettings = appSettings
self.analytics = analytics
super.init(initialViewState: .init(joinedMembersCount: roomProxy.joinedMembersCount, super.init(initialViewState: .init(joinedMembersCount: roomProxy.joinedMembersCount,
bindings: .init(mode: initialMode)), bindings: .init(mode: initialMode)),
@@ -182,6 +185,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
case .success: case .success:
state.bindings.memberToManage = nil state.bindings.memberToManage = nil
hideManageMemberIndicator(title: indicatorTitle) hideManageMemberIndicator(title: indicatorTitle)
analytics.trackRoomModeration(action: .KickMember, role: nil)
case .failure: case .failure:
showManageMemberFailure(title: indicatorTitle) showManageMemberFailure(title: indicatorTitle)
} }
@@ -195,6 +199,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
case .success: case .success:
state.bindings.memberToManage = nil state.bindings.memberToManage = nil
hideManageMemberIndicator(title: indicatorTitle) hideManageMemberIndicator(title: indicatorTitle)
analytics.trackRoomModeration(action: .BanMember, role: nil)
case .failure: case .failure:
showManageMemberFailure(title: indicatorTitle) showManageMemberFailure(title: indicatorTitle)
} }
@@ -208,6 +213,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
case .success: case .success:
state.bindings.memberToManage = nil state.bindings.memberToManage = nil
hideManageMemberIndicator(title: indicatorTitle) hideManageMemberIndicator(title: indicatorTitle)
analytics.trackRoomModeration(action: .UnbanMember, role: nil)
case .failure: case .failure:
showManageMemberFailure(title: indicatorTitle) showManageMemberFailure(title: indicatorTitle)
} }
@@ -106,6 +106,7 @@ private extension RoomMembersListScreenViewModel {
roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)), roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController,
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics)
} }
} }
@@ -183,6 +183,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
canUserInvite: false)), canUserInvite: false)),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController,
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics)
} }
} }
@@ -110,7 +110,8 @@ struct RoomMembersListMemberCell_Previews: PreviewProvider, TestablePreview {
members: members)), members: members)),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController,
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics)
static var previews: some View { static var previews: some View {
VStack(spacing: 12) { VStack(spacing: 12) {
Section("Invited/Joined") { Section("Invited/Joined") {
@@ -22,6 +22,7 @@ import SwiftUI
struct RoomRolesAndPermissionsScreenCoordinatorParameters { struct RoomRolesAndPermissionsScreenCoordinatorParameters {
let roomProxy: RoomProxyProtocol let roomProxy: RoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
} }
enum RoomRolesAndPermissionsScreenCoordinatorAction { enum RoomRolesAndPermissionsScreenCoordinatorAction {
@@ -41,7 +42,8 @@ final class RoomRolesAndPermissionsScreenCoordinator: CoordinatorProtocol {
init(parameters: RoomRolesAndPermissionsScreenCoordinatorParameters) { init(parameters: RoomRolesAndPermissionsScreenCoordinatorParameters) {
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: parameters.roomProxy, viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: parameters.roomProxy,
userIndicatorController: parameters.userIndicatorController) userIndicatorController: parameters.userIndicatorController,
analytics: parameters.analytics)
} }
func start() { func start() {
@@ -22,15 +22,17 @@ typealias RoomRolesAndPermissionsScreenViewModelType = StateStoreViewModel<RoomR
class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewModelType, RoomRolesAndPermissionsScreenViewModelProtocol { class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewModelType, RoomRolesAndPermissionsScreenViewModelProtocol {
private let roomProxy: RoomProxyProtocol private let roomProxy: RoomProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let analytics: AnalyticsService
private var actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenViewModelAction, Never> = .init() private var actionsSubject: PassthroughSubject<RoomRolesAndPermissionsScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> { var actionsPublisher: AnyPublisher<RoomRolesAndPermissionsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init(initialPermissions: RoomPermissions? = nil, roomProxy: RoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) { init(initialPermissions: RoomPermissions? = nil, roomProxy: RoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol, analytics: AnalyticsService) {
self.roomProxy = roomProxy self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.analytics = analytics
super.init(initialViewState: RoomRolesAndPermissionsScreenViewState(permissions: initialPermissions)) super.init(initialViewState: RoomRolesAndPermissionsScreenViewState(permissions: initialPermissions))
// Automatically update the admin/moderator counts. // Automatically update the admin/moderator counts.
@@ -107,6 +109,8 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
_ = await infoTask.value _ = await infoTask.value
await roomProxy.updateMembers() await roomProxy.updateMembers()
analytics.trackRoomModeration(action: .ChangeMemberRole, role: role)
actionsSubject.send(.demotedOwnUser) actionsSubject.send(.demotedOwnUser)
showSuccessIndicator() showSuccessIndicator()
case .failure: case .failure:
@@ -141,6 +145,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
switch await roomProxy.resetPowerLevels() { switch await roomProxy.resetPowerLevels() {
case .success: case .success:
analytics.trackRoomModeration(action: .ResetPermissions, role: nil)
showSuccessIndicator() showSuccessIndicator()
case .failure: case .failure:
state.bindings.alertInfo = AlertInfo(id: .error) state.bindings.alertInfo = AlertInfo(id: .error)
@@ -128,7 +128,8 @@ struct RoomRolesAndPermissionsScreen: View {
struct RoomRolesAndPermissionsScreen_Previews: PreviewProvider, TestablePreview { struct RoomRolesAndPermissionsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = RoomRolesAndPermissionsScreenViewModel(initialPermissions: RoomPermissions(powerLevels: .mock), static let viewModel = RoomRolesAndPermissionsScreenViewModel(initialPermissions: RoomPermissions(powerLevels: .mock),
roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)), roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)),
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
static var previews: some View { static var previews: some View {
NavigationStack { NavigationStack {
RoomRolesAndPermissionsScreen(context: viewModel.context) RoomRolesAndPermissionsScreen(context: viewModel.context)
@@ -196,4 +196,10 @@ extension AnalyticsService {
func trackPollEnd() { func trackPollEnd() {
capture(event: AnalyticsEvent.PollEnd(doNotUse: nil)) capture(event: AnalyticsEvent.PollEnd(doNotUse: nil))
} }
/// Track a room moderation action.
func trackRoomModeration(action: AnalyticsEvent.RoomModeration.Action, role: RoomMemberDetails.Role?) {
let role = role.map(AnalyticsEvent.RoomModeration.Role.init)
capture(event: AnalyticsEvent.RoomModeration(action: action, role: role))
}
} }
@@ -0,0 +1,30 @@
//
// Copyright 2024 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AnalyticsEvents
extension AnalyticsEvent.RoomModeration.Role {
init(role: RoomMemberDetails.Role) {
switch role {
case .administrator:
self = .Administrator
case .moderator:
self = .Moderator
case .user:
self = .User
}
}
}
@@ -718,7 +718,9 @@ class MockScreen: Identifiable {
let members: [RoomMemberProxyMock] = [.mockAlice, .mockBob, .mockCharlie] let members: [RoomMemberProxyMock] = [.mockAlice, .mockBob, .mockCharlie]
let coordinator = RoomMembersListScreenCoordinator(parameters: .init(mediaProvider: MockMediaProvider(), let coordinator = RoomMembersListScreenCoordinator(parameters: .init(mediaProvider: MockMediaProvider(),
roomProxy: RoomProxyMock(with: .init(name: "test", members: members)), roomProxy: RoomProxyMock(with: .init(name: "test", members: members)),
appSettings: ServiceLocator.shared.settings)) userIndicatorController: ServiceLocator.shared.userIndicatorController,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .roomMembersListScreenPendingInvites: case .roomMembersListScreenPendingInvites:
@@ -726,7 +728,9 @@ class MockScreen: Identifiable {
let members: [RoomMemberProxyMock] = [.mockInvitedAlice, .mockBob, .mockCharlie] let members: [RoomMemberProxyMock] = [.mockInvitedAlice, .mockBob, .mockCharlie]
let coordinator = RoomMembersListScreenCoordinator(parameters: .init(mediaProvider: MockMediaProvider(), let coordinator = RoomMembersListScreenCoordinator(parameters: .init(mediaProvider: MockMediaProvider(),
roomProxy: RoomProxyMock(with: .init(name: "test", members: members)), roomProxy: RoomProxyMock(with: .init(name: "test", members: members)),
appSettings: ServiceLocator.shared.settings)) userIndicatorController: ServiceLocator.shared.userIndicatorController,
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .roomNotificationSettingsDefaultSetting: case .roomNotificationSettingsDefaultSetting:
@@ -752,7 +756,8 @@ class MockScreen: Identifiable {
navigationStackCoordinator.setRootCoordinator(BlankFormCoordinator()) navigationStackCoordinator.setRootCoordinator(BlankFormCoordinator())
let coordinator = RoomRolesAndPermissionsFlowCoordinator(parameters: .init(roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)), let coordinator = RoomRolesAndPermissionsFlowCoordinator(parameters: .init(roomProxy: RoomProxyMock(with: .init(members: .allMembersAsAdmin)),
navigationStackCoordinator: navigationStackCoordinator, navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: ServiceLocator.shared.userIndicatorController)) userIndicatorController: ServiceLocator.shared.userIndicatorController,
analytics: ServiceLocator.shared.analytics))
retainedState.append(coordinator) retainedState.append(coordinator)
coordinator.start() coordinator.start()
return navigationStackCoordinator return navigationStackCoordinator
@@ -32,7 +32,8 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase {
viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock), viewModel = RoomChangePermissionsScreenViewModel(currentPermissions: .init(powerLevels: .mock),
group: .roomDetails, group: .roomDetails,
roomProxy: roomProxy, roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock()) userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
} }
func testChangeSetting() { func testChangeSetting() {
@@ -62,6 +63,7 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase {
context.settings[index] = RoomPermissionsSetting(title: "", value: .user, keyPath: \.roomAvatar) context.settings[index] = RoomPermissionsSetting(title: "", value: .user, keyPath: \.roomAvatar)
XCTAssertEqual(context.settings[index].value, .user) XCTAssertEqual(context.settings[index].value, .user)
XCTAssertTrue(context.viewState.hasChanges) XCTAssertTrue(context.viewState.hasChanges)
XCTAssertEqual(context.settings.count, 3)
// When saving changes. // When saving changes.
context.send(viewAction: .save) context.send(viewAction: .save)
@@ -70,8 +72,8 @@ class RoomChangePermissionsScreenViewModelTests: XCTestCase {
// Then the changes should be applied. // Then the changes should be applied.
XCTAssertTrue(roomProxy.applyPowerLevelChangesCalled) XCTAssertTrue(roomProxy.applyPowerLevelChangesCalled)
XCTAssertEqual(roomProxy.applyPowerLevelChangesReceivedChanges, .init(roomName: 50, roomAvatar: 0, roomTopic: 50), XCTAssertEqual(roomProxy.applyPowerLevelChangesReceivedChanges, .init(roomAvatar: 0),
"Only the changes for this screen should be applied, the others should be nil to remain unchanged.") "Only the avatar setting should be applied. No other settings were changed so they should be nil to remain left alone.")
} }
func testSaveNoChanges() async throws { func testSaveNoChanges() async throws {
@@ -28,8 +28,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
} }
func testInitialStateAdministrators() { func testInitialStateAdministrators() {
setupRoomProxy() setupViewModel(mode: .administrator)
viewModel = RoomChangeRolesScreenViewModel(mode: .administrator, roomProxy: roomProxy, userIndicatorController: UserIndicatorControllerMock())
XCTAssertEqual(context.viewState.membersToPromote, []) XCTAssertEqual(context.viewState.membersToPromote, [])
XCTAssertEqual(context.viewState.membersToDemote, []) XCTAssertEqual(context.viewState.membersToDemote, [])
XCTAssertEqual(context.viewState.members, context.viewState.visibleMembers) XCTAssertEqual(context.viewState.members, context.viewState.visibleMembers)
@@ -40,8 +39,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
} }
func testInitialStateModerators() { func testInitialStateModerators() {
setupRoomProxy() setupViewModel(mode: .moderator)
viewModel = RoomChangeRolesScreenViewModel(mode: .moderator, roomProxy: roomProxy, userIndicatorController: UserIndicatorControllerMock())
XCTAssertEqual(context.viewState.membersToPromote, []) XCTAssertEqual(context.viewState.membersToPromote, [])
XCTAssertEqual(context.viewState.membersToDemote, []) XCTAssertEqual(context.viewState.membersToDemote, [])
XCTAssertEqual(context.viewState.members, context.viewState.visibleMembers) XCTAssertEqual(context.viewState.members, context.viewState.visibleMembers)
@@ -150,8 +148,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
func testSaveModeratorChanges() async throws { func testSaveModeratorChanges() async throws {
// Given the change roles view model for moderators. // Given the change roles view model for moderators.
setupRoomProxy() setupViewModel(mode: .moderator)
viewModel = RoomChangeRolesScreenViewModel(mode: .moderator, roomProxy: roomProxy, userIndicatorController: UserIndicatorControllerMock())
guard let firstUser = context.viewState.members.first(where: { !context.viewState.isMemberSelected($0) }), guard let firstUser = context.viewState.members.first(where: { !context.viewState.isMemberSelected($0) }),
let existingModerator = context.viewState.membersWithRole.first else { let existingModerator = context.viewState.membersWithRole.first else {
@@ -175,8 +172,7 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
func testSavePromotedAdministrator() async throws { func testSavePromotedAdministrator() async throws {
// Given the change roles view model for administrators. // Given the change roles view model for administrators.
setupRoomProxy() setupViewModel(mode: .administrator)
viewModel = RoomChangeRolesScreenViewModel(mode: .administrator, roomProxy: roomProxy, userIndicatorController: UserIndicatorControllerMock())
XCTAssertNil(context.alertInfo) XCTAssertNil(context.alertInfo)
guard let firstUser = context.viewState.members.first(where: { !context.viewState.isMemberSelected($0) }) else { guard let firstUser = context.viewState.members.first(where: { !context.viewState.isMemberSelected($0) }) else {
@@ -201,7 +197,11 @@ class RoomChangeRolesScreenViewModelTests: XCTestCase {
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == firstUser.id && $0.powerLevel == 100 }), true) XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.contains(where: { $0.userID == firstUser.id && $0.powerLevel == 100 }), true)
} }
private func setupRoomProxy() { private func setupViewModel(mode: RoomMemberDetails.Role) {
roomProxy = RoomProxyMock(with: .init(members: .allMembersAsAdmin)) roomProxy = RoomProxyMock(with: .init(members: .allMembersAsAdmin))
viewModel = RoomChangeRolesScreenViewModel(mode: mode,
roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
} }
} }
@@ -170,6 +170,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
viewModel = .init(roomProxy: roomProxy, viewModel = .init(roomProxy: roomProxy,
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController,
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics)
} }
} }
@@ -28,25 +28,19 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
} }
func testEmptyCounters() { func testEmptyCounters() {
roomProxy = RoomProxyMock(with: .init()) setupViewModel(members: .allMembers)
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock())
XCTAssertEqual(context.viewState.administratorCount, 0) XCTAssertEqual(context.viewState.administratorCount, 0)
XCTAssertEqual(context.viewState.moderatorCount, 0) XCTAssertEqual(context.viewState.moderatorCount, 0)
} }
func testFilledCounters() { func testFilledCounters() {
roomProxy = RoomProxyMock(with: .init(members: .allMembersAsAdmin)) setupViewModel(members: .allMembersAsAdmin)
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock())
XCTAssertEqual(context.viewState.administratorCount, 2) XCTAssertEqual(context.viewState.administratorCount, 2)
XCTAssertEqual(context.viewState.moderatorCount, 1) XCTAssertEqual(context.viewState.moderatorCount, 1)
} }
func testResetPermissions() async throws { func testResetPermissions() async throws {
roomProxy = RoomProxyMock(with: .init(members: .allMembersAsAdmin)) setupViewModel(members: .allMembersAsAdmin)
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock())
context.send(viewAction: .reset) context.send(viewAction: .reset)
XCTAssertNotNil(context.alertInfo) XCTAssertNotNil(context.alertInfo)
@@ -59,9 +53,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
} }
func testDemoteToModerator() async throws { func testDemoteToModerator() async throws {
roomProxy = RoomProxyMock(with: .init(members: .allMembersAsAdmin)) setupViewModel(members: .allMembersAsAdmin)
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock())
context.send(viewAction: .editOwnUserRole) context.send(viewAction: .editOwnUserRole)
XCTAssertNotNil(context.alertInfo) XCTAssertNotNil(context.alertInfo)
@@ -76,9 +68,7 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
} }
func testDemoteToMember() async throws { func testDemoteToMember() async throws {
roomProxy = RoomProxyMock(with: .init(members: .allMembersAsAdmin)) setupViewModel(members: .allMembersAsAdmin)
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock())
context.send(viewAction: .editOwnUserRole) context.send(viewAction: .editOwnUserRole)
XCTAssertNotNil(context.alertInfo) XCTAssertNotNil(context.alertInfo)
@@ -91,4 +81,11 @@ class RoomRolesAndPermissionsScreenViewModelTests: XCTestCase {
XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel, XCTAssertEqual(roomProxy.updatePowerLevelsForUsersReceivedUpdates?.first?.powerLevel,
RoomMemberDetails.Role.user.rustPowerLevel) RoomMemberDetails.Role.user.rustPowerLevel)
} }
private func setupViewModel(members: [RoomMemberProxyMock]) {
roomProxy = RoomProxyMock(with: .init(members: members))
viewModel = RoomRolesAndPermissionsScreenViewModel(roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
}
} }
+2 -1
View File
@@ -56,7 +56,8 @@ packages:
# path: ../compound-ios # path: ../compound-ios
AnalyticsEvents: AnalyticsEvents:
url: https://github.com/matrix-org/matrix-analytics-events url: https://github.com/matrix-org/matrix-analytics-events
minorVersion: 0.11.0 minorVersion: 0.13.0
# path: ../matrix-analytics-events
Emojibase: Emojibase:
url: https://github.com/matrix-org/emojibase-bindings url: https://github.com/matrix-org/emojibase-bindings
minorVersion: 1.0.0 minorVersion: 1.0.0