diff --git a/CHANGELOG.md b/CHANGELOG.md index 821f9a9e5..cacaa5573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- fix $feature_flag_called not captured after reloading flags ([#232](https://github.com/PostHog/posthog-ios/pull/232)) + ## 3.14.1 - 2024-11-05 - recording: fix RN iOS masking ([#230](https://github.com/PostHog/posthog-ios/pull/230)) diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 0426d154d..9af37e16e 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -30,6 +30,7 @@ let maxRetryDelay = 30.0 private let setupLock = NSLock() private let optOutLock = NSLock() private let groupsLock = NSLock() + private let flagCallReportedLock = NSLock() private let personPropsLock = NSLock() private var queue: PostHogQueue? @@ -347,7 +348,9 @@ let maxRetryDelay = 30.0 // storage also removes all feature flags storage?.reset() config.storageManager?.reset() - flagCallReported.removeAll() + flagCallReportedLock.withLock { + flagCallReported.removeAll() + } PostHogSessionManager.shared.endSession { self.resetViews() } @@ -797,7 +800,12 @@ let maxRetryDelay = 30.0 distinctId: storageManager.getDistinctId(), anonymousId: storageManager.getAnonymousId(), groups: groups ?? [:], - callback: callback + callback: { + self.flagCallReportedLock.withLock { + self.flagCallReported.removeAll() + } + callback() + } ) } @@ -850,14 +858,20 @@ let maxRetryDelay = 30.0 } private func reportFeatureFlagCalled(flagKey: String, flagValue: Any?) { - if !flagCallReported.contains(flagKey) { - let properties: [String: Any] = [ + var shouldCapture = false + + flagCallReportedLock.withLock { + if !flagCallReported.contains(flagKey) { + flagCallReported.insert(flagKey) + shouldCapture = true + } + } + + if shouldCapture { + let properties = [ "$feature_flag": flagKey, "$feature_flag_response": flagValue ?? "", ] - - flagCallReported.insert(flagKey) - capture("$feature_flag_called", properties: properties) } } @@ -932,7 +946,9 @@ let maxRetryDelay = 30.0 self.reachability?.stopNotifier() reachability = nil #endif - flagCallReported.removeAll() + flagCallReportedLock.withLock { + flagCallReported.removeAll() + } context = nil PostHogSessionManager.shared.endSession { self.resetViews() diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index a182bf5f8..a7fd418c1 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -10,18 +10,21 @@ import Nimble import Quick @testable import PostHog +import XCTest class PostHogSDKTest: QuickSpec { func getSut(preloadFeatureFlags: Bool = false, sendFeatureFlagEvent: Bool = false, captureApplicationLifecycleEvents: Bool = false, flushAt: Int = 1, + maxBatchSize: Int = 50, optOut: Bool = false, propertiesSanitizer: PostHogPropertiesSanitizer? = nil, personProfiles: PostHogPersonProfiles = .identifiedOnly) -> PostHogSDK { let config = PostHogConfig(apiKey: "123", host: "http://localhost:9001") config.flushAt = flushAt + config.maxBatchSize = maxBatchSize config.preloadFeatureFlags = preloadFeatureFlags config.sendFeatureFlagEvent = sendFeatureFlagEvent config.disableReachabilityForTesting = true @@ -850,6 +853,52 @@ class PostHogSDKTest: QuickSpec { sut.reset() sut.close() } + + it("captures $feature_flag_called when getFeatureFlag is called") { + let sut = self.getSut( + sendFeatureFlagEvent: true, + flushAt: 1 + ) + + _ = sut.getFeatureFlag("some_key") + + let event = getBatchedEvents(server) + expect(event.first!.event).to(equal("$feature_flag_called")) + } + + it("does not capture $feature_flag_called when getFeatureFlag is called twice") { + let sut = self.getSut( + sendFeatureFlagEvent: true, + flushAt: 2 + ) + + _ = sut.getFeatureFlag("some_key") + _ = sut.getFeatureFlag("some_key") + sut.capture("force_batch_flush") + + let event = getBatchedEvents(server) + expect(event.count).to(equal(2)) + expect(event[0].event).to(equal("$feature_flag_called")) + expect(event[1].event).to(equal("force_batch_flush")) + } + + it("captures $feature_flag_called when getFeatureFlag called twice after reloading flags") { + let sut = self.getSut( + sendFeatureFlagEvent: true, + flushAt: 2 + ) + + _ = sut.getFeatureFlag("some_key") + + sut.reloadFeatureFlags { + _ = sut.getFeatureFlag("some_key") + } + + let event = getBatchedEvents(server) + expect(event.count).to(equal(2)) + expect(event[0].event).to(equal("$feature_flag_called")) + expect(event[1].event).to(equal("$feature_flag_called")) + } } }