Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bsneed/timestamps #876

Merged
merged 9 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,6 @@
ReferencedContainer = "container:Analytics.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9D8CE58B23EE014E00197D0C"
BuildableName = "AnalyticsTestsTVOS.xctest"
BlueprintName = "AnalyticsTestsTVOS"
ReferencedContainer = "container:Analytics.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
3 changes: 3 additions & 0 deletions Analytics/Classes/Integrations/SEGIdentifyPayload.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#import "SEGIdentifyPayload.h"

@interface SEGIdentifyPayload ()
@property (nonatomic, readwrite, nullable) NSString *anonymousId;
@end

@implementation SEGIdentifyPayload

Expand Down
121 changes: 65 additions & 56 deletions Analytics/Classes/Integrations/SEGIntegrationsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,28 @@
static NSString *const SEGCachedSettingsKey = @"analytics.settings.v2.plist";


@interface SEGAnalyticsConfiguration (Private)
@interface SEGIdentifyPayload (AnonymousId)
@property (nonatomic, readwrite, nullable) NSString *anonymousId;
@end


@interface SEGPayload (Options)
@property (readonly) NSDictionary *options;
@end
@implementation SEGPayload (Options)
// Combine context and integrations to form options
- (NSDictionary *)options
{
return @{
@"context" : self.context ?: @{},
@"integrations" : self.integrations ?: @{}
};
}
@end

@property (nonatomic, strong) NSArray *factories;

@interface SEGAnalyticsConfiguration (Private)
@property (nonatomic, strong) NSArray *factories;
@end


Expand Down Expand Up @@ -153,10 +171,37 @@ - (NSString *)description

#pragma mark - Analytics API

- (void)identify:(SEGIdentifyPayload *)payload
{
NSCAssert2(payload.userId.length > 0 || payload.traits.count > 0, @"either userId (%@) or traits (%@) must be provided.", payload.userId, payload.traits);

NSString *anonymousId = payload.anonymousId;
if (anonymousId) {
[self saveAnonymousId:anonymousId];
} else {
anonymousId = self.cachedAnonymousId;
}
payload.anonymousId = anonymousId;

[self callIntegrationsWithSelector:NSSelectorFromString(@"identify:")
arguments:@[ payload ]
options:payload.options
sync:false];
}
/*
- (void)identify:(NSString *)userId traits:(NSDictionary *)traits options:(NSDictionary *)options
{
NSCAssert2(userId.length > 0 || traits.count > 0, @"either userId (%@) or traits (%@) must be provided.", userId, traits);

NSDictionary *options;
if (p.anonymousId) {
NSMutableDictionary *mutableOptions = [[NSMutableDictionary alloc] initWithDictionary:p.options];
mutableOptions[@"anonymousId"] = p.anonymousId;
options = [mutableOptions copy];
} else {
options = p.options;
}

NSString *anonymousId = [options objectForKey:@"anonymousId"];
if (anonymousId) {
[self saveAnonymousId:anonymousId];
Expand All @@ -174,68 +219,49 @@ - (void)identify:(NSString *)userId traits:(NSDictionary *)traits options:(NSDic
arguments:@[ payload ]
options:options
sync:false];
}
}*/

#pragma mark - Track

- (void)track:(NSString *)event properties:(NSDictionary *)properties options:(NSDictionary *)options
- (void)track:(SEGTrackPayload *)payload
{
NSCAssert1(event.length > 0, @"event (%@) must not be empty.", event);

SEGTrackPayload *payload = [[SEGTrackPayload alloc] initWithEvent:event
properties:SEGCoerceDictionary(properties)
context:SEGCoerceDictionary([options objectForKey:@"context"])
integrations:[options objectForKey:@"integrations"]];
NSCAssert1(payload.event.length > 0, @"event (%@) must not be empty.", payload.event);

[self callIntegrationsWithSelector:NSSelectorFromString(@"track:")
arguments:@[ payload ]
options:options
options:payload.options
sync:false];
}

#pragma mark - Screen

- (void)screen:(NSString *)screenTitle properties:(NSDictionary *)properties options:(NSDictionary *)options
- (void)screen:(SEGScreenPayload *)payload
{
NSCAssert1(screenTitle.length > 0, @"screen name (%@) must not be empty.", screenTitle);

SEGScreenPayload *payload = [[SEGScreenPayload alloc] initWithName:screenTitle
properties:SEGCoerceDictionary(properties)
context:SEGCoerceDictionary([options objectForKey:@"context"])
integrations:[options objectForKey:@"integrations"]];
NSCAssert1(payload.name.length > 0, @"screen name (%@) must not be empty.", payload.name);

[self callIntegrationsWithSelector:NSSelectorFromString(@"screen:")
arguments:@[ payload ]
options:options
options:payload.options
sync:false];
}

#pragma mark - Group

- (void)group:(NSString *)groupId traits:(NSDictionary *)traits options:(NSDictionary *)options
- (void)group:(SEGGroupPayload *)payload
{
SEGGroupPayload *payload = [[SEGGroupPayload alloc] initWithGroupId:groupId
traits:SEGCoerceDictionary(traits)
context:SEGCoerceDictionary([options objectForKey:@"context"])
integrations:[options objectForKey:@"integrations"]];

[self callIntegrationsWithSelector:NSSelectorFromString(@"group:")
arguments:@[ payload ]
options:options
options:payload.options
sync:false];
}

#pragma mark - Alias

- (void)alias:(NSString *)newId options:(NSDictionary *)options
- (void)alias:(SEGAliasPayload *)payload
{
SEGAliasPayload *payload = [[SEGAliasPayload alloc] initWithNewId:newId
context:SEGCoerceDictionary([options objectForKey:@"context"])
integrations:[options objectForKey:@"integrations"]];

[self callIntegrationsWithSelector:NSSelectorFromString(@"alias:")
arguments:@[ payload ]
options:options
options:payload.options
sync:false];
}

Expand Down Expand Up @@ -550,32 +576,15 @@ - (void)callIntegrationsWithSelector:(SEL)selector arguments:(NSArray *)argument
@end


@interface SEGPayload (Options)
@property (readonly) NSDictionary *options;
@end


@implementation SEGPayload (Options)

// Combine context and integrations to form options
- (NSDictionary *)options
{
return @{
@"context" : self.context ?: @{},
@"integrations" : self.integrations ?: @{}
};
}

@end


@implementation SEGIntegrationsManager (SEGMiddleware)

- (void)context:(SEGContext *)context next:(void (^_Nonnull)(SEGContext *_Nullable))next
{
switch (context.eventType) {
case SEGEventTypeIdentify: {
SEGIdentifyPayload *p = (SEGIdentifyPayload *)context.payload;
[self identify:p];
/*
NSDictionary *options;
if (p.anonymousId) {
NSMutableDictionary *mutableOptions = [[NSMutableDictionary alloc] initWithDictionary:p.options];
Expand All @@ -584,27 +593,27 @@ - (void)context:(SEGContext *)context next:(void (^_Nonnull)(SEGContext *_Nullab
} else {
options = p.options;
}
[self identify:p.userId traits:p.traits options:options];
[self identify:p.userId traits:p.traits options:options];*/
break;
}
case SEGEventTypeTrack: {
SEGTrackPayload *p = (SEGTrackPayload *)context.payload;
[self track:p.event properties:p.properties options:p.options];
[self track:p];
break;
}
case SEGEventTypeScreen: {
SEGScreenPayload *p = (SEGScreenPayload *)context.payload;
[self screen:p.name properties:p.properties options:p.options];
[self screen:p];
break;
}
case SEGEventTypeGroup: {
SEGGroupPayload *p = (SEGGroupPayload *)context.payload;
[self group:p.groupId traits:p.traits options:p.options];
[self group:p];
break;
}
case SEGEventTypeAlias: {
SEGAliasPayload *p = (SEGAliasPayload *)context.payload;
[self alias:p.theNewId options:p.options];
[self alias:p];
break;
}
case SEGEventTypeReset:
Expand Down
1 change: 1 addition & 0 deletions Analytics/Classes/Integrations/SEGPayload.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ NS_ASSUME_NONNULL_BEGIN

@property (nonatomic, readonly) JSON_DICT context;
@property (nonatomic, readonly) JSON_DICT integrations;
@property (nonatomic, strong) NSString *timestamp;

- (instancetype)initWithContext:(JSON_DICT)context integrations:(JSON_DICT)integrations;

Expand Down
1 change: 1 addition & 0 deletions Analytics/Classes/Internal/SEGAnalyticsUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ BOOL serializableDictionaryTypes(NSDictionary *dict);

// Date Utils
NSString *iso8601FormattedString(NSDate *date);
NSString *iso8601NanoFormattedString(NSDate *date);

void trimQueue(NSMutableArray *array, NSUInteger size);

Expand Down
51 changes: 51 additions & 0 deletions Analytics/Classes/Internal/SEGAnalyticsUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@

static BOOL kAnalyticsLoggerShowLogs = NO;


@interface SEGISO8601NanosecondDateFormatter: NSDateFormatter
@end

@implementation SEGISO8601NanosecondDateFormatter

- (id)init
{
self = [super init];
self.dateFormat = @"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS:'Z'";
self.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
self.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
return self;
}

const __SEG_NANO_MAX_LENGTH = 9;
- (NSString * _Nonnull)stringFromDate:(NSDate *)date
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateComponents = [calendar components:NSCalendarUnitSecond | NSCalendarUnitNanosecond fromDate:date];
NSString *genericDateString = [super stringFromDate:date];

NSMutableArray *stringComponents = [[genericDateString componentsSeparatedByString:@"."] mutableCopy];
NSString *nanoSeconds = [NSString stringWithFormat:@"%li", (long)dateComponents.nanosecond];

if (nanoSeconds.length > __SEG_NANO_MAX_LENGTH) {
nanoSeconds = [nanoSeconds substringToIndex:__SEG_NANO_MAX_LENGTH];
} else {
nanoSeconds = [nanoSeconds stringByPaddingToLength:__SEG_NANO_MAX_LENGTH withString:@"0" startingAtIndex:0];
}

NSString *result = [NSString stringWithFormat:@"%@.%@Z", stringComponents[0], nanoSeconds];

return result;
}

@end


NSString *GenerateUUIDString()
{
CFUUIDRef theUUID = CFUUIDCreate(NULL);
Expand All @@ -11,7 +50,18 @@
return UUIDString;
}


// Date Utils
NSString *iso8601NanoFormattedString(NSDate *date)
{
static NSDateFormatter *dateFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [[SEGISO8601NanosecondDateFormatter alloc] init];
});
return [dateFormatter stringFromDate:date];
}

NSString *iso8601FormattedString(NSDate *date)
{
static NSDateFormatter *dateFormatter;
Expand All @@ -25,6 +75,7 @@
return [dateFormatter stringFromDate:date];
}


/** trim the queue so that it contains only upto `max` number of elements. */
void trimQueue(NSMutableArray *queue, NSUInteger max)
{
Expand Down
8 changes: 5 additions & 3 deletions Analytics/Classes/Internal/SEGSegmentIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ - (void)identify:(SEGIdentifyPayload *)payload

NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:payload.traits forKey:@"traits"];

[dictionary setValue:payload.timestamp forKey:@"timestamp"];
[self enqueueAction:@"identify" dictionary:dictionary context:payload.context integrations:payload.integrations];
}

Expand All @@ -377,6 +377,7 @@ - (void)track:(SEGTrackPayload *)payload
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:payload.event forKey:@"event"];
[dictionary setValue:payload.properties forKey:@"properties"];
[dictionary setValue:payload.timestamp forKey:@"timestamp"];
[self enqueueAction:@"track" dictionary:dictionary context:payload.context integrations:payload.integrations];
}

Expand All @@ -385,6 +386,7 @@ - (void)screen:(SEGScreenPayload *)payload
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:payload.name forKey:@"name"];
[dictionary setValue:payload.properties forKey:@"properties"];
[dictionary setValue:payload.timestamp forKey:@"timestamp"];

[self enqueueAction:@"screen" dictionary:dictionary context:payload.context integrations:payload.integrations];
}
Expand All @@ -394,6 +396,7 @@ - (void)group:(SEGGroupPayload *)payload
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:payload.groupId forKey:@"groupId"];
[dictionary setValue:payload.traits forKey:@"traits"];
[dictionary setValue:payload.timestamp forKey:@"timestamp"];

[self enqueueAction:@"group" dictionary:dictionary context:payload.context integrations:payload.integrations];
}
Expand All @@ -403,6 +406,7 @@ - (void)alias:(SEGAliasPayload *)payload
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setValue:payload.theNewId forKey:@"userId"];
[dictionary setValue:self.userId ?: [self.analytics getAnonymousId] forKey:@"previousId"];
[dictionary setValue:payload.timestamp forKey:@"timestamp"];

[self enqueueAction:@"alias" dictionary:dictionary context:payload.context integrations:payload.integrations];
}
Expand Down Expand Up @@ -457,9 +461,7 @@ - (NSDictionary *)integrationsDictionary:(NSDictionary *)integrations
- (void)enqueueAction:(NSString *)action dictionary:(NSMutableDictionary *)payload context:(NSDictionary *)context integrations:(NSDictionary *)integrations
{
// attach these parts of the payload outside since they are all synchronous
// and the timestamp will be more accurate.
payload[@"type"] = action;
payload[@"timestamp"] = iso8601FormattedString([NSDate date]);
payload[@"messageId"] = GenerateUUIDString();

[self dispatchBackground:^{
Expand Down
5 changes: 5 additions & 0 deletions Analytics/Classes/Middlewares/SEGContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ - (SEGContext *_Nonnull)modify:(void (^_Nonnull)(id<SEGMutableContext> _Nonnull
// of immutable data structure without the cost of having to allocate and reallocate
// objects over and over again.
SEGContext *context = self.debug ? [self copy] : self;
NSString *originalTimestamp = context.payload.timestamp;
modify(context);
if (originalTimestamp) {
context.payload.timestamp = originalTimestamp;
}

// TODO: We could probably add some validation here that the newly modified context
// is actualy valid. For example, `eventType` should match `paylaod` class.
// or anonymousId should never be null.
Expand Down
Loading