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

Make timestamp include milliseconds, report upload errors, and include prior attempts count in uploaded events #239

Closed
Closed
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
23 changes: 14 additions & 9 deletions KeenClient/KIODBStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "KIODBStore.h"
#import "KIODBStorePrivate.h"
#import "keen_io_sqlite3.h"
#import "KeenConstants.h"

@interface KIODBStore ()

Expand Down Expand Up @@ -555,13 +556,17 @@ - (NSMutableDictionary *)getEventsWithMaxAttempts:(int)maxAttempts andProjectID:
// Fetch data out the statement
long long eventId = keen_io_sqlite3_column_int64(find_event_stmt, 0);

NSString *coll = [NSString stringWithUTF8String:(char *)keen_io_sqlite3_column_text(find_event_stmt, 1)];
NSString *collection =
[NSString stringWithUTF8String:(char *)keen_io_sqlite3_column_text(find_event_stmt, 1)];

const void *dataPtr = keen_io_sqlite3_column_blob(find_event_stmt, 2);
int dataSize = keen_io_sqlite3_column_bytes(find_event_stmt, 2);

NSData *data = [[NSData alloc] initWithBytes:dataPtr length:dataSize];

// Get number of upload attempts
NSNumber *attempts = [NSNumber numberWithUnsignedLong:keen_io_sqlite3_column_int(find_event_stmt, 3)];

// Bind and mark the event pending.
if (keen_io_sqlite3_bind_int64(make_pending_event_stmt, 1, eventId) != SQLITE_OK) {
[self handleSQLiteFailure:@"bind int for make pending"];
Expand All @@ -575,13 +580,14 @@ - (NSMutableDictionary *)getEventsWithMaxAttempts:(int)maxAttempts andProjectID:
// Reset the pendifier
[self resetSQLiteStatement:make_pending_event_stmt];

if ([events objectForKey:coll] == nil) {
if ([events objectForKey:collection] == nil) {
// We don't have an entry in the dictionary yet for this collection
// so create one.
[events setObject:[NSMutableDictionary dictionary] forKey:coll];
[events setObject:[NSMutableDictionary dictionary] forKey:collection];
}
NSMutableDictionary *collectionData = events[collection];

[[events objectForKey:coll] setObject:data forKey:[NSNumber numberWithUnsignedLongLong:eventId]];
collectionData[@(eventId)] = @{ @"data": data, kKeenEventKeenDataAttemptsKey: attempts };
}

[self resetSQLiteStatement:find_event_stmt];
Expand Down Expand Up @@ -1106,11 +1112,10 @@ - (BOOL)prepareAllSQLiteStatements {
return NO;

// This statement finds non-pending events in the table.
if (![self
prepareSQLStatement:&find_event_stmt
sqlQuery:
"SELECT id, collection, eventData FROM events WHERE pending=0 AND projectID=? AND attempts<?"
failureMessage:@"prepare find non-pending events statement"])
if (![self prepareSQLStatement:&find_event_stmt
sqlQuery:"SELECT id, collection, eventData, attempts FROM events WHERE pending=0 AND "
"projectID=? AND attempts<?"
failureMessage:@"prepare find non-pending events statement"])
return NO;

// This statement counts the total number of events (pending or not)
Expand Down
2 changes: 1 addition & 1 deletion KeenClient/KIOUploader.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
- (instancetype)initWithNetwork:(KIONetwork *)network andStore:(KIODBStore *)store;

// Upload events in the store for a given project
- (void)uploadEventsForConfig:(KeenClientConfig *)config completionHandler:(void (^)())completionHandler;
- (void)uploadEventsForConfig:(KeenClientConfig *)config completionHandler:(UploadCompletionBlock)completionHandler;

@end
204 changes: 152 additions & 52 deletions KeenClient/KIOUploader.m

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions KeenClient/KIOUtil.m
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ + (BOOL)handleError:(NSError **)error
const id objects[] = {errorMessage, underlyingError};
NSUInteger count = underlyingError ? 2 : 1;
NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:count];
*error = [NSError errorWithDomain:kKeenErrorDomain code:1 userInfo:userInfo];
*error = [NSError errorWithDomain:kKeenErrorDomain code:KeenErrorCodeGeneral userInfo:userInfo];
KCLogError(@"%@", *error);
}

Expand All @@ -96,7 +96,7 @@ + (id)convertDate:(id)date {
NSDateFormatter *dateFormatter = [NSDateFormatter new];
NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"];

NSString *iso8601String = [dateFormatter stringFromDate:date];
return iso8601String;
Expand Down
34 changes: 32 additions & 2 deletions KeenClient/KeenClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ typedef NSDictionary * (^KeenGlobalPropertiesBlock)(NSString *eventCollection);
// Block type for analysis/query completion
typedef void (^AnalysisCompletionBlock)(NSData *responseData, NSURLResponse *response, NSError *error);

// Block type for upload completion
typedef void (^UploadCompletionBlock)(NSError *error);

/**
KeenClient has class methods to return managed instances of itself and instance methods
to collect new events and upload them through the Keen IO API.
Expand All @@ -30,7 +33,11 @@ typedef void (^AnalysisCompletionBlock)(NSData *responseData, NSURLResponse *res
andReadKey:@"my_read_key"];
NSDictionary *myEvent = [NSDictionary dictionary];
[[KeenClient sharedClient] addEvent:myEvent toEventCollection:@"purchases"];
[[KeenClient sharedClient] uploadWithFinishedBlock:nil];
[[KeenClient sharedClient] uploadWithCompletionHandler:^(NSError *error) {
if (error) {
// Upload failed, check error for details
}
}];
*/
@interface KeenClient : NSObject <CLLocationManagerDelegate>

Expand Down Expand Up @@ -327,7 +334,30 @@ typedef void (^AnalysisCompletionBlock)(NSData *responseData, NSURLResponse *res
@param block The block to be executed once uploading is finished, regardless of whether or not the upload succeeded.
The block is also called when no upload was necessary because no events were captured.
*/
- (void)uploadWithFinishedBlock:(void (^)())block;
- (void)uploadWithFinishedBlock:(void (^)())block DEPRECATED_MSG_ATTRIBUTE("use uploadWithCompletionHandler:");

/**
Upload all the events captured so far by addEvent. This will asynchronously upload all events that have been cached.

If an upload fails, the events will be saved for a later attempt. It is possible that connectivity loss will cause
events to be successfully recorded, but the client will fail to read the server response. In this case, events will be
uploaded again as a duplicate event.

If a particular event is invalid, the event will be dropped from the queue and the failure message
will be logged.

@param completionHandler The block to be executed once uploading is finished, regardless of whether or not the upload
succeeded. The block is also called when no upload was necessary because no events were captured. If an error occured,
the error parameter passed to the block will be non-null with a keen-specific error domain and error code. In the case
where an error is returned to the SDK from a system API, the underlying NSError can be found in
error.userInfo[kKeenErrorInnerErrorKey]. If specific events fail to upload, error.code will be
KeenErrorCodeEventUploadError and the corresponding failures can be accessed as an NSArray of NSError through
error.userInfo[kKeenErrorInnerErrorArrayKey]. If a HTTP status other than 2XX is read, error.code will be
KeenErrorCodeResponseError and the status code will be available as error.userInfo[kKeenErrorHttpStatus].
KeenErrorCodeResponseError is also reported for other errors when reading a response. An error.code of
KeenErrorCodeNetworkDisconnected indicates that no network connection was available, and so nothing was uploaded.
*/
- (void)uploadWithCompletionHandler:(UploadCompletionBlock)completionHandler;

/**
Refresh the current geo location. The Keen Client only gets geo at the beginning of each session (i.e. when the client
Expand Down
12 changes: 8 additions & 4 deletions KeenClient/KeenClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ - (BOOL)validateEvent:(NSDictionary *)event withDepth:(NSUInteger)depth error:(N
errorMessage = @"You must specify a non-null, non-empty event.";
return [KIOUtil handleError:anError withErrorMessage:errorMessage];
}
id keenObject = [event objectForKey:@"keen"];
id keenObject = event[kKeenEventKeenDataKey];
if (keenObject && ![keenObject isKindOfClass:[NSDictionary class]]) {
errorMessage = @"An event's root-level property named 'keen' must be a dictionary.";
return [KIOUtil handleError:anError withErrorMessage:errorMessage];
Expand Down Expand Up @@ -530,15 +530,15 @@ - (BOOL)addEvent:(NSDictionary *)event
NSMutableDictionary *eventToWrite = [NSMutableDictionary dictionaryWithDictionary:event];

// either set "keen" only from keen properties or merge in
NSDictionary *originalKeenDict = [eventToWrite objectForKey:@"keen"];
NSDictionary *originalKeenDict = eventToWrite[kKeenEventKeenDataKey];
if (originalKeenDict) {
// have to merge
NSMutableDictionary *keenDict = [KIOUtil handleInvalidJSONInObject:keenProperties];
[keenDict addEntriesFromDictionary:originalKeenDict];
[eventToWrite setObject:keenDict forKey:@"keen"];
eventToWrite[kKeenEventKeenDataKey] = keenDict;
} else {
// just set it directly
[eventToWrite setObject:keenProperties forKey:@"keen"];
eventToWrite[kKeenEventKeenDataKey] = keenProperties;
}

NSError *serializationError;
Expand Down Expand Up @@ -567,6 +567,10 @@ - (void)uploadWithFinishedBlock:(void (^)())block {
[self.uploader uploadEventsForConfig:self.config completionHandler:block];
}

- (void)uploadWithCompletionHandler:(UploadCompletionBlock)completionHandler {
[self.uploader uploadEventsForConfig:self.config completionHandler:completionHandler];
}

#pragma mark - Querying

#pragma mark Async methods
Expand Down
72 changes: 56 additions & 16 deletions KeenClient/KeenConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,65 @@

#define kKeenSdkVersion @"3.7.0"

extern NSString * const kKeenApiUrlScheme;
extern NSString * const kKeenDefaultApiUrlAuthority;
extern NSString * const kKeenApiVersion;

extern NSString * const kKeenNameParam;
extern NSString * const kKeenDescriptionParam;
extern NSString * const kKeenSuccessParam;
extern NSString * const kKeenErrorParam;
extern NSString * const kKeenErrorCodeParam;
extern NSString * const kKeenInvalidCollectionNameError;
extern NSString * const kKeenInvalidPropertyNameError;
extern NSString * const kKeenInvalidPropertyValueError;
// Keen API constants

extern NSString *const kKeenApiUrlScheme;
extern NSString *const kKeenDefaultApiUrlAuthority;
extern NSString *const kKeenApiVersion;

extern NSString *const kKeenResponseErrorNameKey;
extern NSString *const kKeenResponseErrorDescriptionKey;
extern NSString *const kKeenSuccessParam;
extern NSString *const kKeenResponseErrorDictionaryKey;
extern NSString *const kKeenErrorCodeParam;
extern NSString *const kKeenInvalidCollectionNameError;
extern NSString *const kKeenInvalidPropertyNameError;
extern NSString *const kKeenInvalidPropertyValueError;

// The key on an event dictionary for keen-provided data
extern NSString *const kKeenEventKeenDataKey;

// A key containing the number of upload attempts for a given event
extern NSString *const kKeenEventKeenDataAttemptsKey;

// how many events can be stored for a single collection before aging them out
extern NSUInteger const kKeenMaxEventsPerCollection;

// how many events to drop when aging out
extern NSUInteger const kKeenNumberEventsToForget;

extern NSString * const kKeenErrorDomain;
// custom domain for NSErrors
extern NSString *const kKeenErrorDomain;

// Error codes for NSError
// clang-format off
typedef NS_ENUM(NSInteger, KeenErrorCode) {
KeenErrorCodeGeneral = 1,
KeenErrorCodeNetworkDisconnected = 2,
KeenErrorCodeSerialization = 3,
KeenErrorCodeResponseError = 4,
KeenErrorCodeEventUploadError = 6
};
// clang-format on

// Key in NSError userInfo used for storing an instance of an NSError returned
// from an underlying API call
extern NSString *const kKeenErrorInnerErrorKey;

// Key in NSError userInfo used for storing an NSArray of underlying NSErrors
extern NSString *const kKeenErrorInnerErrorArrayKey;

// Key in NSError userInfo used for error descriptions
extern NSString *const kKeenErrorDescriptionKey;

// Key in NSError userInfo used for http status codes
extern NSString *const kKeenErrorHttpStatus;

// Name of header that provides SDK version info
extern NSString *const kKeenSdkVersionHeader;

extern NSString * const kKeenSdkVersionHeader;
extern NSString * const kKeenSdkVersionWithPlatform;
// The SDK version info header content.
extern NSString *const kKeenSdkVersionWithPlatform;

extern NSString * const kKeenFileStoreImportedKey;
// User settings key indicating that a file store import has already been done.
extern NSString *const kKeenFileStoreImportedKey;
28 changes: 16 additions & 12 deletions KeenClient/KeenConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,35 @@
NSString *const kKeenDefaultApiUrlAuthority = @"api.keen.io";
NSString *const kKeenApiVersion = @"3.0";

// Keen API constants

NSString *const kKeenNameParam = @"name";
NSString *const kKeenDescriptionParam = @"description";
NSString *const kKeenResponseErrorNameKey = @"name";
NSString *const kKeenResponseErrorDescriptionKey = @"description";
NSString *const kKeenSuccessParam = @"success";
NSString *const kKeenErrorParam = @"error";
NSString *const kKeenResponseErrorDictionaryKey = @"error";
NSString *const kKeenErrorCodeParam = @"error_code";
NSString *const kKeenInvalidCollectionNameError = @"InvalidCollectionNameError";
NSString *const kKeenInvalidPropertyNameError = @"InvalidPropertyNameError";
NSString *const kKeenInvalidPropertyValueError = @"InvalidPropertyValueError";

// Keen constants related to how much data we'll cache on the device before aging it out
NSString *const kKeenEventKeenDataKey = @"keen";

NSString *const kKeenEventKeenDataAttemptsKey = @"keen_prior_upload_attempts";

// how many events can be stored for a single collection before aging them out
NSUInteger const kKeenMaxEventsPerCollection = 10000;
// how many events to drop when aging out

NSUInteger const kKeenNumberEventsToForget = 100;

// custom domain for NSErrors
NSString *const kKeenErrorDomain = @"io.keen";

// Name of header that provides SDK version info
NSString *const kKeenErrorInnerErrorKey = @"InnerError";

NSString *const kKeenErrorInnerErrorArrayKey = @"InnerErrorArray";

NSString *const kKeenErrorDescriptionKey = @"Description";

NSString *const kKeenErrorHttpStatus = @"HTTPStatus";

NSString *const kKeenSdkVersionHeader = @"Keen-Sdk";
// The SDK version info header content.

NSString *const kKeenSdkVersionWithPlatform = @"ios-" kKeenSdkVersion;

// User settings key indicating that a file store import has already been done.
NSString *const kKeenFileStoreImportedKey = @"didFSImport";
Loading