From 8545c31678de86991117de1ad3d7a4a02205793a Mon Sep 17 00:00:00 2001 From: Ioannis J Date: Mon, 4 Nov 2024 09:09:59 +0200 Subject: [PATCH 1/3] feat(capture): add a signature with timestamp parameter --- PostHog/PostHogSDK.swift | 39 ++++++--- PostHogObjCExample/AppDelegate.m | 140 +++++++++++++++++------------- PostHogTests/PostHogSDKTest.swift | 39 ++++++++- 3 files changed, 146 insertions(+), 72 deletions(-) diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 971ab089e..173a1c970 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -458,7 +458,7 @@ let maxRetryDelay = 30.0 let properties = buildProperties(distinctId: distinctId, properties: [ "distinct_id": distinctId, - "$anon_distinct_id": oldDistinctId, + "$anon_distinct_id": oldDistinctId ], userProperties: sanitizeDicionary(userProperties), userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce)) let sanitizedProperties = sanitizeProperties(properties) @@ -474,6 +474,14 @@ let maxRetryDelay = 30.0 } } + private func isOptOutState() -> Bool { + if config.optOut { + hedgeLog("PostHog is in OptOut state.") + return true + } + return false + } + @objc public func capture(_ event: String) { capture(event, distinctId: nil, properties: nil, userProperties: nil, userPropertiesSetOnce: nil, groups: nil) } @@ -502,14 +510,6 @@ let maxRetryDelay = 30.0 capture(event, distinctId: nil, properties: properties, userProperties: userProperties, userPropertiesSetOnce: userPropertiesSetOnce, groups: nil) } - private func isOptOutState() -> Bool { - if config.optOut { - hedgeLog("PostHog is in OptOut state.") - return true - } - return false - } - @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:groups:) public func capture(_ event: String, properties: [String: Any]? = nil, @@ -527,6 +527,18 @@ let maxRetryDelay = 30.0 userProperties: [String: Any]? = nil, userPropertiesSetOnce: [String: Any]? = nil, groups: [String: String]? = nil) + { + capture(event, distinctId: distinctId, properties: properties, userProperties: userProperties, userPropertiesSetOnce: userPropertiesSetOnce, groups: groups, timestamp: nil) + } + + @objc(captureWithEvent:distinctId:properties:userProperties:userPropertiesSetOnce:groups:timestamp:) + public func capture(_ event: String, + distinctId: String? = nil, + properties: [String: Any]? = nil, + userProperties: [String: Any]? = nil, + userPropertiesSetOnce: [String: Any]? = nil, + groups: [String: String]? = nil, + timestamp: Date? = nil) { if !isEnabled() { return @@ -552,6 +564,8 @@ let maxRetryDelay = 30.0 } } + let eventTimestamp = timestamp ?? Date() + let eventDistinctId = distinctId ?? getDistinctId() // if the user isn't identified but passed userProperties, userPropertiesSetOnce or groups, @@ -571,7 +585,8 @@ let maxRetryDelay = 30.0 let posthogEvent = PostHogEvent( event: event, distinctId: eventDistinctId, - properties: sanitizedProperties + properties: sanitizedProperties, + timestamp: eventTimestamp ) // Session Replay has its own queue @@ -605,7 +620,7 @@ let maxRetryDelay = 30.0 } let props = [ - "$screen_name": screenTitle, + "$screen_name": screenTitle ].merging(sanitizeDicionary(properties) ?? [:]) { prop, _ in prop } let distinctId = getDistinctId() @@ -826,7 +841,7 @@ let maxRetryDelay = 30.0 if !flagCallReported.contains(flagKey) { let properties: [String: Any] = [ "$feature_flag": flagKey, - "$feature_flag_response": flagValue ?? "", + "$feature_flag_response": flagValue ?? "" ] flagCallReported.insert(flagKey) diff --git a/PostHogObjCExample/AppDelegate.m b/PostHogObjCExample/AppDelegate.m index f8f0a5010..bd470af76 100644 --- a/PostHogObjCExample/AppDelegate.m +++ b/PostHogObjCExample/AppDelegate.m @@ -53,64 +53,88 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( userPropertiesSetOnce:userPropertiesSetOnce ]; -// NSLog(@"getDistinctId: %@", [[PostHogSDK shared] getDistinctId]); -// NSLog(@"getAnonymousId: %@", [[PostHogSDK shared] getAnonymousId]); -// -// NSMutableDictionary *props = [NSMutableDictionary dictionary]; -// props[@"state"] = @"running"; -// -// NSMutableDictionary *userProps = [NSMutableDictionary dictionary]; -// userProps[@"userAge"] = @50; -// -// NSMutableDictionary *userPropsOnce = [NSMutableDictionary dictionary]; -// userPropsOnce[@"userAlive"] = @YES; -// -// NSMutableDictionary *groupProps = [NSMutableDictionary dictionary]; -// groupProps[@"groupName"] = @"theGroup"; -// -// NSMutableDictionary *registerProps = [NSMutableDictionary dictionary]; -// props[@"loggedIn"] = @YES; -// [[PostHogSDK shared] registerProperties:registerProps]; -// [[PostHogSDK shared] unregisterProperties:@"test2"]; -// -// [[PostHogSDK shared] identify:@"my_new_id"]; -// [[PostHogSDK shared] identifyWithDistinctId:@"my_new_id" userProperties:userProps]; -// [[PostHogSDK shared] identifyWithDistinctId:@"my_new_id" userProperties:userProps userPropertiesSetOnce:userPropsOnce]; -// -// -// [[PostHogSDK shared] optIn]; -// [[PostHogSDK shared] optOut]; -// NSLog(@"isOptOut: %d", [[PostHogSDK shared] isOptOut]); -// NSLog(@"isFeatureEnabled: %d", [[PostHogSDK shared] isFeatureEnabled:@"myFlag"]); -// NSLog(@"getFeatureFlag: %@", [[PostHogSDK shared] getFeatureFlag:@"myFlag"]); -// NSLog(@"getFeatureFlagPayload: %@", [[PostHogSDK shared] getFeatureFlagPayload:@"myFlag"]); -// -// [[PostHogSDK shared] reloadFeatureFlags]; -// [[PostHogSDK shared] reloadFeatureFlagsWithCallback:^(){ -// NSLog(@"called"); -// }]; -// -// [[PostHogSDK shared] capture:@"theEvent"]; -// [[PostHogSDK shared] captureWithEvent:@"theEvent" properties:props]; -// [[PostHogSDK shared] captureWithEvent:@"theEvent" properties:props userProperties:userProps]; -// [[PostHogSDK shared] captureWithEvent:@"theEvent" properties:props userProperties:userProps userPropertiesSetOnce:userPropsOnce]; -// [[PostHogSDK shared] captureWithEvent:@"theEvent" distinctId:@"custom_distinct_id" properties:props userProperties:userProps userPropertiesSetOnce:userPropsOnce groupProperties:groupProps]; -// -// [[PostHogSDK shared] groupWithType:@"theType" key:@"theKey"]; -// [[PostHogSDK shared] groupWithType:@"theType" key:@"theKey" groupProperties:groupProps]; -// -// [[PostHogSDK shared] alias:@"theAlias"]; -// -// [[PostHogSDK shared] screen:@"theScreen"]; -// [[PostHogSDK shared] screenWithTitle:@"theScreen" properties:props]; - -// [[PostHogSDK shared] flush]; -// [[PostHogSDK shared] reset]; -// [[PostHogSDK shared] close]; - -// PostHogSDK *postHog = [PostHogSDK with:config]; -// -// [postHog capture:@"theCapture"]; + NSLog(@"getDistinctId: %@", [[PostHogSDK shared] getDistinctId]); + NSLog(@"getAnonymousId: %@", [[PostHogSDK shared] getAnonymousId]); + + NSMutableDictionary *props = [NSMutableDictionary dictionary]; + props[@"state"] = @"running"; + + NSMutableDictionary *userProps = [NSMutableDictionary dictionary]; + userProps[@"userAge"] = @50; + + NSMutableDictionary *userPropsOnce = [NSMutableDictionary dictionary]; + userPropsOnce[@"userAlive"] = @YES; + + NSMutableDictionary *groupProps = [NSMutableDictionary dictionary]; + groupProps[@"groupName"] = @"theGroup"; + + NSMutableDictionary *registerProps = [NSMutableDictionary dictionary]; + props[@"loggedIn"] = @YES; + [[PostHogSDK shared] registerProperties:registerProps]; + [[PostHogSDK shared] unregisterProperties:@"test2"]; + + [[PostHogSDK shared] identify:@"my_new_id"]; + [[PostHogSDK shared] identifyWithDistinctId:@"my_new_id" userProperties:userProps]; + [[PostHogSDK shared] identifyWithDistinctId:@"my_new_id" userProperties:userProps userPropertiesSetOnce:userPropsOnce]; + + + [[PostHogSDK shared] optIn]; + [[PostHogSDK shared] optOut]; + NSLog(@"isOptOut: %d", [[PostHogSDK shared] isOptOut]); + NSLog(@"isFeatureEnabled: %d", [[PostHogSDK shared] isFeatureEnabled:@"myFlag"]); + NSLog(@"getFeatureFlag: %@", [[PostHogSDK shared] getFeatureFlag:@"myFlag"]); + NSLog(@"getFeatureFlagPayload: %@", [[PostHogSDK shared] getFeatureFlagPayload:@"myFlag"]); + + [[PostHogSDK shared] reloadFeatureFlags]; + [[PostHogSDK shared] reloadFeatureFlagsWithCallback:^(){ + NSLog(@"called"); + }]; + + [[PostHogSDK shared] capture:@"theEvent"]; + + [[PostHogSDK shared] captureWithEvent:@"theEvent" + properties:props]; + + [[PostHogSDK shared] captureWithEvent:@"theEvent" + properties:props + userProperties:userProps]; + + [[PostHogSDK shared] captureWithEvent:@"theEvent" + properties:props + userProperties:userProps + userPropertiesSetOnce:userPropsOnce]; + + [[PostHogSDK shared] captureWithEvent:@"theEvent" + distinctId:@"custom_distinct_id" + properties:props + userProperties:userProps + userPropertiesSetOnce:userPropsOnce + groups:groupProps]; + + [[PostHogSDK shared] captureWithEvent:@"theEvent" + distinctId:@"custom_distinct_id" + properties:props + userProperties:userProps + userPropertiesSetOnce:userPropsOnce + groups:groupProps + timestamp:[NSDate date]]; + + + [[PostHogSDK shared] groupWithType:@"theType" key:@"theKey"]; + [[PostHogSDK shared] groupWithType:@"theType" key:@"theKey" groupProperties:groupProps]; + + [[PostHogSDK shared] alias:@"theAlias"]; + + [[PostHogSDK shared] screen:@"theScreen"]; + [[PostHogSDK shared] screenWithTitle:@"theScreen" properties:props]; + + [[PostHogSDK shared] flush]; + [[PostHogSDK shared] reset]; + [[PostHogSDK shared] close]; + + PostHogSDK *postHog = [PostHogSDK with:config]; + + [postHog capture:@"theCapture"]; return YES; } diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index 28823f1d9..88b19878b 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -229,7 +229,7 @@ class PostHogSDKTest: QuickSpec { let sut = self.getSut() sut.group(type: "some-type", key: "some-key", groupProperties: [ - "name": "some-company-name", + "name": "some-company-name" ]) let events = getBatchedEvents(server) @@ -514,7 +514,7 @@ class PostHogSDKTest: QuickSpec { let sut = self.getSut() sut.group(type: "some-type", key: "some-key", groupProperties: [ - "name": "some-company-name", + "name": "some-company-name" ]) let events = getBatchedEvents(server) @@ -812,6 +812,41 @@ class PostHogSDKTest: QuickSpec { sut.close() } + + it("captures an event with a custom timestamp") { + let sut = self.getSut() + let eventDate = Date().addingTimeInterval(-60 * 30) + + sut.capture("test event", + properties: ["foo": "bar"], + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"], + groups: ["groupProp": "value"], + timestamp: eventDate) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "test event" + + expect(event.properties["foo"] as? String) == "bar" + + let set = event.properties["$set"] as? [String: Any] ?? [:] + expect(set["userProp"] as? String) == "value" + + let setOnce = event.properties["$set_once"] as? [String: Any] ?? [:] + expect(setOnce["userPropOnce"] as? String) == "value" + + let groupProps = event.properties["$groups"] as? [String: String] ?? [:] + expect(groupProps["groupProp"]) == "value" + + expect(toISO8601String(event.timestamp)).to(equal(toISO8601String(eventDate))) + + sut.reset() + sut.close() + } } } From 18214963186cfe80c6814f80d3391c7211f97fa7 Mon Sep 17 00:00:00 2001 From: Ioannis J Date: Mon, 4 Nov 2024 09:17:08 +0200 Subject: [PATCH 2/3] chore: Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a0ea361..70a457f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- add option to pass a custom timestamp when calling capture() ([#228](https://github.com/PostHog/posthog-ios/pull/228)) + ## 3.13.3 - 2024-10-25 - fix race condition in PostHogFileBackedQueue.deleteFiles ([#218](https://github.com/PostHog/posthog-ios/pull/218)) From 8a79c8921fb29235e8cd5fca3217ff6c20b96525 Mon Sep 17 00:00:00 2001 From: Ioannis J Date: Mon, 4 Nov 2024 09:20:00 +0200 Subject: [PATCH 3/3] chore: lint --- PostHog/PostHogSDK.swift | 14 ++++++++++---- PostHogTests/PostHogSDKTest.swift | 8 ++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 173a1c970..dfba4f56c 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -458,7 +458,7 @@ let maxRetryDelay = 30.0 let properties = buildProperties(distinctId: distinctId, properties: [ "distinct_id": distinctId, - "$anon_distinct_id": oldDistinctId + "$anon_distinct_id": oldDistinctId, ], userProperties: sanitizeDicionary(userProperties), userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce)) let sanitizedProperties = sanitizeProperties(properties) @@ -528,7 +528,13 @@ let maxRetryDelay = 30.0 userPropertiesSetOnce: [String: Any]? = nil, groups: [String: String]? = nil) { - capture(event, distinctId: distinctId, properties: properties, userProperties: userProperties, userPropertiesSetOnce: userPropertiesSetOnce, groups: groups, timestamp: nil) + capture(event, + distinctId: distinctId, + properties: properties, + userProperties: userProperties, + userPropertiesSetOnce: userPropertiesSetOnce, + groups: groups, + timestamp: nil) } @objc(captureWithEvent:distinctId:properties:userProperties:userPropertiesSetOnce:groups:timestamp:) @@ -620,7 +626,7 @@ let maxRetryDelay = 30.0 } let props = [ - "$screen_name": screenTitle + "$screen_name": screenTitle, ].merging(sanitizeDicionary(properties) ?? [:]) { prop, _ in prop } let distinctId = getDistinctId() @@ -841,7 +847,7 @@ let maxRetryDelay = 30.0 if !flagCallReported.contains(flagKey) { let properties: [String: Any] = [ "$feature_flag": flagKey, - "$feature_flag_response": flagValue ?? "" + "$feature_flag_response": flagValue ?? "", ] flagCallReported.insert(flagKey) diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index 88b19878b..1eab8eb03 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -229,7 +229,7 @@ class PostHogSDKTest: QuickSpec { let sut = self.getSut() sut.group(type: "some-type", key: "some-key", groupProperties: [ - "name": "some-company-name" + "name": "some-company-name", ]) let events = getBatchedEvents(server) @@ -514,7 +514,7 @@ class PostHogSDKTest: QuickSpec { let sut = self.getSut() sut.group(type: "some-type", key: "some-key", groupProperties: [ - "name": "some-company-name" + "name": "some-company-name", ]) let events = getBatchedEvents(server) @@ -815,7 +815,7 @@ class PostHogSDKTest: QuickSpec { it("captures an event with a custom timestamp") { let sut = self.getSut() - let eventDate = Date().addingTimeInterval(-60 * 30) + let eventDate = Date().addingTimeInterval(-60 * 30) sut.capture("test event", properties: ["foo": "bar"], @@ -841,7 +841,7 @@ class PostHogSDKTest: QuickSpec { let groupProps = event.properties["$groups"] as? [String: String] ?? [:] expect(groupProps["groupProp"]) == "value" - + expect(toISO8601String(event.timestamp)).to(equal(toISO8601String(eventDate))) sut.reset()