Skip to content

Commit

Permalink
Summary Event Synchronization (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
markpokornycos authored Dec 21, 2018
1 parent b590c58 commit b04a4b5
Show file tree
Hide file tree
Showing 26 changed files with 379 additions and 52 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to the LaunchDarkly iOS SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).

## [2.14.1] - 2018-12-21
### Added
- Added copy methods to several objects involved in creating a summary event
- Added additional synchronization to creating a summary event

## [2.14.0] - 2018-12-05
### Added
- Added `allFlags` property to `LDClient` that provides a dictionary of feature flag keys and values. Accessing feature flags via `allFlags` does not record any analytics events.
Expand Down
2 changes: 1 addition & 1 deletion Darkly/DataModels/DarklyConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#import "DarklyConstants.h"

NSString * const kClientVersion = @"2.14.0";
NSString * const kClientVersion = @"2.14.1";
NSString * const kLDPrimaryEnvironmentName = @"LaunchDarkly.EnvironmentName.Primary";
NSString * const kBaseUrl = @"https://app.launchdarkly.com";
NSString * const kEventsUrl = @"https://mobile.launchdarkly.com";
Expand Down
3 changes: 2 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDEventTrackingContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#import <Foundation/Foundation.h>

@interface LDEventTrackingContext : NSObject<NSCoding>
@interface LDEventTrackingContext : NSObject<NSCoding, NSCopying>
@property (nonatomic, assign) BOOL trackEvents;
@property (nullable, nonatomic, strong) NSDate *debugEventsUntilDate;

Expand All @@ -18,4 +18,5 @@
-(void)encodeWithCoder:(nonnull NSCoder*)aCoder;
-(nullable instancetype)initWithCoder:(nonnull NSCoder*)aDecoder;
-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;
@end
7 changes: 7 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDEventTrackingContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,11 @@ -(instancetype)initWithCoder:(NSCoder*)decoder {
-(NSString*)description {
return [NSString stringWithFormat:@"<LDEventTrackingContext: %p, trackEvents: %@, debugEventsUntilDate: %@>", self, self.trackEvents ? @"YES" : @"NO", self.debugEventsUntilDate ?: @"nil"];
}

-(id)copyWithZone:(NSZone*)zone {
LDEventTrackingContext *copiedContext = [[self class] new];
copiedContext.trackEvents = self.trackEvents;
copiedContext.debugEventsUntilDate = [self.debugEventsUntilDate copy]; //This may not return a different object, NSDate may be treated more like a value-type
return copiedContext;
}
@end
3 changes: 2 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagConfigTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@class LDFlagCounter;
@class LDFlagConfigValue;

@interface LDFlagConfigTracker : NSObject
@interface LDFlagConfigTracker : NSObject <NSCopying>
@property (nonatomic, assign, readonly) LDMillisecond startDateMillis;
@property (nonatomic, assign, readonly) BOOL hasTrackedEvents;

Expand All @@ -25,4 +25,5 @@
defaultValue:(nullable id)defaultValue;
-(nonnull NSDictionary<NSString*, NSDictionary*>*)flagRequestSummary;
-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;
@end
12 changes: 12 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagConfigTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "LDFlagConfigValue.h"
#import "NSDate+ReferencedDate.h"
#import "LDUtil.h"
#import "NSDictionary+LaunchDarkly.h"

@interface LDFlagConfigTracker()
@property (nonatomic, assign) LDMillisecond startDateMillis;
Expand Down Expand Up @@ -71,4 +72,15 @@ -(void)logRequestForFlagKey:(NSString*)flagKey reportedFlagValue:(id)reportedFla
-(NSString*)description {
return [NSString stringWithFormat:@"<LDFlagConfigTracker: %p, flagCounters: %@, startDateMillis: %ld>", self, [self.mutableFlagCounters description], (long)self.startDateMillis];
}

-(id)copyWithZone:(NSZone*)zone {
LDFlagConfigTracker *copy = [[self class] new];
copy.startDateMillis = self.startDateMillis;
copy.mutableFlagCounters = [NSMutableDictionary dictionary];
NSDictionary<NSString*, LDFlagCounter*> *flagCountersCopy = [self.mutableFlagCounters copy];
for (NSString *flagKey in flagCountersCopy.allKeys) {
copy.mutableFlagCounters[flagKey] = [flagCountersCopy[flagKey] copy];
}
return copy;
}
@end
4 changes: 3 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagConfigValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extern NSString * _Nonnull const kLDFlagConfigValueKeyVariation;

extern NSInteger const kLDFlagConfigValueItemDoesNotExist;

@interface LDFlagConfigValue: NSObject
@interface LDFlagConfigValue: NSObject <NSCopying>
//Core Items
@property (nullable, nonatomic, strong) id value;
@property (nonatomic, assign) NSInteger modelVersion;
Expand All @@ -43,4 +43,6 @@ extern NSInteger const kLDFlagConfigValueItemDoesNotExist;
-(BOOL)hasPropertiesMatchingDictionary:(nullable NSDictionary*)dictionary;

-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;

@end
11 changes: 11 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagConfigValue.m
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,15 @@ -(BOOL)hasPropertiesMatchingDictionary:(NSDictionary*)dictionary {
-(NSString*)description {
return [NSString stringWithFormat:@"<LDFlagConfigValue: %p, value: %@, modelVersion: %ld, variation: %ld, flagVersion: %@, eventTrackingContext: %@>", self, [self.value description], (long)self.modelVersion, (long)self.variation, self.flagVersion != nil ? [self.flagVersion description] : @"nil", self.eventTrackingContext ?: @"nil"];
}

-(id)copyWithZone:(NSZone*)zone {
LDFlagConfigValue *copy = [[self class] new];
copy.value = [self.value copy];
copy.modelVersion = self.modelVersion;
copy.variation = self.variation;
copy.flagVersion = [self.flagVersion copy];
copy.eventTrackingContext = [self.eventTrackingContext copy];

return copy;
}
@end
4 changes: 3 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagCounter.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@class LDFlagValueCounter;
@class LDFlagConfigValue;

@interface LDFlagCounter : NSObject
@interface LDFlagCounter : NSObject <NSCopying>
@property (nonatomic, strong, nonnull, readonly) NSString *flagKey;
@property (nonatomic, strong, nonnull) id defaultValue;
@property (nonatomic, assign, readonly) BOOL hasLoggedRequests;
Expand All @@ -24,4 +24,6 @@
-(nonnull NSDictionary*)dictionaryValue;

-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;

@end
13 changes: 13 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagCounter.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "LDFlagCounter.h"
#import "LDFlagValueCounter.h"
#import "LDFlagConfigValue.h"
#import "NSDictionary+LaunchDarkly.h"

NSString * const kLDFlagCounterKeyDefaultValue = @"default";
NSString * const kLDFlagCounterKeyCounters = @"counters";
Expand Down Expand Up @@ -98,4 +99,16 @@ -(NSString*)description {
[self.flagValueCounters description]];
}

-(id)copyWithZone:(nullable NSZone*)zone {
LDFlagCounter *copy = [[self class] new];
copy.flagKey = self.flagKey;
copy.defaultValue = [self.defaultValue copy];
NSMutableArray *copiedFlagValueCounters = [NSMutableArray arrayWithCapacity:self.flagValueCounters.count];
[self.flagValueCounters enumerateObjectsUsingBlock:^(LDFlagValueCounter * _Nonnull flagValueCounter, NSUInteger idx, BOOL * _Nonnull stop) {
[copiedFlagValueCounters addObject:[flagValueCounter copy]];
}];
copy.flagValueCounters = copiedFlagValueCounters;

return copy;
}
@end
4 changes: 3 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagValueCounter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

@class LDFlagConfigValue;

@interface LDFlagValueCounter : NSObject
@interface LDFlagValueCounter : NSObject <NSCopying>
@property (nonnull, nonatomic, strong) id reportedFlagValue;
@property (nullable, nonatomic, strong, readonly) LDFlagConfigValue *flagConfigValue;
@property (nonatomic, assign, readonly, getter=isKnown) BOOL known;
Expand All @@ -22,4 +22,6 @@
-(nonnull NSDictionary*)dictionaryValue;

-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;

@end
10 changes: 10 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagValueCounter.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,14 @@ -(NSString*)description {
self, [self.reportedFlagValue description], [self.flagConfigValue description], (long)self.count, self.known ? @"YES" : @"NO"];
}

-(id)copyWithZone:(NSZone*)zone {
LDFlagValueCounter *copy = [[self class] new];
copy.flagConfigValue = [self.flagConfigValue copy];
copy.reportedFlagValue = [self.reportedFlagValue copy];
copy.known = self.known;
copy.count = self.count;

return copy;
}

@end
2 changes: 1 addition & 1 deletion Darkly/Services/LDDataManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
user:(nonnull LDUserModel*)user;
-(void)recordCustomEventWithKey:(nonnull NSString*)eventKey customData:(nullable NSDictionary*)customData user:(nonnull LDUserModel*)user;
-(void)recordIdentifyEventWithUser:(nonnull LDUserModel*)user;
-(void)recordSummaryEventWithTracker:(nullable LDFlagConfigTracker*)tracker;
-(void)recordSummaryEventAndResetTrackerForUser:(nonnull LDUserModel*)user;
-(void)recordDebugEventWithFlagKey:(nonnull NSString*)flagKey
reportedFlagValue:(nonnull id)reportedFlagValue
flagConfigValue:(nullable LDFlagConfigValue*)flagConfigValue
Expand Down
27 changes: 16 additions & 11 deletions Darkly/Services/LDDataManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -376,18 +376,23 @@ -(void)recordIdentifyEventWithUser:(LDUserModel*)user {
[self addEventDictionary:[[LDEventModel identifyEventWithUser:user] dictionaryValueUsingConfig:self.config]];
}

-(void)recordSummaryEventWithTracker:(LDFlagConfigTracker*)tracker {
if (!tracker.hasTrackedEvents) {
DEBUG_LOGX(@"Tracker has no events to report. Discarding summary event.");
return;
}
LDEventModel *summaryEvent = [LDEventModel summaryEventWithTracker:tracker];
if (summaryEvent == nil) {
DEBUG_LOGX(@"Failed to create summary event. Aborting.");
return;
-(void)recordSummaryEventAndResetTrackerForUser:(LDUserModel*)user {
@synchronized (self) {
LDFlagConfigTracker *trackerCopy = [user.flagConfigTracker copy];
if (!trackerCopy.hasTrackedEvents) {
DEBUG_LOGX(@"Tracker has no events to report. Discarding summary event.");
return;
}
[user resetTracker];

LDEventModel *summaryEvent = [LDEventModel summaryEventWithTracker:trackerCopy];
if (summaryEvent == nil) {
DEBUG_LOGX(@"Failed to create summary event. Aborting.");
return;
}
DEBUG_LOGX(@"Creating summary event");
[self addEventDictionary:[summaryEvent dictionaryValueUsingConfig:self.config]];
}
DEBUG_LOGX(@"Creating summary event");
[self addEventDictionary:[summaryEvent dictionaryValueUsingConfig:self.config]];
}

-(void)recordDebugEventWithFlagKey:(NSString *)flagKey
Expand Down
2 changes: 1 addition & 1 deletion Darkly/Services/LDEnvironment.m
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ -(void)updateUser:(LDUserModel*)newUser {
return;
}

[self.dataManager recordSummaryEventWithTracker:self.user.flagConfigTracker];
[self.dataManager recordSummaryEventAndResetTrackerForUser:self.user];

DEBUG_LOG(@"LDEnvironment updating user key: %@, was online: %@", newUser.key, self.isOnline ? @"YES" : @"NO");
BOOL wasOnline = self.isOnline;
Expand Down
3 changes: 1 addition & 2 deletions Darkly/Services/LDEnvironmentController.m
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,11 @@ -(void)syncWithServerForEvents {

DEBUG_LOGX(@"EnvironmentController syncing events with server");

[self.dataManager recordSummaryEventWithTracker:self.user.flagConfigTracker];
[self.dataManager recordSummaryEventAndResetTrackerForUser:self.user];

__weak typeof(self) weakSelf = self;
[self.dataManager allEventDictionaries:^(NSArray *eventDictionaries) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.user resetTracker];
if (eventDictionaries.count == 0) {
DEBUG_LOGX(@"EnvironmentController has no events so won't sync events with server");
return;
Expand Down
22 changes: 9 additions & 13 deletions DarklyTests/LDEnvironmentControllerTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ - (void)testStopPolling_streaming {
self.environmentController.online = YES; //triggers startPolling
[[self.pollingManagerMock expect] stopEventPolling];
[[self.eventSourceMock expect] close];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand All @@ -250,7 +250,7 @@ - (void)testStopPolling_polling {
self.environmentController.online = YES; //triggers startPolling
[[self.pollingManagerMock expect] stopEventPolling];
[[self.pollingManagerMock expect] stopFlagConfigPolling];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand All @@ -270,7 +270,7 @@ - (void)testWillEnterBackground_streaming {
[[self.pollingManagerMock expect] suspendEventPolling];
[[self.eventSourceMock expect] close];
[[self.pollingManagerMock reject] suspendFlagConfigPolling];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand All @@ -292,7 +292,7 @@ - (void)testWillEnterBackground_polling {
[[self.pollingManagerMock expect] suspendEventPolling];
[[self.eventSourceMock reject] close];
[[self.pollingManagerMock expect] suspendFlagConfigPolling];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand Down Expand Up @@ -1357,7 +1357,7 @@ - (void)testEventTimerFiredNotification {
completion(self.events);
return YES;
}]];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
self.environmentController.online = YES;
[[self.requestManagerMock expect] performEventRequest:self.events isOnline:self.environmentController.isOnline];

Expand All @@ -1375,21 +1375,17 @@ - (void)testSyncWithServerForEvents_eventsExist {
}]];
self.environmentController.online = YES;
[[self.requestManagerMock expect] performEventRequest:self.events isOnline:YES];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
LDMillisecond startDateMillis = [[NSDate date] millisSince1970];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];

[self.environmentController syncWithServerForEvents];

[self.requestManagerMock verify];
[self.dataManagerMock verify];
XCTAssertNotNil(self.user.flagConfigTracker);
XCTAssertFalse(self.user.flagConfigTracker.hasTrackedEvents);
XCTAssertTrue(Approximately(self.user.flagConfigTracker.startDateMillis, startDateMillis, 10));
}

- (void)testSyncWithServerForEvents_eventsEmpty {
self.environmentController.online = YES;
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock stub] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
void (^completion)(NSArray *) = obj;
completion(@[]);
Expand All @@ -1405,7 +1401,7 @@ - (void)testSyncWithServerForEvents_eventsEmpty {

- (void)testSyncWithServerForEvents_eventsNil {
self.environmentController.online = YES;
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock stub] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
void (^completion)(NSArray *) = obj;
completion(nil);
Expand Down Expand Up @@ -1490,7 +1486,7 @@ - (void)testFlushEventsWhenOnline {
completion(self.events);
return YES;
}]];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];

[self.environmentController flushEvents];

Expand Down
10 changes: 5 additions & 5 deletions DarklyTests/LDEnvironmentTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ -(void)testUpdateUser {
LDUserModel *newUser = [LDUserModel stubWithKey:[[NSUUID UUID] UUIDString]];
id newUserMock = [OCMockObject niceMockForClass:[LDUserModel class]];
[[[newUserMock expect] andReturn:newUser] copy];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.environment.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.environment.user];
[[self.environmentControllerMock expect] setOnline:NO];
[[self.dataManagerMock expect] convertToEnvironmentBasedCacheForUser:newUser config:self.config];
[[self.dataManagerMock expect] retrieveFlagConfigForUser:newUser];
Expand Down Expand Up @@ -719,7 +719,7 @@ -(void)testUpdateUser_secondaryEnvironment {

[self.environment start];
self.environment.online = YES;
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.environment.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.environment.user];
[[originalEnvironmentControllerMock expect] setOnline:NO];
[[self.dataManagerMock reject] convertToEnvironmentBasedCacheForUser:[OCMArg any] config:[OCMArg any]];
[[self.dataManagerMock expect] retrieveFlagConfigForUser:newUser];
Expand All @@ -741,7 +741,7 @@ -(void)testUpdateUser_offline {
LDUserModel *newUser = [LDUserModel stubWithKey:[[NSUUID UUID] UUIDString]];
id newUserMock = [OCMockObject niceMockForClass:[LDUserModel class]];
[[[newUserMock expect] andReturn:newUser] copy];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.environment.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.environment.user];
[[self.environmentControllerMock reject] setOnline:NO];
[[self.dataManagerMock expect] convertToEnvironmentBasedCacheForUser:newUser config:self.config];
[[self.dataManagerMock expect] retrieveFlagConfigForUser:newUser];
Expand All @@ -763,7 +763,7 @@ -(void)testUpdateUser_missingNewUser {
LDUserModel *originalEnvironmentUser = self.environment.user;
[self.environment start];
self.environment.online = YES;
[[self.dataManagerMock reject] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock reject] recordSummaryEventAndResetTrackerForUser:[OCMArg any]];
[[self.environmentControllerMock reject] setOnline:[OCMArg any]];
[[self.dataManagerMock reject] convertToEnvironmentBasedCacheForUser:[OCMArg any] config:[OCMArg any]];
[[self.dataManagerMock reject] retrieveFlagConfigForUser:[OCMArg any]];
Expand All @@ -781,7 +781,7 @@ -(void)testUpdateUser_missingNewUser {
-(void)testUpdateUser_notStarted {
LDUserModel *originalEnvironmentUser = self.environment.user;
LDUserModel *newUser = [LDUserModel stubWithKey:[[NSUUID UUID] UUIDString]];
[[self.dataManagerMock reject] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock reject] recordSummaryEventAndResetTrackerForUser:[OCMArg any]];
[[self.environmentControllerMock reject] setOnline:[OCMArg any]];
[[self.dataManagerMock reject] convertToEnvironmentBasedCacheForUser:[OCMArg any] config:[OCMArg any]];
[[self.dataManagerMock reject] retrieveFlagConfigForUser:[OCMArg any]];
Expand Down
Loading

0 comments on commit b04a4b5

Please sign in to comment.