mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 15:45:11 +00:00
Support for posthog super properties (#2774)
* Support for posthog super properties * Use typed SuperProperties and add tests * update superProperty in AppCoordinator
This commit is contained in:
@@ -461,6 +461,7 @@
|
|||||||
6F26CBC84AE87EB4068D398B /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 78B28D75FF7AF8E6146DEE2A /* LRUCache */; };
|
6F26CBC84AE87EB4068D398B /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 78B28D75FF7AF8E6146DEE2A /* LRUCache */; };
|
||||||
6F2AB43A1EFAD8A97AF41A15 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 9C73F37731C9FDED1BB24C1C /* Collections */; };
|
6F2AB43A1EFAD8A97AF41A15 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 9C73F37731C9FDED1BB24C1C /* Collections */; };
|
||||||
6F2D5D4F2590310DFAE973E4 /* WaitingDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6D698BFD68B061350553930 /* WaitingDialog.swift */; };
|
6F2D5D4F2590310DFAE973E4 /* WaitingDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6D698BFD68B061350553930 /* WaitingDialog.swift */; };
|
||||||
|
6F86349BDEAF4495EAE38931 /* PHGPostHogMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2AD8A56CD37E23071A2F4BF /* PHGPostHogMock.swift */; };
|
||||||
6FC10A00D268FCD48B631E37 /* ViewFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */; };
|
6FC10A00D268FCD48B631E37 /* ViewFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */; };
|
||||||
6FD8053301C5FEFA82D2F246 /* URLComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BFDCA5A09EE70BC17F2EFA7 /* URLComponents.swift */; };
|
6FD8053301C5FEFA82D2F246 /* URLComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BFDCA5A09EE70BC17F2EFA7 /* URLComponents.swift */; };
|
||||||
6FF51EB400DBA0668FC38B97 /* TimelineStartRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */; };
|
6FF51EB400DBA0668FC38B97 /* TimelineStartRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */; };
|
||||||
@@ -1036,6 +1037,7 @@
|
|||||||
F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */; };
|
F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */; };
|
||||||
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0900BBF0A5D5D775E917C70 /* EventBasedMessageTimelineItemProtocol.swift */; };
|
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0900BBF0A5D5D775E917C70 /* EventBasedMessageTimelineItemProtocol.swift */; };
|
||||||
F7BC744FFA7FE248FAE7F570 /* UserIndicatorToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57C8022B8A871A1DCD1750A /* UserIndicatorToastView.swift */; };
|
F7BC744FFA7FE248FAE7F570 /* UserIndicatorToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57C8022B8A871A1DCD1750A /* UserIndicatorToastView.swift */; };
|
||||||
|
F7D709D7ECABE46641BB8B6B /* PHGPostHogProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEEAE1BFAACD6C96B6DB731 /* PHGPostHogProtocol.swift */; };
|
||||||
F833D5B5BE6707F961FA88DB /* SecureBackupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1119E9C63AE530252640D2 /* SecureBackupController.swift */; };
|
F833D5B5BE6707F961FA88DB /* SecureBackupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1119E9C63AE530252640D2 /* SecureBackupController.swift */; };
|
||||||
F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */; };
|
F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */; };
|
||||||
F8C87130FD999F7F1076208C /* RoomChangePermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */; };
|
F8C87130FD999F7F1076208C /* RoomChangePermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AAEA70CFF3284920811941 /* RoomChangePermissionsScreen.swift */; };
|
||||||
@@ -1497,6 +1499,7 @@
|
|||||||
5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = "<group>"; };
|
5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = "<group>"; };
|
5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
5CEEAE1BFAACD6C96B6DB731 /* PHGPostHogProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogProtocol.swift; sourceTree = "<group>"; };
|
||||||
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
|
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
|
||||||
5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledFrameModifier.swift; sourceTree = "<group>"; };
|
5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledFrameModifier.swift; sourceTree = "<group>"; };
|
||||||
5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetection.swift; sourceTree = "<group>"; };
|
5D99730313BEBF08CDE81EE3 /* EmojiDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiDetection.swift; sourceTree = "<group>"; };
|
||||||
@@ -1810,6 +1813,7 @@
|
|||||||
B172057567E049007A5C4D92 /* Strings+SAS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+SAS.swift"; sourceTree = "<group>"; };
|
B172057567E049007A5C4D92 /* Strings+SAS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+SAS.swift"; sourceTree = "<group>"; };
|
||||||
B1E227F34BE43B08E098796E /* TestablePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreview.swift; sourceTree = "<group>"; };
|
B1E227F34BE43B08E098796E /* TestablePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreview.swift; sourceTree = "<group>"; };
|
||||||
B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = "<group>"; };
|
B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
B2AD8A56CD37E23071A2F4BF /* PHGPostHogMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogMock.swift; sourceTree = "<group>"; };
|
||||||
B2B1DC3B3FB40A7F4AE9B7BF /* RoomRolesAndPermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreen.swift; sourceTree = "<group>"; };
|
B2B1DC3B3FB40A7F4AE9B7BF /* RoomRolesAndPermissionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreen.swift; sourceTree = "<group>"; };
|
||||||
B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenModels.swift; sourceTree = "<group>"; };
|
B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenModels.swift; sourceTree = "<group>"; };
|
||||||
@@ -2673,6 +2677,7 @@
|
|||||||
3BAC027034248429A438886B /* AppMediatorMock.swift */,
|
3BAC027034248429A438886B /* AppMediatorMock.swift */,
|
||||||
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
|
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
|
||||||
382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */,
|
382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */,
|
||||||
|
B2AD8A56CD37E23071A2F4BF /* PHGPostHogMock.swift */,
|
||||||
D38391154120264910D19528 /* PollMock.swift */,
|
D38391154120264910D19528 /* PollMock.swift */,
|
||||||
90B4ED923603F6110D4960C5 /* QRCodeLoginServiceMock.swift */,
|
90B4ED923603F6110D4960C5 /* QRCodeLoginServiceMock.swift */,
|
||||||
894EE8F5B399A165BA2A6634 /* RoomDirectorySearchMock.swift */,
|
894EE8F5B399A165BA2A6634 /* RoomDirectorySearchMock.swift */,
|
||||||
@@ -3132,6 +3137,7 @@
|
|||||||
57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */,
|
57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */,
|
||||||
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */,
|
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */,
|
||||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */,
|
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */,
|
||||||
|
5CEEAE1BFAACD6C96B6DB731 /* PHGPostHogProtocol.swift */,
|
||||||
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */,
|
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */,
|
||||||
752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */,
|
752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */,
|
||||||
3A304097A59704AC9B869EC6 /* Helpers */,
|
3A304097A59704AC9B869EC6 /* Helpers */,
|
||||||
@@ -6206,6 +6212,8 @@
|
|||||||
AA5924D3B67F7ACD98BBEFDC /* OrientationManagerProtocol.swift in Sources */,
|
AA5924D3B67F7ACD98BBEFDC /* OrientationManagerProtocol.swift in Sources */,
|
||||||
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */,
|
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */,
|
||||||
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */,
|
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */,
|
||||||
|
6F86349BDEAF4495EAE38931 /* PHGPostHogMock.swift in Sources */,
|
||||||
|
F7D709D7ECABE46641BB8B6B /* PHGPostHogProtocol.swift in Sources */,
|
||||||
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */,
|
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */,
|
||||||
7501442D52A65F73DF79FFD4 /* PaginationIndicatorRoomTimelineItem.swift in Sources */,
|
7501442D52A65F73DF79FFD4 /* PaginationIndicatorRoomTimelineItem.swift in Sources */,
|
||||||
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */,
|
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import AnalyticsEvents
|
||||||
import BackgroundTasks
|
import BackgroundTasks
|
||||||
import Combine
|
import Combine
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
@@ -313,7 +314,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
applicationId: appSettings.bugReportApplicationId,
|
applicationId: appSettings.bugReportApplicationId,
|
||||||
sdkGitSHA: sdkGitSha(),
|
sdkGitSHA: sdkGitSha(),
|
||||||
maxUploadSize: appSettings.bugReportMaxUploadSize))
|
maxUploadSize: appSettings.bugReportMaxUploadSize))
|
||||||
ServiceLocator.shared.register(analytics: AnalyticsService(client: PostHogAnalyticsClient(),
|
let posthogAnalyticsClient = PostHogAnalyticsClient()
|
||||||
|
posthogAnalyticsClient.updateSuperProperties(AnalyticsEvent.SuperProperties(appPlatform: nil, cryptoSDK: .Rust, cryptoSDKVersion: sdkGitSha()))
|
||||||
|
ServiceLocator.shared.register(analytics: AnalyticsService(client: posthogAnalyticsClient,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
bugReportService: ServiceLocator.shared.bugReportService))
|
bugReportService: ServiceLocator.shared.bugReportService))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Generated using Sourcery 2.2.2 — https://github.com/krzysztofzablocki/Sourcery
|
// Generated using Sourcery 2.2.4 — https://github.com/krzysztofzablocki/Sourcery
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
// swiftlint:disable all
|
// swiftlint:disable all
|
||||||
@@ -6585,6 +6585,197 @@ class OrientationManagerMock: OrientationManagerProtocol {
|
|||||||
lockOrientationClosure?(orientation)
|
lockOrientationClosure?(orientation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class PHGPostHogMock: PHGPostHogProtocol {
|
||||||
|
var enabled: Bool {
|
||||||
|
get { return underlyingEnabled }
|
||||||
|
set(value) { underlyingEnabled = value }
|
||||||
|
}
|
||||||
|
var underlyingEnabled: Bool!
|
||||||
|
|
||||||
|
//MARK: - enable
|
||||||
|
|
||||||
|
var enableUnderlyingCallsCount = 0
|
||||||
|
var enableCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return enableUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = enableUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
enableUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
enableUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var enableCalled: Bool {
|
||||||
|
return enableCallsCount > 0
|
||||||
|
}
|
||||||
|
var enableClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func enable() {
|
||||||
|
enableCallsCount += 1
|
||||||
|
enableClosure?()
|
||||||
|
}
|
||||||
|
//MARK: - disable
|
||||||
|
|
||||||
|
var disableUnderlyingCallsCount = 0
|
||||||
|
var disableCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return disableUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = disableUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
disableUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
disableUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var disableCalled: Bool {
|
||||||
|
return disableCallsCount > 0
|
||||||
|
}
|
||||||
|
var disableClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func disable() {
|
||||||
|
disableCallsCount += 1
|
||||||
|
disableClosure?()
|
||||||
|
}
|
||||||
|
//MARK: - reset
|
||||||
|
|
||||||
|
var resetUnderlyingCallsCount = 0
|
||||||
|
var resetCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return resetUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = resetUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
resetUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
resetUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var resetCalled: Bool {
|
||||||
|
return resetCallsCount > 0
|
||||||
|
}
|
||||||
|
var resetClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
resetCallsCount += 1
|
||||||
|
resetClosure?()
|
||||||
|
}
|
||||||
|
//MARK: - capture
|
||||||
|
|
||||||
|
var capturePropertiesUnderlyingCallsCount = 0
|
||||||
|
var capturePropertiesCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return capturePropertiesUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = capturePropertiesUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
capturePropertiesUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
capturePropertiesUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var capturePropertiesCalled: Bool {
|
||||||
|
return capturePropertiesCallsCount > 0
|
||||||
|
}
|
||||||
|
var capturePropertiesReceivedArguments: (event: String, properties: [String: Any]?)?
|
||||||
|
var capturePropertiesReceivedInvocations: [(event: String, properties: [String: Any]?)] = []
|
||||||
|
var capturePropertiesClosure: ((String, [String: Any]?) -> Void)?
|
||||||
|
|
||||||
|
func capture(_ event: String, properties: [String: Any]?) {
|
||||||
|
capturePropertiesCallsCount += 1
|
||||||
|
capturePropertiesReceivedArguments = (event: event, properties: properties)
|
||||||
|
capturePropertiesReceivedInvocations.append((event: event, properties: properties))
|
||||||
|
capturePropertiesClosure?(event, properties)
|
||||||
|
}
|
||||||
|
//MARK: - screen
|
||||||
|
|
||||||
|
var screenPropertiesUnderlyingCallsCount = 0
|
||||||
|
var screenPropertiesCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return screenPropertiesUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = screenPropertiesUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
screenPropertiesUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
screenPropertiesUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var screenPropertiesCalled: Bool {
|
||||||
|
return screenPropertiesCallsCount > 0
|
||||||
|
}
|
||||||
|
var screenPropertiesReceivedArguments: (screenTitle: String, properties: [String: Any]?)?
|
||||||
|
var screenPropertiesReceivedInvocations: [(screenTitle: String, properties: [String: Any]?)] = []
|
||||||
|
var screenPropertiesClosure: ((String, [String: Any]?) -> Void)?
|
||||||
|
|
||||||
|
func screen(_ screenTitle: String, properties: [String: Any]?) {
|
||||||
|
screenPropertiesCallsCount += 1
|
||||||
|
screenPropertiesReceivedArguments = (screenTitle: screenTitle, properties: properties)
|
||||||
|
screenPropertiesReceivedInvocations.append((screenTitle: screenTitle, properties: properties))
|
||||||
|
screenPropertiesClosure?(screenTitle, properties)
|
||||||
|
}
|
||||||
|
}
|
||||||
class PollInteractionHandlerMock: PollInteractionHandlerProtocol {
|
class PollInteractionHandlerMock: PollInteractionHandlerProtocol {
|
||||||
|
|
||||||
//MARK: - sendPollResponse
|
//MARK: - sendPollResponse
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// 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 Foundation
|
||||||
|
import PostHog
|
||||||
|
|
||||||
|
extension PHGPostHogMock {
|
||||||
|
func configureMockBehavior() {
|
||||||
|
enableClosure = {
|
||||||
|
self.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockPostHogFactory: PostHogFactory {
|
||||||
|
var mock: PHGPostHogProtocol!
|
||||||
|
|
||||||
|
init(mock: PHGPostHogProtocol!) {
|
||||||
|
self.mock = mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPostHog(config: PHGPostHogConfiguration) -> ElementX.PHGPostHogProtocol {
|
||||||
|
mock
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// 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 Foundation
|
||||||
|
import PostHog
|
||||||
|
|
||||||
|
// sourcery: AutoMockable
|
||||||
|
protocol PHGPostHogProtocol {
|
||||||
|
var enabled: Bool { get }
|
||||||
|
|
||||||
|
func enable()
|
||||||
|
|
||||||
|
func disable()
|
||||||
|
|
||||||
|
func reset()
|
||||||
|
|
||||||
|
func capture(_ event: String, properties: [String: Any]?)
|
||||||
|
|
||||||
|
func screen(_ screenTitle: String, properties: [String: Any]?)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol PostHogFactory {
|
||||||
|
func createPostHog(config: PHGPostHogConfiguration) -> PHGPostHogProtocol
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultPostHogFactory: PostHogFactory {
|
||||||
|
func createPostHog(config: PHGPostHogConfiguration) -> PHGPostHogProtocol {
|
||||||
|
PHGPostHog(configuration: config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PHGPostHog: PHGPostHogProtocol { }
|
||||||
@@ -19,12 +19,25 @@ import PostHog
|
|||||||
|
|
||||||
/// An analytics client that reports events to a PostHog server.
|
/// An analytics client that reports events to a PostHog server.
|
||||||
class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||||
|
private var posthogFactory: PostHogFactory = DefaultPostHogFactory()
|
||||||
|
|
||||||
|
init(posthogFactory: PostHogFactory? = nil) {
|
||||||
|
if let factory = posthogFactory {
|
||||||
|
self.posthogFactory = factory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The PHGPostHog object used to report events.
|
/// The PHGPostHog object used to report events.
|
||||||
private var postHog: PHGPostHog?
|
private var postHog: PHGPostHogProtocol?
|
||||||
|
|
||||||
/// Any user properties to be included with the next captured event.
|
/// Any user properties to be included with the next captured event.
|
||||||
private(set) var pendingUserProperties: AnalyticsEvent.UserProperties?
|
private(set) var pendingUserProperties: AnalyticsEvent.UserProperties?
|
||||||
|
|
||||||
|
/// Super Properties are properties associated with events that are set once and then sent with every capture call, be it a $screen, an autocaptured button click, or anything else.
|
||||||
|
/// It is different from user properties that will be attached to the user and not events.
|
||||||
|
/// Not persisted for now, should be set on start.
|
||||||
|
private var superProperties: AnalyticsEvent.SuperProperties?
|
||||||
|
|
||||||
var isRunning: Bool { postHog?.enabled ?? false }
|
var isRunning: Bool { postHog?.enabled ?? false }
|
||||||
|
|
||||||
func start(analyticsConfiguration: AnalyticsConfiguration) {
|
func start(analyticsConfiguration: AnalyticsConfiguration) {
|
||||||
@@ -32,9 +45,11 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
|||||||
guard let configuration = PHGPostHogConfiguration.standard(analyticsConfiguration: analyticsConfiguration) else { return }
|
guard let configuration = PHGPostHogConfiguration.standard(analyticsConfiguration: analyticsConfiguration) else { return }
|
||||||
|
|
||||||
if postHog == nil {
|
if postHog == nil {
|
||||||
postHog = PHGPostHog(configuration: configuration)
|
postHog = posthogFactory.createPostHog(config: configuration)
|
||||||
}
|
}
|
||||||
|
// Add super property cryptoSDK to the captured events, to allow easy
|
||||||
|
// filtering of events across different client by using same filter.
|
||||||
|
superProperties = AnalyticsEvent.SuperProperties(appPlatform: nil, cryptoSDK: .Rust, cryptoSDKVersion: nil)
|
||||||
postHog?.enable()
|
postHog?.enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +67,12 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
|||||||
|
|
||||||
func capture(_ event: AnalyticsEventProtocol) {
|
func capture(_ event: AnalyticsEventProtocol) {
|
||||||
guard isRunning else { return }
|
guard isRunning else { return }
|
||||||
postHog?.capture(event.eventName, properties: attachUserProperties(to: event.properties))
|
postHog?.capture(event.eventName, properties: attachUserProperties(to: attachSuperProperties(to: event.properties)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func screen(_ event: AnalyticsScreenProtocol) {
|
func screen(_ event: AnalyticsScreenProtocol) {
|
||||||
guard isRunning else { return }
|
guard isRunning else { return }
|
||||||
postHog?.screen(event.screenName.rawValue, properties: attachUserProperties(to: event.properties))
|
postHog?.screen(event.screenName.rawValue, properties: attachUserProperties(to: attachSuperProperties(to: event.properties)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) {
|
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) {
|
||||||
@@ -73,6 +88,20 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
|||||||
numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces)
|
numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateSuperProperties(_ updatedProperties: AnalyticsEvent.SuperProperties) {
|
||||||
|
guard let currentProperties = superProperties else {
|
||||||
|
superProperties = updatedProperties
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
superProperties = AnalyticsEvent.SuperProperties(appPlatform: updatedProperties.appPlatform ??
|
||||||
|
currentProperties.appPlatform,
|
||||||
|
cryptoSDK: updatedProperties.cryptoSDK ??
|
||||||
|
currentProperties.cryptoSDK,
|
||||||
|
cryptoSDKVersion: updatedProperties.cryptoSDKVersion ??
|
||||||
|
currentProperties.cryptoSDKVersion)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
/// Given a dictionary containing properties from an event, this method will return those properties
|
/// Given a dictionary containing properties from an event, this method will return those properties
|
||||||
@@ -89,4 +118,19 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
|||||||
pendingUserProperties = nil
|
pendingUserProperties = nil
|
||||||
return properties
|
return properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attach super properties to events.
|
||||||
|
/// If the property is already set on the event, the already set value will be kept.
|
||||||
|
private func attachSuperProperties(to properties: [String: Any]) -> [String: Any] {
|
||||||
|
guard isRunning, let superProperties else { return properties }
|
||||||
|
|
||||||
|
var properties = properties
|
||||||
|
|
||||||
|
superProperties.properties.forEach { (key: String, value: Any) in
|
||||||
|
if properties[key] == nil {
|
||||||
|
properties[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return properties
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,14 @@
|
|||||||
|
|
||||||
import AnalyticsEvents
|
import AnalyticsEvents
|
||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
|
import PostHog
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class AnalyticsTests: XCTestCase {
|
class AnalyticsTests: XCTestCase {
|
||||||
private var appSettings: AppSettings!
|
private var appSettings: AppSettings!
|
||||||
private var analyticsClient: AnalyticsClientMock!
|
private var analyticsClient: AnalyticsClientMock!
|
||||||
private var bugReportService: BugReportServiceMock!
|
private var bugReportService: BugReportServiceMock!
|
||||||
|
private var posthogMock: PHGPostHogMock!
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
AppSettings.resetAllSettings()
|
AppSettings.resetAllSettings()
|
||||||
@@ -35,6 +37,9 @@ class AnalyticsTests: XCTestCase {
|
|||||||
ServiceLocator.shared.register(analytics: AnalyticsService(client: analyticsClient,
|
ServiceLocator.shared.register(analytics: AnalyticsService(client: analyticsClient,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
bugReportService: ServiceLocator.shared.bugReportService))
|
bugReportService: ServiceLocator.shared.bugReportService))
|
||||||
|
|
||||||
|
posthogMock = PHGPostHogMock()
|
||||||
|
posthogMock.configureMockBehavior()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
@@ -80,7 +85,7 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertFalse(analyticsClient.startAnalyticsConfigurationCalled)
|
XCTAssertFalse(analyticsClient.startAnalyticsConfigurationCalled)
|
||||||
XCTAssertFalse(bugReportService.startCalled)
|
XCTAssertFalse(bugReportService.startCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAnalyticsOptOut() {
|
func testAnalyticsOptOut() {
|
||||||
// Given a fresh install of the app (without PostHog analytics having been set).
|
// Given a fresh install of the app (without PostHog analytics having been set).
|
||||||
// When analytics is opt-out
|
// When analytics is opt-out
|
||||||
@@ -95,7 +100,7 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertTrue(analyticsClient.stopCalled)
|
XCTAssertTrue(analyticsClient.stopCalled)
|
||||||
XCTAssertTrue(bugReportService.stopCalled)
|
XCTAssertTrue(bugReportService.stopCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAnalyticsOptIn() {
|
func testAnalyticsOptIn() {
|
||||||
// Given a fresh install of the app (without PostHog analytics having been set).
|
// Given a fresh install of the app (without PostHog analytics having been set).
|
||||||
// When analytics is opt-in
|
// When analytics is opt-in
|
||||||
@@ -107,7 +112,7 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertTrue(analyticsClient.startAnalyticsConfigurationCalled)
|
XCTAssertTrue(analyticsClient.startAnalyticsConfigurationCalled)
|
||||||
XCTAssertTrue(bugReportService.startCalled)
|
XCTAssertTrue(bugReportService.startCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAnalyticsStartIfNotEnabled() {
|
func testAnalyticsStartIfNotEnabled() {
|
||||||
// Given an existing install of the app where the user previously declined the tracking
|
// Given an existing install of the app where the user previously declined the tracking
|
||||||
appSettings.analyticsConsentState = .optedOut
|
appSettings.analyticsConsentState = .optedOut
|
||||||
@@ -192,7 +197,7 @@ class AnalyticsTests: XCTestCase {
|
|||||||
// Given an existing install of the app where the user previously accpeted the tracking
|
// Given an existing install of the app where the user previously accpeted the tracking
|
||||||
appSettings.analyticsConsentState = .optedIn
|
appSettings.analyticsConsentState = .optedIn
|
||||||
XCTAssertFalse(ServiceLocator.shared.analytics.shouldShowAnalyticsPrompt)
|
XCTAssertFalse(ServiceLocator.shared.analytics.shouldShowAnalyticsPrompt)
|
||||||
|
|
||||||
// When forgetting analytics consents
|
// When forgetting analytics consents
|
||||||
ServiceLocator.shared.analytics.resetConsentState()
|
ServiceLocator.shared.analytics.resetConsentState()
|
||||||
|
|
||||||
@@ -200,4 +205,64 @@ class AnalyticsTests: XCTestCase {
|
|||||||
XCTAssertEqual(appSettings.analyticsConsentState, .unknown)
|
XCTAssertEqual(appSettings.analyticsConsentState, .unknown)
|
||||||
XCTAssertTrue(ServiceLocator.shared.analytics.shouldShowAnalyticsPrompt)
|
XCTAssertTrue(ServiceLocator.shared.analytics.shouldShowAnalyticsPrompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSendingAndUpdatingSuperProperties() {
|
||||||
|
// Given a client with user properties set
|
||||||
|
let client = PostHogAnalyticsClient(posthogFactory: MockPostHogFactory(mock: posthogMock))
|
||||||
|
client.start(analyticsConfiguration: appSettings.analyticsConfiguration)
|
||||||
|
|
||||||
|
client.updateSuperProperties(
|
||||||
|
AnalyticsEvent.SuperProperties(appPlatform: "A thing",
|
||||||
|
cryptoSDK: .Rust,
|
||||||
|
cryptoSDKVersion: "000")
|
||||||
|
)
|
||||||
|
|
||||||
|
// When sending an event (tests run under Debug configuration so this is sent to the development instance)
|
||||||
|
client.screen(AnalyticsEvent.MobileScreen(durationMs: nil, screenName: .Home))
|
||||||
|
|
||||||
|
let screenEvent = posthogMock.screenPropertiesReceivedArguments
|
||||||
|
|
||||||
|
XCTAssertEqual(screenEvent?.screenTitle, AnalyticsEvent.MobileScreen.ScreenName.Home.rawValue)
|
||||||
|
|
||||||
|
// All the super properties should have been added
|
||||||
|
XCTAssertEqual(screenEvent?.properties?["cryptoSDK"] as? String, AnalyticsEvent.SuperProperties.CryptoSDK.Rust.rawValue)
|
||||||
|
XCTAssertEqual(screenEvent?.properties?["appPlatform"] as? String, "A thing")
|
||||||
|
XCTAssertEqual(screenEvent?.properties?["cryptoSDKVersion"] as? String, "000")
|
||||||
|
|
||||||
|
// It should be the same for any event
|
||||||
|
let someEvent = AnalyticsEvent.Error(context: nil,
|
||||||
|
cryptoModule: .Rust,
|
||||||
|
cryptoSDK: .Rust,
|
||||||
|
domain: .E2EE,
|
||||||
|
eventLocalAgeMillis: nil,
|
||||||
|
isFederated: nil,
|
||||||
|
isMatrixDotOrg: nil,
|
||||||
|
name: .OlmKeysNotSentError,
|
||||||
|
timeToDecryptMillis: nil,
|
||||||
|
userTrustsOwnIdentity: nil,
|
||||||
|
wasVisibleToUser: nil)
|
||||||
|
client.capture(someEvent)
|
||||||
|
|
||||||
|
let capturedEvent = posthogMock.capturePropertiesReceivedArguments
|
||||||
|
|
||||||
|
// All the super properties should have been added
|
||||||
|
XCTAssertEqual(capturedEvent?.properties?["cryptoSDK"] as? String, AnalyticsEvent.SuperProperties.CryptoSDK.Rust.rawValue)
|
||||||
|
XCTAssertEqual(capturedEvent?.properties?["appPlatform"] as? String, "A thing")
|
||||||
|
XCTAssertEqual(capturedEvent?.properties?["cryptoSDKVersion"] as? String, "000")
|
||||||
|
|
||||||
|
// Updating should keep the previously set properties
|
||||||
|
client.updateSuperProperties(
|
||||||
|
AnalyticsEvent.SuperProperties(appPlatform: nil,
|
||||||
|
cryptoSDK: nil,
|
||||||
|
cryptoSDKVersion: "001")
|
||||||
|
)
|
||||||
|
|
||||||
|
client.capture(someEvent)
|
||||||
|
let capturedEvent2 = posthogMock.capturePropertiesReceivedArguments
|
||||||
|
|
||||||
|
// All the super properties should have been added, with the one udpated
|
||||||
|
XCTAssertEqual(capturedEvent2?.properties?["cryptoSDK"] as? String, AnalyticsEvent.SuperProperties.CryptoSDK.Rust.rawValue)
|
||||||
|
XCTAssertEqual(capturedEvent2?.properties?["appPlatform"] as? String, "A thing")
|
||||||
|
XCTAssertEqual(capturedEvent2?.properties?["cryptoSDKVersion"] as? String, "001")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user