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 */; };
|
||||
6F2AB43A1EFAD8A97AF41A15 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 9C73F37731C9FDED1BB24C1C /* Collections */; };
|
||||
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 */; };
|
||||
6FD8053301C5FEFA82D2F246 /* URLComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BFDCA5A09EE70BC17F2EFA7 /* URLComponents.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 */; };
|
||||
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0900BBF0A5D5D775E917C70 /* EventBasedMessageTimelineItemProtocol.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 */; };
|
||||
F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1810,6 +1813,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -2673,6 +2677,7 @@
|
||||
3BAC027034248429A438886B /* AppMediatorMock.swift */,
|
||||
E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */,
|
||||
382B50F7E379B3DBBD174364 /* NotificationSettingsProxyMock.swift */,
|
||||
B2AD8A56CD37E23071A2F4BF /* PHGPostHogMock.swift */,
|
||||
D38391154120264910D19528 /* PollMock.swift */,
|
||||
90B4ED923603F6110D4960C5 /* QRCodeLoginServiceMock.swift */,
|
||||
894EE8F5B399A165BA2A6634 /* RoomDirectorySearchMock.swift */,
|
||||
@@ -3132,6 +3137,7 @@
|
||||
57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */,
|
||||
5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */,
|
||||
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */,
|
||||
5CEEAE1BFAACD6C96B6DB731 /* PHGPostHogProtocol.swift */,
|
||||
1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */,
|
||||
752A0EB49BF5BCEA37EDF7A3 /* Signposter.swift */,
|
||||
3A304097A59704AC9B869EC6 /* Helpers */,
|
||||
@@ -6206,6 +6212,8 @@
|
||||
AA5924D3B67F7ACD98BBEFDC /* OrientationManagerProtocol.swift in Sources */,
|
||||
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */,
|
||||
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */,
|
||||
6F86349BDEAF4495EAE38931 /* PHGPostHogMock.swift in Sources */,
|
||||
F7D709D7ECABE46641BB8B6B /* PHGPostHogProtocol.swift in Sources */,
|
||||
847DE3A7EB9FCA2C429C6E85 /* PINTextField.swift in Sources */,
|
||||
7501442D52A65F73DF79FFD4 /* PaginationIndicatorRoomTimelineItem.swift in Sources */,
|
||||
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import AnalyticsEvents
|
||||
import BackgroundTasks
|
||||
import Combine
|
||||
import MatrixRustSDK
|
||||
@@ -313,7 +314,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
applicationId: appSettings.bugReportApplicationId,
|
||||
sdkGitSHA: sdkGitSha(),
|
||||
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,
|
||||
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
|
||||
|
||||
// swiftlint:disable all
|
||||
@@ -6585,6 +6585,197 @@ class OrientationManagerMock: OrientationManagerProtocol {
|
||||
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 {
|
||||
|
||||
//MARK: - sendPollResponse
|
||||
|
||||
38
ElementX/Sources/Mocks/PHGPostHogMock.swift
Normal file
38
ElementX/Sources/Mocks/PHGPostHogMock.swift
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
45
ElementX/Sources/Services/Analytics/PHGPostHogProtocol.swift
Normal file
45
ElementX/Sources/Services/Analytics/PHGPostHogProtocol.swift
Normal file
@@ -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.
|
||||
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.
|
||||
private var postHog: PHGPostHog?
|
||||
private var postHog: PHGPostHogProtocol?
|
||||
|
||||
/// Any user properties to be included with the next captured event.
|
||||
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 }
|
||||
|
||||
func start(analyticsConfiguration: AnalyticsConfiguration) {
|
||||
@@ -32,9 +45,11 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
guard let configuration = PHGPostHogConfiguration.standard(analyticsConfiguration: analyticsConfiguration) else { return }
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -52,12 +67,12 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
|
||||
func capture(_ event: AnalyticsEventProtocol) {
|
||||
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) {
|
||||
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) {
|
||||
@@ -73,6 +88,20 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
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
|
||||
|
||||
/// Given a dictionary containing properties from an event, this method will return those properties
|
||||
@@ -89,4 +118,19 @@ class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
pendingUserProperties = nil
|
||||
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
|
||||
@testable import ElementX
|
||||
import PostHog
|
||||
import XCTest
|
||||
|
||||
class AnalyticsTests: XCTestCase {
|
||||
private var appSettings: AppSettings!
|
||||
private var analyticsClient: AnalyticsClientMock!
|
||||
private var bugReportService: BugReportServiceMock!
|
||||
private var posthogMock: PHGPostHogMock!
|
||||
|
||||
override func setUp() {
|
||||
AppSettings.resetAllSettings()
|
||||
@@ -35,6 +37,9 @@ class AnalyticsTests: XCTestCase {
|
||||
ServiceLocator.shared.register(analytics: AnalyticsService(client: analyticsClient,
|
||||
appSettings: appSettings,
|
||||
bugReportService: ServiceLocator.shared.bugReportService))
|
||||
|
||||
posthogMock = PHGPostHogMock()
|
||||
posthogMock.configureMockBehavior()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -80,7 +85,7 @@ class AnalyticsTests: XCTestCase {
|
||||
XCTAssertFalse(analyticsClient.startAnalyticsConfigurationCalled)
|
||||
XCTAssertFalse(bugReportService.startCalled)
|
||||
}
|
||||
|
||||
|
||||
func testAnalyticsOptOut() {
|
||||
// Given a fresh install of the app (without PostHog analytics having been set).
|
||||
// When analytics is opt-out
|
||||
@@ -95,7 +100,7 @@ class AnalyticsTests: XCTestCase {
|
||||
XCTAssertTrue(analyticsClient.stopCalled)
|
||||
XCTAssertTrue(bugReportService.stopCalled)
|
||||
}
|
||||
|
||||
|
||||
func testAnalyticsOptIn() {
|
||||
// Given a fresh install of the app (without PostHog analytics having been set).
|
||||
// When analytics is opt-in
|
||||
@@ -107,7 +112,7 @@ class AnalyticsTests: XCTestCase {
|
||||
XCTAssertTrue(analyticsClient.startAnalyticsConfigurationCalled)
|
||||
XCTAssertTrue(bugReportService.startCalled)
|
||||
}
|
||||
|
||||
|
||||
func testAnalyticsStartIfNotEnabled() {
|
||||
// Given an existing install of the app where the user previously declined the tracking
|
||||
appSettings.analyticsConsentState = .optedOut
|
||||
@@ -192,7 +197,7 @@ class AnalyticsTests: XCTestCase {
|
||||
// Given an existing install of the app where the user previously accpeted the tracking
|
||||
appSettings.analyticsConsentState = .optedIn
|
||||
XCTAssertFalse(ServiceLocator.shared.analytics.shouldShowAnalyticsPrompt)
|
||||
|
||||
|
||||
// When forgetting analytics consents
|
||||
ServiceLocator.shared.analytics.resetConsentState()
|
||||
|
||||
@@ -200,4 +205,64 @@ class AnalyticsTests: XCTestCase {
|
||||
XCTAssertEqual(appSettings.analyticsConsentState, .unknown)
|
||||
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