From 2c04d342a99c73db1c5189a643cccb346c302f1a Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 9 Sep 2024 16:03:54 +0200 Subject: [PATCH 01/10] chore: add personProfiles support --- CHANGELOG.md | 1 + .../xcschemes/PostHogTests.xcscheme | 19 +++++++++++ PostHog/PostHogConfig.swift | 23 +++++++++++++ PostHog/PostHogSDK.swift | 32 +++++++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba7f38fd..f61757c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - recording: mask swiftui picker if masking enabled ([#184](https://github.com/PostHog/posthog-ios/pull/184)) - chore: add is identified property ([#186](https://github.com/PostHog/posthog-ios/pull/186)) +- chore: add `personProfiles` support ([#186](https://github.com/PostHog/posthog-ios/pull/186)) ## 3.9.1 - 2024-09-06 diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme index dcc85f91b..dc6bf09d4 100644 --- a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme @@ -47,6 +47,16 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> + + + + + + + + diff --git a/PostHog/PostHogConfig.swift b/PostHog/PostHogConfig.swift index 78487e9df..edeefc5ab 100644 --- a/PostHog/PostHogConfig.swift +++ b/PostHog/PostHogConfig.swift @@ -13,6 +13,27 @@ import Foundation case any } + /// Determines the behavior for processing user profiles. + /// - `never`: We won't process persons for any event. This means that anonymous users will not be merged once they sign up or login, so you lose the ability to create funnels that track users from anonymous to identified. All events (including `$identify`) will be sent with `$process_person_profile: False`. + /// - `always`: We will process persons data for all events. + /// - `identifiedOnly`: (default): we will only process persons when you call `identify`, `alias`, and `group`, Anonymous users won't get person profiles. + @objc(PostHogPersonProfiles) public enum PostHogPersonProfiles: Int { + case never + case always + case identifiedOnly + + var description: String { + switch self { + case .never: + return "never" + case .always: + return "always" + case .identifiedOnly: + return "identified_only" + } + } + } + @objc public let host: URL @objc public let apiKey: String @objc public var flushAt: Int = 20 @@ -30,6 +51,8 @@ import Foundation /// Hook that allows to sanitize the event properties /// The hook is called before the event is cached or sent over the wire @objc public var propertiesSanitizer: PostHogPropertiesSanitizer? + /// Determines the behavior for processing user profiles. + @objc public var personProfiles: PostHogPersonProfiles = .identifiedOnly /// Internal var snapshotEndpoint: String = "/s/" diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 351f0be7e..69a724f3a 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -239,6 +239,24 @@ let maxRetryDelay = 30.0 return properties } + private func hasPersonProcessing() -> Bool { + !( + config.personProfiles == .never || + ( + config.personProfiles == .identifiedOnly && + storageManager?.isIdentified() == false + ) + ) + } + + private func requirePersonProcessing() -> Bool { + if config.personProfiles == .never { + hedgeLog("personProfiles is set to `never`. This call will be ignored.") + return false + } + return true + } + private func buildProperties(distinctId: String, properties: [String: Any]?, userProperties: [String: Any]? = nil, @@ -276,6 +294,8 @@ let maxRetryDelay = 30.0 if let isIdentified = storageManager?.isIdentified() { props["$is_identified"] = isIdentified } + + props["$process_person_profile"] = hasPersonProcessing() } if let sessionId = PostHogSessionManager.shared.getSessionId() { @@ -407,6 +427,10 @@ let maxRetryDelay = 30.0 return } + if !requirePersonProcessing() { + return + } + guard let queue = queue, let storageManager = storageManager else { return } @@ -584,6 +608,10 @@ let maxRetryDelay = 30.0 return } + if !requirePersonProcessing() { + return + } + guard let queue = queue else { return } @@ -680,6 +708,10 @@ let maxRetryDelay = 30.0 return } + if !requirePersonProcessing() { + return + } + _ = groups([type: key]) groupIdentify(type: type, key: key, groupProperties: sanitizeDicionary(groupProperties)) From a0962f0f17b04eb1370476482de2224f9acea45e Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 9 Sep 2024 17:03:32 +0200 Subject: [PATCH 02/10] ref --- CHANGELOG.md | 2 +- PostHog.xcodeproj/project.pbxproj | 4 ++ PostHog/PostHogConfig.swift | 21 --------- PostHog/PostHogPersonProfiles.swift | 18 ++++++++ PostHogTests/PostHogSDKTest.swift | 71 ++++++++++++++++++++++++++++- 5 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 PostHog/PostHogPersonProfiles.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index f61757c8c..2542b640f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - recording: mask swiftui picker if masking enabled ([#184](https://github.com/PostHog/posthog-ios/pull/184)) - chore: add is identified property ([#186](https://github.com/PostHog/posthog-ios/pull/186)) -- chore: add `personProfiles` support ([#186](https://github.com/PostHog/posthog-ios/pull/186)) +- chore: add personProfiles support ([#187](https://github.com/PostHog/posthog-ios/pull/187)) ## 3.9.1 - 2024-09-06 diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj index 6c44cb052..5a84a6a98 100644 --- a/PostHog.xcodeproj/project.pbxproj +++ b/PostHog.xcodeproj/project.pbxproj @@ -90,6 +90,7 @@ 69BA38D72B888E8500AA69D6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */; }; 69ED1A5C2C7F15F300FE7A91 /* PostHogSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED1A5B2C7F15F300FE7A91 /* PostHogSessionManager.swift */; }; 69ED1A882C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED1A872C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift */; }; + 69ED1A9F2C8F451B00FE7A91 /* PostHogPersonProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED1A9E2C8F451B00FE7A91 /* PostHogPersonProfiles.swift */; }; 69EE82BA2BA9C50400EB9542 /* PostHogReplayIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */; }; 69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */; }; 69EE82BE2BA9C8AA00EB9542 /* ViewLayoutTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */; }; @@ -343,6 +344,7 @@ 69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 69ED1A5B2C7F15F300FE7A91 /* PostHogSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionManager.swift; sourceTree = ""; }; 69ED1A872C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSwiftUIViewModifiers.swift; sourceTree = ""; }; + 69ED1A9E2C8F451B00FE7A91 /* PostHogPersonProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogPersonProfiles.swift; sourceTree = ""; }; 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogReplayIntegration.swift; sourceTree = ""; }; 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionReplayConfig.swift; sourceTree = ""; }; 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLayoutTracker.swift; sourceTree = ""; }; @@ -552,6 +554,7 @@ 693E977A2C625208004B1030 /* PostHogPropertiesSanitizer.swift */, 69ED1A5B2C7F15F300FE7A91 /* PostHogSessionManager.swift */, 69ED1A872C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift */, + 69ED1A9E2C8F451B00FE7A91 /* PostHogPersonProfiles.swift */, ); path = PostHog; sourceTree = ""; @@ -1106,6 +1109,7 @@ 69261D1B2AD9678C00232EC7 /* PostHogEvent.swift in Sources */, 69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */, 69EE82CE2BAAC76000EB9542 /* ViewTreeSnapshotStatus.swift in Sources */, + 69ED1A9F2C8F451B00FE7A91 /* PostHogPersonProfiles.swift in Sources */, 69EE82BA2BA9C50400EB9542 /* PostHogReplayIntegration.swift in Sources */, 3AE3FB472992AB0000AFFC18 /* Hedgelog.swift in Sources */, 69261D132AD5685B00232EC7 /* PostHogFeatureFlags.swift in Sources */, diff --git a/PostHog/PostHogConfig.swift b/PostHog/PostHogConfig.swift index edeefc5ab..75170864a 100644 --- a/PostHog/PostHogConfig.swift +++ b/PostHog/PostHogConfig.swift @@ -13,27 +13,6 @@ import Foundation case any } - /// Determines the behavior for processing user profiles. - /// - `never`: We won't process persons for any event. This means that anonymous users will not be merged once they sign up or login, so you lose the ability to create funnels that track users from anonymous to identified. All events (including `$identify`) will be sent with `$process_person_profile: False`. - /// - `always`: We will process persons data for all events. - /// - `identifiedOnly`: (default): we will only process persons when you call `identify`, `alias`, and `group`, Anonymous users won't get person profiles. - @objc(PostHogPersonProfiles) public enum PostHogPersonProfiles: Int { - case never - case always - case identifiedOnly - - var description: String { - switch self { - case .never: - return "never" - case .always: - return "always" - case .identifiedOnly: - return "identified_only" - } - } - } - @objc public let host: URL @objc public let apiKey: String @objc public var flushAt: Int = 20 diff --git a/PostHog/PostHogPersonProfiles.swift b/PostHog/PostHogPersonProfiles.swift new file mode 100644 index 000000000..3675df031 --- /dev/null +++ b/PostHog/PostHogPersonProfiles.swift @@ -0,0 +1,18 @@ +// +// PostHogPersonProfiles.swift +// PostHog +// +// Created by Manoel Aranda Neto on 09.09.24. +// + +import Foundation + +/// Determines the behavior for processing user profiles. +/// - `never`: We won't process persons for any event. This means that anonymous users will not be merged once they sign up or login, so you lose the ability to create funnels that track users from anonymous to identified. All events (including `$identify`) will be sent with `$process_person_profile: False`. +/// - `always`: We will process persons data for all events. +/// - `identifiedOnly`: (default): we will only process persons when you call `identify`, `alias`, and `group`, Anonymous users won't get person profiles. +@objc(PostHogPersonProfiles) public enum PostHogPersonProfiles: Int { + case never + case always + case identifiedOnly +} diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index 9ffa00d22..49a012f00 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -17,7 +17,8 @@ class PostHogSDKTest: QuickSpec { captureApplicationLifecycleEvents: Bool = false, flushAt: Int = 1, optOut: Bool = false, - propertiesSanitizer: PostHogPropertiesSanitizer? = nil) -> PostHogSDK + propertiesSanitizer: PostHogPropertiesSanitizer? = nil, + personProfiles: PostHogPersonProfiles = .identifiedOnly) -> PostHogSDK { let config = PostHogConfig(apiKey: "123", host: "http://localhost:9001") config.flushAt = flushAt @@ -28,6 +29,7 @@ class PostHogSDKTest: QuickSpec { config.captureApplicationLifecycleEvents = captureApplicationLifecycleEvents config.optOut = optOut config.propertiesSanitizer = propertiesSanitizer + config.personProfiles = personProfiles return PostHogSDK.with(config) } @@ -791,6 +793,73 @@ class PostHogSDKTest: QuickSpec { sut.close() } + + it("capture sets process person to false if identified only and not identified") { + let sut = self.getSut() + + sut.capture("test event", + properties: ["foo": "bar"], + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"], + groups: ["groupProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == false + + sut.reset() + sut.close() + } + + it("capture sets process person to true if identified only and identified") { + let sut = self.getSut(flushAt: 2) + + sut.identify("distinctId", + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"]) + + sut.capture("test event", + properties: ["foo": "bar"], + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"], + groups: ["groupProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 2 + + let event = events.last! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + +// it("capture sets process person to true if always") { +// let sut = self.getSut(personProfiles: .always) +// +// sut.capture("test event", +// properties: ["foo": "bar"], +// userProperties: ["userProp": "value"], +// userPropertiesSetOnce: ["userPropOnce": "value"], +// groups: ["groupProp": "value"]) +// +// let events = getBatchedEvents(server) +// +// expect(events.count) == 1 +// +// let event = events.first! +// +// expect(event.properties["$process_person_profile"] as? Bool) == true +// +// sut.reset() +// sut.close() +// } } } From 100a2bd02a03db3859ccd52ee8466eb18115747e Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 9 Sep 2024 17:40:24 +0200 Subject: [PATCH 03/10] tests --- PostHogTests/PostHogSDKTest.swift | 65 +++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index 49a012f00..3b74b394c 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -840,26 +840,51 @@ class PostHogSDKTest: QuickSpec { sut.close() } -// it("capture sets process person to true if always") { -// let sut = self.getSut(personProfiles: .always) -// -// sut.capture("test event", -// properties: ["foo": "bar"], -// userProperties: ["userProp": "value"], -// userPropertiesSetOnce: ["userPropOnce": "value"], -// groups: ["groupProp": "value"]) -// -// let events = getBatchedEvents(server) -// -// expect(events.count) == 1 -// -// let event = events.first! -// -// expect(event.properties["$process_person_profile"] as? Bool) == true -// -// sut.reset() -// sut.close() -// } + it("capture sets process person to true if always") { + let sut = self.getSut(personProfiles: .always) + + sut.capture("test event", + properties: ["foo": "bar"], + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"], + groups: ["groupProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture sets process person to false if never") { + let sut = self.getSut(personProfiles: .never) + + sut.identify("distinctId", + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"]) + + sut.capture("test event", + properties: ["foo": "bar"], + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"], + groups: ["groupProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == false + + sut.reset() + sut.close() + } } } From 5a4a371f3aabb37f6a4b7bb648ed11e8340f662b Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 9 Sep 2024 17:42:52 +0200 Subject: [PATCH 04/10] add comment --- PostHogTests/PostHogSDKTest.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index 3b74b394c..ca8debe60 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -876,6 +876,7 @@ class PostHogSDKTest: QuickSpec { let events = getBatchedEvents(server) + // identify, alias and group will be ignored here hence only 1 expect(events.count) == 1 let event = events.first! From b59e459073eb0a07c063e773e242620166a87dda Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 10 Sep 2024 14:09:01 +0200 Subject: [PATCH 05/10] review --- PostHog/PostHogPersonProfiles.swift | 4 +++- PostHog/PostHogSDK.swift | 10 +++++++++- PostHog/PostHogStorage.swift | 2 ++ PostHog/PostHogStorageManager.swift | 27 +++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/PostHog/PostHogPersonProfiles.swift b/PostHog/PostHogPersonProfiles.swift index 3675df031..ddcaa524a 100644 --- a/PostHog/PostHogPersonProfiles.swift +++ b/PostHog/PostHogPersonProfiles.swift @@ -8,7 +8,9 @@ import Foundation /// Determines the behavior for processing user profiles. -/// - `never`: We won't process persons for any event. This means that anonymous users will not be merged once they sign up or login, so you lose the ability to create funnels that track users from anonymous to identified. All events (including `$identify`) will be sent with `$process_person_profile: False`. +/// - `never`: We won't process persons for any event. This means that anonymous users will not be merged once +/// they sign up or login, so you lose the ability to create funnels that track users from anonymous to identified. +/// All events (including `$identify`) will be sent with `$process_person_profile: False`. /// - `always`: We will process persons data for all events. /// - `identifiedOnly`: (default): we will only process persons when you call `identify`, `alias`, and `group`, Anonymous users won't get person profiles. @objc(PostHogPersonProfiles) public enum PostHogPersonProfiles: Int { diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 69a724f3a..74231c36d 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -244,7 +244,8 @@ let maxRetryDelay = 30.0 config.personProfiles == .never || ( config.personProfiles == .identifiedOnly && - storageManager?.isIdentified() == false + storageManager?.isIdentified() == false && + storageManager?.isPersonProcessing() == false ) ) } @@ -254,6 +255,7 @@ let maxRetryDelay = 30.0 hedgeLog("personProfiles is set to `never`. This call will be ignored.") return false } + storageManager?.setPersonProcessing(true) return true } @@ -532,6 +534,12 @@ let maxRetryDelay = 30.0 let distinctId = getDistinctId() + // if the user isn't identified but passed userProperties or userPropertiesSetOnce, + // we should still enable person processing since this is intentional + if userProperties?.isEmpty == false || userPropertiesSetOnce?.isEmpty == false { + requirePersonProcessing() + } + let properties = buildProperties(distinctId: distinctId, properties: sanitizeDicionary(properties), userProperties: sanitizeDicionary(userProperties), diff --git a/PostHog/PostHogStorage.swift b/PostHog/PostHogStorage.swift index c56dc5a2f..b905a2338 100644 --- a/PostHog/PostHogStorage.swift +++ b/PostHog/PostHogStorage.swift @@ -34,6 +34,7 @@ class PostHogStorage { case optOut = "posthog.optOut" case sessionReplay = "posthog.sessionReplay" case isIdentified = "posthog.isIdentified" + case personProcessingEnabled = "posthog.personProcessingEnabled" } private let config: PostHogConfig @@ -141,6 +142,7 @@ class PostHogStorage { deleteSafely(url(forKey: .optOut)) deleteSafely(url(forKey: .sessionReplay)) deleteSafely(url(forKey: .isIdentified)) + deleteSafely(url(forKey: .personProcessingEnabled)) } public func remove(key: StorageKey) { diff --git a/PostHog/PostHogStorageManager.swift b/PostHog/PostHogStorageManager.swift index e4767143b..f94f1ecb5 100644 --- a/PostHog/PostHogStorageManager.swift +++ b/PostHog/PostHogStorageManager.swift @@ -13,12 +13,14 @@ class PostHogStorageManager { private let anonLock = NSLock() private let distinctLock = NSLock() private let identifiedLock = NSLock() + private let personProcessingLock = NSLock() private let idGen: (UUID) -> UUID private var distinctId: String? private var cachedDistinctId = false private var anonymousId: String? private var isIdentifiedValue: Bool? + private var personProcessingEnabled: Bool? init(_ config: PostHogConfig) { storage = PostHogStorage(config) @@ -105,6 +107,25 @@ class PostHogStorageManager { } } + public func isPersonProcessing() -> Bool { + personProcessingLock.withLock { + if personProcessingEnabled == nil { + personProcessingEnabled = storage.getBool(forKey: .personProcessingEnabled) ?? false + } + } + return personProcessingEnabled ?? false + } + + public func setPersonProcessing(_ personProcessingEnabled: Bool) { + personProcessingLock.withLock { + // only set if its different to avoid IO since this is called more often + if self.personProcessingEnabled != personProcessingEnabled { + self.personProcessingEnabled = personProcessingEnabled + storage.setBool(forKey: .personProcessingEnabled, contents: personProcessingEnabled) + } + } + } + public func reset(_ resetStorage: Bool = false) { // resetStorage is only used for testing, when the reset method is called, // the storage is also cleared, so we dont do here to not do it twice. @@ -127,5 +148,11 @@ class PostHogStorageManager { storage.remove(key: .isIdentified) } } + personProcessingLock.withLock { + personProcessingEnabled = nil + if resetStorage { + storage.remove(key: .personProcessingEnabled) + } + } } } From d26b1357ec749d1bc23790ae7d217d73edc737fe Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 10 Sep 2024 14:20:03 +0200 Subject: [PATCH 06/10] fix tests --- PostHog/PostHogSDK.swift | 2 +- PostHogTests/PostHogSDKTest.swift | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 74231c36d..e4db07cbb 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -536,7 +536,7 @@ let maxRetryDelay = 30.0 // if the user isn't identified but passed userProperties or userPropertiesSetOnce, // we should still enable person processing since this is intentional - if userProperties?.isEmpty == false || userPropertiesSetOnce?.isEmpty == false { + if userProperties?.isEmpty == false || userPropertiesSetOnce?.isEmpty == false || groups?.isEmpty == false { requirePersonProcessing() } diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index ca8debe60..88d859f90 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -798,10 +798,7 @@ class PostHogSDKTest: QuickSpec { let sut = self.getSut() sut.capture("test event", - properties: ["foo": "bar"], - userProperties: ["userProp": "value"], - userPropertiesSetOnce: ["userPropOnce": "value"], - groups: ["groupProp": "value"]) + properties: ["foo": "bar"]) let events = getBatchedEvents(server) From 275ee23dec16eef6c140bfc6ef84f7969f06f4a4 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 10 Sep 2024 14:26:53 +0200 Subject: [PATCH 07/10] ref tests --- PostHog.xcodeproj/project.pbxproj | 4 + .../PostHogSDKPersonProfilesTest.swift | 182 ++++++++++++++++++ PostHogTests/PostHogSDKTest.swift | 90 --------- 3 files changed, 186 insertions(+), 90 deletions(-) create mode 100644 PostHogTests/PostHogSDKPersonProfilesTest.swift diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj index 5a84a6a98..968874b2c 100644 --- a/PostHog.xcodeproj/project.pbxproj +++ b/PostHog.xcodeproj/project.pbxproj @@ -91,6 +91,7 @@ 69ED1A5C2C7F15F300FE7A91 /* PostHogSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED1A5B2C7F15F300FE7A91 /* PostHogSessionManager.swift */; }; 69ED1A882C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED1A872C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift */; }; 69ED1A9F2C8F451B00FE7A91 /* PostHogPersonProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED1A9E2C8F451B00FE7A91 /* PostHogPersonProfiles.swift */; }; + 69ED1AB62C90711D00FE7A91 /* PostHogSDKPersonProfilesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69ED1AB52C90711D00FE7A91 /* PostHogSDKPersonProfilesTest.swift */; }; 69EE82BA2BA9C50400EB9542 /* PostHogReplayIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */; }; 69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */; }; 69EE82BE2BA9C8AA00EB9542 /* ViewLayoutTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */; }; @@ -345,6 +346,7 @@ 69ED1A5B2C7F15F300FE7A91 /* PostHogSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionManager.swift; sourceTree = ""; }; 69ED1A872C89B73100FE7A91 /* PostHogSwiftUIViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSwiftUIViewModifiers.swift; sourceTree = ""; }; 69ED1A9E2C8F451B00FE7A91 /* PostHogPersonProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogPersonProfiles.swift; sourceTree = ""; }; + 69ED1AB52C90711D00FE7A91 /* PostHogSDKPersonProfilesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSDKPersonProfilesTest.swift; sourceTree = ""; }; 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogReplayIntegration.swift; sourceTree = ""; }; 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionReplayConfig.swift; sourceTree = ""; }; 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLayoutTracker.swift; sourceTree = ""; }; @@ -574,6 +576,7 @@ 690FF0F42AF0F06100A0B06B /* PostHogSDKTest.swift */, 699C5FEE2C20242A007DB818 /* UUIDTest.swift */, 693E977C2C6257F9004B1030 /* ExampleSanitizer.swift */, + 69ED1AB52C90711D00FE7A91 /* PostHogSDKPersonProfilesTest.swift */, ); path = PostHogTests; sourceTree = ""; @@ -1129,6 +1132,7 @@ 690FF0E12AEFC59100A0B06B /* PostHogFileBackedQueueTest.swift in Sources */, 3A62647529CB0168007E8C07 /* TestPostHog.swift in Sources */, 699C5FEF2C20242A007DB818 /* UUIDTest.swift in Sources */, + 69ED1AB62C90711D00FE7A91 /* PostHogSDKPersonProfilesTest.swift in Sources */, 3A62646A29C9E385007E8C07 /* MockPostHogServer.swift in Sources */, 690FF0BB2AEF8B8200A0B06B /* PostHogContextTest.swift in Sources */, 690FF0E32AEFD12900A0B06B /* PostHogConfigTest.swift in Sources */, diff --git a/PostHogTests/PostHogSDKPersonProfilesTest.swift b/PostHogTests/PostHogSDKPersonProfilesTest.swift new file mode 100644 index 000000000..142313c43 --- /dev/null +++ b/PostHogTests/PostHogSDKPersonProfilesTest.swift @@ -0,0 +1,182 @@ +// +// PostHogSDKPersonProfilesTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 10.09.24. +// + +import Foundation +import Nimble +import Quick + +@testable import PostHog + +class PostHogSDKPersonProfilesTest: QuickSpec { + func getSut(flushAt: Int = 1, + personProfiles: PostHogPersonProfiles = .identifiedOnly) -> PostHogSDK + { + let config = PostHogConfig(apiKey: "123", host: "http://localhost:9001") + config.flushAt = flushAt + config.preloadFeatureFlags = false + config.sendFeatureFlagEvent = false + config.disableReachabilityForTesting = true + config.disableQueueTimerForTesting = true + config.captureApplicationLifecycleEvents = false + config.personProfiles = personProfiles + return PostHogSDK.with(config) + } + + override func spec() { + var server: MockPostHogServer! + + func deleteDefaults() { + let userDefaults = UserDefaults.standard + userDefaults.removeObject(forKey: "PHGVersionKey") + userDefaults.removeObject(forKey: "PHGBuildKeyV2") + userDefaults.synchronize() + + deleteSafely(applicationSupportDirectoryURL()) + } + + beforeEach { + deleteDefaults() + server = MockPostHogServer() + server.start() + } + afterEach { + server.stop() + server = nil + } + + it("capture sets process person to false if identified only and not identified") { + let sut = self.getSut() + + sut.capture("test event") + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == false + + sut.reset() + sut.close() + } + + it("capture sets process person to true if identified only and with user props") { + let sut = self.getSut() + + sut.capture("test event", + userProperties: ["userProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture sets process person to true if identified only and with user set once props") { + let sut = self.getSut() + + sut.capture("test event", + userPropertiesSetOnce: ["userProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture sets process person to true if identified only and with group props") { + let sut = self.getSut() + + sut.capture("test event", + groups: ["groupProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture sets process person to true if identified only and identified") { + let sut = self.getSut(flushAt: 2) + + sut.identify("distinctId") + + sut.capture("test event") + + let events = getBatchedEvents(server) + + expect(events.count) == 2 + + let event = events.last! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture sets process person to true if always") { + let sut = self.getSut(personProfiles: .always) + + sut.capture("test event") + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture sets process person to false if never") { + let sut = self.getSut(personProfiles: .never) + + sut.identify("distinctId") + + sut.capture("test event") + + let events = getBatchedEvents(server) + + // identify, alias and group will be ignored here hence only 1 + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == false + + sut.reset() + sut.close() + } + } +} + +private class MockDate { + var date = Date() +} diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index 88d859f90..8473a786c 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -793,96 +793,6 @@ class PostHogSDKTest: QuickSpec { sut.close() } - - it("capture sets process person to false if identified only and not identified") { - let sut = self.getSut() - - sut.capture("test event", - properties: ["foo": "bar"]) - - let events = getBatchedEvents(server) - - expect(events.count) == 1 - - let event = events.first! - - expect(event.properties["$process_person_profile"] as? Bool) == false - - sut.reset() - sut.close() - } - - it("capture sets process person to true if identified only and identified") { - let sut = self.getSut(flushAt: 2) - - sut.identify("distinctId", - userProperties: ["userProp": "value"], - userPropertiesSetOnce: ["userPropOnce": "value"]) - - sut.capture("test event", - properties: ["foo": "bar"], - userProperties: ["userProp": "value"], - userPropertiesSetOnce: ["userPropOnce": "value"], - groups: ["groupProp": "value"]) - - let events = getBatchedEvents(server) - - expect(events.count) == 2 - - let event = events.last! - - expect(event.properties["$process_person_profile"] as? Bool) == true - - sut.reset() - sut.close() - } - - it("capture sets process person to true if always") { - let sut = self.getSut(personProfiles: .always) - - sut.capture("test event", - properties: ["foo": "bar"], - userProperties: ["userProp": "value"], - userPropertiesSetOnce: ["userPropOnce": "value"], - groups: ["groupProp": "value"]) - - let events = getBatchedEvents(server) - - expect(events.count) == 1 - - let event = events.first! - - expect(event.properties["$process_person_profile"] as? Bool) == true - - sut.reset() - sut.close() - } - - it("capture sets process person to false if never") { - let sut = self.getSut(personProfiles: .never) - - sut.identify("distinctId", - userProperties: ["userProp": "value"], - userPropertiesSetOnce: ["userPropOnce": "value"]) - - sut.capture("test event", - properties: ["foo": "bar"], - userProperties: ["userProp": "value"], - userPropertiesSetOnce: ["userPropOnce": "value"], - groups: ["groupProp": "value"]) - - let events = getBatchedEvents(server) - - // identify, alias and group will be ignored here hence only 1 - expect(events.count) == 1 - - let event = events.first! - - expect(event.properties["$process_person_profile"] as? Bool) == false - - sut.reset() - sut.close() - } } } From 8bf562ec4457446727845fc4d10145e3c1b3a8f0 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 10 Sep 2024 14:33:10 +0200 Subject: [PATCH 08/10] tests --- .../PostHogSDKPersonProfilesTest.swift | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/PostHogTests/PostHogSDKPersonProfilesTest.swift b/PostHogTests/PostHogSDKPersonProfilesTest.swift index 142313c43..aff6d19c5 100644 --- a/PostHogTests/PostHogSDKPersonProfilesTest.swift +++ b/PostHogTests/PostHogSDKPersonProfilesTest.swift @@ -138,6 +138,44 @@ class PostHogSDKPersonProfilesTest: QuickSpec { sut.close() } + it("capture sets process person to true if identified only and with alias") { + let sut = self.getSut(flushAt: 2) + + sut.alias("distinctId") + + sut.capture("test event") + + let events = getBatchedEvents(server) + + expect(events.count) == 2 + + let event = events.last! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture sets process person to true if identified only and with groups") { + let sut = self.getSut(flushAt: 2) + + sut.group(type: "theType", key: "theKey") + + sut.capture("test event") + + let events = getBatchedEvents(server) + + expect(events.count) == 2 + + let event = events.last! + + expect(event.properties["$process_person_profile"] as? Bool) == true + + sut.reset() + sut.close() + } + it("capture sets process person to true if always") { let sut = self.getSut(personProfiles: .always) @@ -155,7 +193,7 @@ class PostHogSDKPersonProfilesTest: QuickSpec { sut.close() } - it("capture sets process person to false if never") { + it("capture sets process person to false if never and identify called") { let sut = self.getSut(personProfiles: .never) sut.identify("distinctId") @@ -164,7 +202,47 @@ class PostHogSDKPersonProfilesTest: QuickSpec { let events = getBatchedEvents(server) - // identify, alias and group will be ignored here hence only 1 + // identify will be ignored here hence only 1 + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == false + + sut.reset() + sut.close() + } + + it("capture sets process person to false if never and alias called") { + let sut = self.getSut(personProfiles: .never) + + sut.alias("distinctId") + + sut.capture("test event") + + let events = getBatchedEvents(server) + + // alias will be ignored here hence only 1 + expect(events.count) == 1 + + let event = events.first! + + expect(event.properties["$process_person_profile"] as? Bool) == false + + sut.reset() + sut.close() + } + + it("capture sets process person to false if never and group called") { + let sut = self.getSut(personProfiles: .never) + + sut.group(type: "theType", key: "theKey") + + sut.capture("test event") + + let events = getBatchedEvents(server) + + // group will be ignored here hence only 1 expect(events.count) == 1 let event = events.first! From 84dbe06bf02ccc497ca80bc5f4c65ffcf0000955 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 10 Sep 2024 14:37:06 +0200 Subject: [PATCH 09/10] fix --- PostHog/PostHogSDK.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index e4db07cbb..7d78b77b4 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -534,7 +534,7 @@ let maxRetryDelay = 30.0 let distinctId = getDistinctId() - // if the user isn't identified but passed userProperties or userPropertiesSetOnce, + // if the user isn't identified but passed userProperties, userPropertiesSetOnce or groups, // we should still enable person processing since this is intentional if userProperties?.isEmpty == false || userPropertiesSetOnce?.isEmpty == false || groups?.isEmpty == false { requirePersonProcessing() From f095654fc5638d38f84335edbe09de40b90c50a5 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 10 Sep 2024 16:13:48 +0200 Subject: [PATCH 10/10] ref --- PostHog/PostHogStorage.swift | 2 +- PostHog/PostHogStorageManager.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/PostHog/PostHogStorage.swift b/PostHog/PostHogStorage.swift index b905a2338..6160bca29 100644 --- a/PostHog/PostHogStorage.swift +++ b/PostHog/PostHogStorage.swift @@ -34,7 +34,7 @@ class PostHogStorage { case optOut = "posthog.optOut" case sessionReplay = "posthog.sessionReplay" case isIdentified = "posthog.isIdentified" - case personProcessingEnabled = "posthog.personProcessingEnabled" + case personProcessingEnabled = "posthog.enabledPersonProcessing" } private let config: PostHogConfig diff --git a/PostHog/PostHogStorageManager.swift b/PostHog/PostHogStorageManager.swift index f94f1ecb5..326832275 100644 --- a/PostHog/PostHogStorageManager.swift +++ b/PostHog/PostHogStorageManager.swift @@ -116,12 +116,12 @@ class PostHogStorageManager { return personProcessingEnabled ?? false } - public func setPersonProcessing(_ personProcessingEnabled: Bool) { + public func setPersonProcessing(_ enable: Bool) { personProcessingLock.withLock { // only set if its different to avoid IO since this is called more often - if self.personProcessingEnabled != personProcessingEnabled { - self.personProcessingEnabled = personProcessingEnabled - storage.setBool(forKey: .personProcessingEnabled, contents: personProcessingEnabled) + if self.personProcessingEnabled != enable { + self.personProcessingEnabled = enable + storage.setBool(forKey: .personProcessingEnabled, contents: enable) } } }