Skip to content

Commit 330b471

Browse files
Initial support for Feature Flags (#670)
* initial feature flag support * tests and tweaks * add new sources to podspec * fix config init * tests and cleanup * pass to AnalyticsMessages and add to mixpaneldemo * fix test * rename APIs * more renaming * fix demo app delegate * fix tests * update podspec * revert demo app changes * one more config -> options rename * nope... more config->options renaming * modify enabled method names and load flags on identify * Update Sources/MixpanelPersistence.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix response parser function test * update README with DeepWiki badge * tweak README --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 6714969 commit 330b471

File tree

13 files changed

+1565
-6
lines changed

13 files changed

+1565
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ profile
3232
Carthage/Build/
3333

3434
MixpanelDemo/build/
35+
36+
# Claude.ai instructions
37+
CLAUDE.md

Mixpanel-swift.podspec

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ Pod::Spec.new do |s|
1919
base_source_files = ['Sources/Network.swift', 'Sources/FlushRequest.swift', 'Sources/PrintLogging.swift', 'Sources/FileLogging.swift',
2020
'Sources/MixpanelLogger.swift', 'Sources/JSONHandler.swift', 'Sources/Error.swift', 'Sources/AutomaticProperties.swift',
2121
'Sources/Constants.swift', 'Sources/MixpanelType.swift', 'Sources/Mixpanel.swift', 'Sources/MixpanelInstance.swift',
22-
'Sources/Flush.swift','Sources/Track.swift', 'Sources/People.swift', 'Sources/AutomaticEvents.swift',
23-
'Sources/Group.swift',
24-
'Sources/ReadWriteLock.swift', 'Sources/SessionMetadata.swift', 'Sources/MPDB.swift', 'Sources/MixpanelPersistence.swift', 'Sources/Data+Compression.swift']
22+
'Sources/Flush.swift', 'Sources/Track.swift', 'Sources/People.swift', 'Sources/AutomaticEvents.swift',
23+
'Sources/Group.swift', 'Sources/ReadWriteLock.swift', 'Sources/SessionMetadata.swift', 'Sources/MPDB.swift', 'Sources/MixpanelPersistence.swift',
24+
'Sources/Data+Compression.swift', 'Sources/MixpanelOptions.swift', 'Sources/FeatureFlags.swift']
2525
s.tvos.deployment_target = '11.0'
2626
s.tvos.frameworks = 'UIKit', 'Foundation'
2727
s.tvos.pod_target_xcconfig = {

Mixpanel.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
171E4C122DAF108400B7CB11 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C112DAF108400B7CB11 /* FeatureFlags.swift */; };
11+
171E4C132DAF108400B7CB11 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C112DAF108400B7CB11 /* FeatureFlags.swift */; };
12+
171E4C142DAF108400B7CB11 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C112DAF108400B7CB11 /* FeatureFlags.swift */; };
13+
171E4C152DAF108400B7CB11 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C112DAF108400B7CB11 /* FeatureFlags.swift */; };
14+
171E4C172DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C162DAF2B3100B7CB11 /* MixpanelOptions.swift */; };
15+
171E4C182DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C162DAF2B3100B7CB11 /* MixpanelOptions.swift */; };
16+
171E4C192DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C162DAF2B3100B7CB11 /* MixpanelOptions.swift */; };
17+
171E4C1A2DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C162DAF2B3100B7CB11 /* MixpanelOptions.swift */; };
1018
17C6547A2BB1F15C00C8A126 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1728208D2BA8BDE4002CD973 /* PrivacyInfo.xcprivacy */; };
1119
17C6547B2BB1F16000C8A126 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1728208D2BA8BDE4002CD973 /* PrivacyInfo.xcprivacy */; };
1220
17C6547C2BB1F16400C8A126 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1728208D2BA8BDE4002CD973 /* PrivacyInfo.xcprivacy */; };
@@ -103,6 +111,8 @@
103111
/* End PBXBuildFile section */
104112

105113
/* Begin PBXFileReference section */
114+
171E4C112DAF108400B7CB11 /* FeatureFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = "<group>"; };
115+
171E4C162DAF2B3100B7CB11 /* MixpanelOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixpanelOptions.swift; sourceTree = "<group>"; };
106116
1728208D2BA8BDE4002CD973 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Sources/Mixpanel/PrivacyInfo.xcprivacy; sourceTree = SOURCE_ROOT; };
107117
51DD56791D306B740045D3DB /* MixpanelLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelLogger.swift; sourceTree = "<group>"; };
108118
51DD56801D306B7B0045D3DB /* PrintLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrintLogging.swift; sourceTree = "<group>"; };
@@ -226,12 +236,14 @@
226236
E11594881CFF14D3007F8B4F /* Source */ = {
227237
isa = PBXGroup;
228238
children = (
239+
171E4C162DAF2B3100B7CB11 /* MixpanelOptions.swift */,
229240
17C654792BB1EF6700C8A126 /* Mixpanel */,
230241
E189D8FB1D5A6943007F3F29 /* Networking */,
231242
51DD56771D306B620045D3DB /* Log */,
232243
E189D8FA1D5A692A007F3F29 /* Utilities */,
233244
E115948A1CFF1538007F8B4F /* Mixpanel.swift */,
234245
E115948D1D000709007F8B4F /* MixpanelInstance.swift */,
246+
171E4C112DAF108400B7CB11 /* FeatureFlags.swift */,
235247
E115949E1D01BE14007F8B4F /* Flush.swift */,
236248
E11594A01D01C597007F8B4F /* Track.swift */,
237249
E15FF7C71D0435670076CDE3 /* People.swift */,
@@ -488,13 +500,15 @@
488500
86F86EC722443A3C00B69832 /* FileLogging.swift in Sources */,
489501
86F86EC622443A3100B69832 /* Error.swift in Sources */,
490502
86F86EC522443A2C00B69832 /* People.swift in Sources */,
503+
171E4C172DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */,
491504
86F86EC422443A2300B69832 /* ReadWriteLock.swift in Sources */,
492505
8625BEBE26D045CE0009BAA9 /* MPDB.swift in Sources */,
493506
95ECF06B2C9B851C006364D2 /* Data+Compression.swift in Sources */,
494507
86F86EC222443A1300B69832 /* Track.swift in Sources */,
495508
86F86EC122443A0E00B69832 /* JSONHandler.swift in Sources */,
496509
86F86EC022443A0800B69832 /* MixpanelType.swift in Sources */,
497510
86F86EBE224439FA00B69832 /* Network.swift in Sources */,
511+
171E4C142DAF108400B7CB11 /* FeatureFlags.swift in Sources */,
498512
86F86EBD224439F500B69832 /* Flush.swift in Sources */,
499513
86F86EBC224439F100B69832 /* PrintLogging.swift in Sources */,
500514
868550AF2699096F001FCDDC /* MixpanelPersistence.swift in Sources */,
@@ -517,13 +531,15 @@
517531
E1D335CE1D30578E00E68E12 /* Constants.swift in Sources */,
518532
E115949F1D01BE14007F8B4F /* Flush.swift in Sources */,
519533
E11594971D006022007F8B4F /* Network.swift in Sources */,
534+
171E4C182DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */,
520535
E15FF7C81D0435670076CDE3 /* People.swift in Sources */,
521536
673ABE3A21360CBE00B1784B /* Group.swift in Sources */,
522537
95ECF0682C9B851A006364D2 /* Data+Compression.swift in Sources */,
523538
E11594A11D01C597007F8B4F /* Track.swift in Sources */,
524539
E11594991D01689F007F8B4F /* JSONHandler.swift in Sources */,
525540
E1D335D01D3059A800E68E12 /* AutomaticProperties.swift in Sources */,
526541
51DD567C1D306B740045D3DB /* MixpanelLogger.swift in Sources */,
542+
171E4C122DAF108400B7CB11 /* FeatureFlags.swift in Sources */,
527543
E165228F1D6781DF000D5949 /* MixpanelType.swift in Sources */,
528544
BB9614171F3BB87700C3EF3E /* ReadWriteLock.swift in Sources */,
529545
E190522D1F9FC1BC00900E5D /* SessionMetadata.swift in Sources */,
@@ -546,13 +562,15 @@
546562
E12782BD1D4AB5CB0025FB05 /* MixpanelLogger.swift in Sources */,
547563
E12782BE1D4AB5CB0025FB05 /* Mixpanel.swift in Sources */,
548564
E12782BF1D4AB5CB0025FB05 /* MixpanelInstance.swift in Sources */,
565+
171E4C192DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */,
549566
E12782C11D4AB5CB0025FB05 /* Network.swift in Sources */,
550567
8625BEBC26D045CE0009BAA9 /* MPDB.swift in Sources */,
551568
95ECF0692C9B851B006364D2 /* Data+Compression.swift in Sources */,
552569
E12782C21D4AB5CB0025FB05 /* JSONHandler.swift in Sources */,
553570
E12782C31D4AB5CB0025FB05 /* Flush.swift in Sources */,
554571
E12782C41D4AB5CB0025FB05 /* FlushRequest.swift in Sources */,
555572
E12782C51D4AB5CB0025FB05 /* Track.swift in Sources */,
573+
171E4C132DAF108400B7CB11 /* FeatureFlags.swift in Sources */,
556574
E12782C61D4AB5CB0025FB05 /* People.swift in Sources */,
557575
E19052001F9548F000900E5D /* ReadWriteLock.swift in Sources */,
558576
868550AD2699096F001FCDDC /* MixpanelPersistence.swift in Sources */,
@@ -575,13 +593,15 @@
575593
E1F15FDC1E64B60A00391AE3 /* AutomaticProperties.swift in Sources */,
576594
E1F15FD91E64B60600391AE3 /* MixpanelLogger.swift in Sources */,
577595
E1F15FD61E64B5FC00391AE3 /* FlushRequest.swift in Sources */,
596+
171E4C1A2DAF2B3100B7CB11 /* MixpanelOptions.swift in Sources */,
578597
E1F15FD71E64B60200391AE3 /* PrintLogging.swift in Sources */,
579598
8625BEBD26D045CE0009BAA9 /* MPDB.swift in Sources */,
580599
95ECF06A2C9B851B006364D2 /* Data+Compression.swift in Sources */,
581600
E1F15FE21E64B60D00391AE3 /* Flush.swift in Sources */,
582601
E1F15FD51E64B5F800391AE3 /* Network.swift in Sources */,
583602
E1F15FDE1E64B60A00391AE3 /* MixpanelType.swift in Sources */,
584603
E1F15FDA1E64B60A00391AE3 /* JSONHandler.swift in Sources */,
604+
171E4C152DAF108400B7CB11 /* FeatureFlags.swift in Sources */,
585605
E1F15FE31E64B60D00391AE3 /* Track.swift in Sources */,
586606
E19052011F9548F000900E5D /* ReadWriteLock.swift in Sources */,
587607
868550AE2699096F001FCDDC /* MixpanelPersistence.swift in Sources */,

MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
171E4C1C2DB055BC00B7CB11 /* MixpanelFeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171E4C1B2DB055A900B7CB11 /* MixpanelFeatureFlagTests.swift */; };
1011
51DD568A1D3077390045D3DB /* LoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DD56891D3077390045D3DB /* LoggerTests.swift */; };
1112
60CB587123D77F9200F1632B /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CB587023D77F9200F1632B /* LoginViewController.swift */; };
1213
671EECAF21432E5F006DD9FA /* GroupsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671EECAE21432E5F006DD9FA /* GroupsViewController.swift */; };
@@ -249,6 +250,7 @@
249250
/* End PBXCopyFilesBuildPhase section */
250251

251252
/* Begin PBXFileReference section */
253+
171E4C1B2DB055A900B7CB11 /* MixpanelFeatureFlagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixpanelFeatureFlagTests.swift; sourceTree = "<group>"; };
252254
51DD56891D3077390045D3DB /* LoggerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggerTests.swift; sourceTree = "<group>"; };
253255
60CB587023D77F9200F1632B /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
254256
671EECAE21432E5F006DD9FA /* GroupsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupsViewController.swift; sourceTree = "<group>"; };
@@ -590,6 +592,7 @@
590592
E15FF7EA1D0461130076CDE3 /* MixpanelDemoTests */ = {
591593
isa = PBXGroup;
592594
children = (
595+
171E4C1B2DB055A900B7CB11 /* MixpanelFeatureFlagTests.swift */,
593596
E124061F1D249B2500383635 /* MixpanelBaseTests.swift */,
594597
E15FF7EB1D0461130076CDE3 /* MixpanelDemoTests.swift */,
595598
E1C61EB91D22F6470056C56C /* MixpanelPeopleTests.swift */,
@@ -1114,6 +1117,7 @@
11141117
E12406201D249B2500383635 /* MixpanelBaseTests.swift in Sources */,
11151118
E17AA05E1EC6234E0066EFE8 /* MixpanelAutomaticEventsTests.swift in Sources */,
11161119
E15FF7EC1D0461130076CDE3 /* MixpanelDemoTests.swift in Sources */,
1120+
171E4C1C2DB055BC00B7CB11 /* MixpanelFeatureFlagTests.swift in Sources */,
11171121
);
11181122
runOnlyForDeploymentPostprocessing = 0;
11191123
};

MixpanelDemo/MixpanelDemo/AppDelegate.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
1717
func application(_ application: UIApplication,
1818
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
1919
var ADD_YOUR_MIXPANEL_TOKEN_BELOW_🛠🛠🛠🛠🛠🛠: String
20-
Mixpanel.initialize(token: "MIXPANEL_TOKEN", trackAutomaticEvents: true)
20+
let mixpanelOptions = MixpanelOptions(token: "MIXPANEL_TOKEN", trackAutomaticEvents: true)
21+
Mixpanel.initialize(options: mixpanelOptions)
2122
Mixpanel.mainInstance().loggingEnabled = true
2223

2324
return true

MixpanelDemo/MixpanelDemoTests/MixpanelDemoTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,51 @@ class MixpanelDemoTests: MixpanelBaseTests {
179179
removeDBfile(testMixpanel.apiToken)
180180
}
181181

182+
// Mock implementation of MixpanelFlags to track loadFlags calls
183+
class MockMixpanelFlags: MixpanelFlags {
184+
var delegate: MixpanelFlagDelegate?
185+
var loadFlagsCallCount = 0
186+
187+
func loadFlags() {
188+
loadFlagsCallCount += 1
189+
}
190+
191+
func areFlagsReady() -> Bool {
192+
return true
193+
}
194+
195+
func getVariantSync(_ flagName: String, fallback: MixpanelFlagVariant) -> MixpanelFlagVariant {
196+
return fallback
197+
}
198+
199+
func getVariant(_ flagName: String, fallback: MixpanelFlagVariant, completion: @escaping (MixpanelFlagVariant) -> Void) {
200+
completion(fallback)
201+
}
202+
203+
func getVariantValueSync(_ flagName: String, fallbackValue: Any?) -> Any? {
204+
return fallbackValue
205+
}
206+
207+
func getVariantValue(_ flagName: String, fallbackValue: Any?, completion: @escaping (Any?) -> Void) {
208+
completion(fallbackValue)
209+
}
210+
211+
func isEnabledSync(_ flagName: String, fallbackValue: Bool) -> Bool {
212+
return fallbackValue
213+
}
214+
215+
func isEnabled(_ flagName: String, fallbackValue: Bool, completion: @escaping (Bool) -> Void) {
216+
completion(fallbackValue)
217+
}
218+
}
219+
182220
func testIdentify() {
183221
let testMixpanel = Mixpanel.initialize(token: randomId(), trackAutomaticEvents: true, flushInterval: 60)
222+
223+
// Inject our mock flags object
224+
let mockFlags = MockMixpanelFlags()
225+
testMixpanel.flags = mockFlags
226+
184227
for _ in 0..<2 {
185228
// run this twice to test reset works correctly wrt to distinct ids
186229
let distinctId: String = "d1"
@@ -221,8 +264,16 @@ class MixpanelDemoTests: MixpanelBaseTests {
221264
XCTAssertEqual(unidentifiedQueue.last?["$token"] as? String,
222265
testMixpanel.apiToken,
223266
"incorrect project token in people record")
267+
// Record the loadFlags call count before identify
268+
let loadFlagsCallCountBefore = mockFlags.loadFlagsCallCount
269+
224270
testMixpanel.identify(distinctId: distinctId)
225271
waitForTrackingQueue(testMixpanel)
272+
273+
// Assert that loadFlags was called when distinctId changed
274+
XCTAssertEqual(mockFlags.loadFlagsCallCount, loadFlagsCallCountBefore + 1,
275+
"loadFlags should be called when distinctId changes during identify")
276+
226277
let anonymousId = testMixpanel.anonymousId
227278
peopleQueue_value = peopleQueue(token: testMixpanel.apiToken)
228279
unidentifiedQueue = unIdentifiedPeopleQueue(token: testMixpanel.apiToken)
@@ -263,6 +314,14 @@ class MixpanelDemoTests: MixpanelBaseTests {
263314
let newDistinctId = (eventQueue(token: testMixpanel.apiToken).last?["properties"] as? InternalProperties)?["distinct_id"] as? String
264315
XCTAssertEqual(newDistinctId, distinctId,
265316
"events should use new distinct id after identify:")
317+
318+
// Test that calling identify with the same distinctId does NOT trigger loadFlags
319+
let loadFlagsCountBeforeSameId = mockFlags.loadFlagsCallCount
320+
testMixpanel.identify(distinctId: distinctId) // Same distinctId
321+
waitForTrackingQueue(testMixpanel)
322+
XCTAssertEqual(mockFlags.loadFlagsCallCount, loadFlagsCountBeforeSameId,
323+
"loadFlags should NOT be called when distinctId doesn't change")
324+
266325
testMixpanel.reset()
267326
waitForTrackingQueue(testMixpanel)
268327
}

0 commit comments

Comments
 (0)