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

chore(ios): add custom event tracking #1676

Merged
merged 1 commit into from
Jan 8, 2025
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
7 changes: 7 additions & 0 deletions ios/DemoApp/Controller/ObjcDetailViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ - (void)viewDidLoad {

UIView *headerView = [self createTableHeaderView];
tableView.tableHeaderView = headerView;

NSDictionary *userAttributes = @{
@"user_name": @"Alice",
@"paid_user": @YES,
@"credit_balance": @1000,
@"latitude": @30.2661403415387};
[[Measure shared] trackEvent:@"event-name" attributes:userAttributes timestamp:nil];

[self setTitle:@"Objc View Controller"];

Expand Down
9 changes: 9 additions & 0 deletions ios/DemoApp/Controller/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ import MeasureSDK
view.addSubview(tableView)
}

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
let attributes: [String: AttributeValue] = ["user_name": .string("Alice"),
"paid_user": .boolean(true),
"credit_balance": .int(1000),
"latitude": .double(30.2661403415387)]
Measure.shared.trackEvent(name: "custom_event", attributes: attributes, timestamp: nil)
}

// MARK: - Table Header View with Buttons

func createTableHeaderView() -> UIView {
Expand Down
16 changes: 16 additions & 0 deletions ios/MeasureSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
5225D0332D088B7100FD240D /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D02C2D088B7100FD240D /* URLSessionTaskSwizzler.swift */; };
5225D0352D0AEB1A00FD240D /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D0342D0AEB1A00FD240D /* String+Extension.swift */; };
5225D0502D0FECFF00FD240D /* InputStream+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D04F2D0FECFF00FD240D /* InputStream+Extension.swift */; };
522532CA2D295F3F001B5D7C /* AttributeValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522532C92D295F3F001B5D7C /* AttributeValue.swift */; };
5229D16E2CCB533C00EFFE44 /* RecentSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5229D16D2CCB533C00EFFE44 /* RecentSession.swift */; };
523287692C85E07B000EE268 /* LifecycleObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287682C85E07B000EE268 /* LifecycleObserverTests.swift */; };
523287732C86195E000EE268 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287722C86195E000EE268 /* SessionManagerTests.swift */; };
Expand Down Expand Up @@ -115,6 +116,8 @@
52AE72032CABAE9000F2830A /* GestureEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52AE71FD2CABAE9000F2830A /* GestureEvents.swift */; };
52AE72082CABAEAB00F2830A /* UIWindow+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52AE72062CABAEAB00F2830A /* UIWindow+Extension.swift */; };
52AE72092CABAEAB00F2830A /* NSObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52AE72072CABAEAB00F2830A /* NSObject+Extension.swift */; };
52B2A8772D1A790200C6B5CF /* CustomEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B2A8762D1A790200C6B5CF /* CustomEventData.swift */; };
52B2A8792D1A89EF00C6B5CF /* CustomEventCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B2A8782D1A89EF00C6B5CF /* CustomEventCollector.swift */; };
52BCF1DC2CB42026003102DF /* MeasureModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF1DA2CB42026003102DF /* MeasureModel.xcdatamodeld */; };
52BCF1DD2CB42383003102DF /* MeasureModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF1DA2CB42026003102DF /* MeasureModel.xcdatamodeld */; };
52BCF20A2CB59FBF003102DF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF2092CB59FBF003102DF /* AppDelegate.swift */; };
Expand Down Expand Up @@ -177,6 +180,7 @@
52E303E42D1818EA008FE733 /* NetworkChangeDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E303E32D1818EA008FE733 /* NetworkChangeDetector.swift */; };
52E303E62D191A05008FE733 /* NetworkChangeCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E303E52D191A05008FE733 /* NetworkChangeCallback.swift */; };
52EB380C2C8C7334002D63EC /* SignPost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB380B2C8C7334002D63EC /* SignPost.swift */; };
52F0C6352D2D46980060FD08 /* CustomEventCollectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F0C6342D2D46980060FD08 /* CustomEventCollectorTests.swift */; };
52F3773B2CB41DDF006147E8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F3773A2CB41DDF006147E8 /* AppDelegate.swift */; };
52F3773D2CB41DE0006147E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F3773C2CB41DDF006147E8 /* SceneDelegate.swift */; };
52F377422CB41DE0006147E8 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 52F377412CB41DE0006147E8 /* Base */; };
Expand Down Expand Up @@ -361,6 +365,7 @@
5225D02C2D088B7100FD240D /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = "<group>"; };
5225D0342D0AEB1A00FD240D /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
5225D04F2D0FECFF00FD240D /* InputStream+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InputStream+Extension.swift"; sourceTree = "<group>"; };
522532C92D295F3F001B5D7C /* AttributeValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeValue.swift; sourceTree = "<group>"; };
5229D16D2CCB533C00EFFE44 /* RecentSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSession.swift; sourceTree = "<group>"; };
523287682C85E07B000EE268 /* LifecycleObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifecycleObserverTests.swift; sourceTree = "<group>"; };
523287722C86195E000EE268 /* SessionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -430,6 +435,8 @@
52AE71FD2CABAE9000F2830A /* GestureEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureEvents.swift; sourceTree = "<group>"; };
52AE72062CABAEAB00F2830A /* UIWindow+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIWindow+Extension.swift"; sourceTree = "<group>"; };
52AE72072CABAEAB00F2830A /* NSObject+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Extension.swift"; sourceTree = "<group>"; };
52B2A8762D1A790200C6B5CF /* CustomEventData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventData.swift; sourceTree = "<group>"; };
52B2A8782D1A89EF00C6B5CF /* CustomEventCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventCollector.swift; sourceTree = "<group>"; };
52BCF1DB2CB42026003102DF /* MeasureModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeasureModel.xcdatamodel; sourceTree = "<group>"; };
52BCF2072CB59FBF003102DF /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
52BCF2092CB59FBF003102DF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -486,6 +493,7 @@
52E303E32D1818EA008FE733 /* NetworkChangeDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkChangeDetector.swift; sourceTree = "<group>"; };
52E303E52D191A05008FE733 /* NetworkChangeCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkChangeCallback.swift; sourceTree = "<group>"; };
52EB380B2C8C7334002D63EC /* SignPost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignPost.swift; sourceTree = "<group>"; };
52F0C6342D2D46980060FD08 /* CustomEventCollectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventCollectorTests.swift; sourceTree = "<group>"; };
52F377382CB41DDF006147E8 /* DemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
52F3773A2CB41DDF006147E8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
52F3773C2CB41DDF006147E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -572,6 +580,7 @@
5202BE302C895FC800A3496E /* AppAttributeProcessor.swift */,
5202BE312C895FC800A3496E /* Attribute.swift */,
5202BE322C895FC800A3496E /* AttributeProcessor.swift */,
522532C92D295F3F001B5D7C /* AttributeValue.swift */,
5202BE332C895FC800A3496E /* ComputeOnceAttributeProcessor.swift */,
5202BE342C895FC800A3496E /* DeviceAttributeProcessor.swift */,
5202BE352C895FC800A3496E /* InstallationIdAttributeProcessor.swift */,
Expand Down Expand Up @@ -619,6 +628,8 @@
children = (
5202BE732C8B117900A3496E /* Attachment.swift */,
5202BE742C8B117900A3496E /* AttachmentType.swift */,
52B2A8782D1A89EF00C6B5CF /* CustomEventCollector.swift */,
52B2A8762D1A790200C6B5CF /* CustomEventData.swift */,
5202BE752C8B117900A3496E /* Event.swift */,
5202BE762C8B117900A3496E /* EventProcessor.swift */,
5202BE772C8B117900A3496E /* EventType.swift */,
Expand Down Expand Up @@ -897,6 +908,7 @@
isa = PBXGroup;
children = (
52A1A9612CA592A100461103 /* EventProcessorTests.swift */,
52F0C6342D2D46980060FD08 /* CustomEventCollectorTests.swift */,
);
path = Event;
sourceTree = "<group>";
Expand Down Expand Up @@ -1412,12 +1424,14 @@
52AE72022CABAE9000F2830A /* GestureDetector.swift in Sources */,
52CC63C12C9C608E00F7CA0A /* CrashDataPersistence.swift in Sources */,
52EB380C2C8C7334002D63EC /* SignPost.swift in Sources */,
52B2A8792D1A89EF00C6B5CF /* CustomEventCollector.swift in Sources */,
52A8533E2C994CC200B2A39F /* ExceptionDetail.swift in Sources */,
5225D02F2D088B7100FD240D /* HttpEventCollector.swift in Sources */,
526E30F62CE77BCB00F484B4 /* AppLaunchEvents.swift in Sources */,
52A853402C994D7900B2A39F /* Exception.swift in Sources */,
5222C9E82D14605000B198DA /* NetworkInterceptor.swift in Sources */,
52159E272CC802A800486F54 /* EventSerializer.swift in Sources */,
522532CA2D295F3F001B5D7C /* AttributeValue.swift in Sources */,
528EAB892C804AA100CB1574 /* SessionManager.swift in Sources */,
52CD91262C7C3D90000189BA /* MeasureInitializer.swift in Sources */,
52A1A94A2CA3CF9B00461103 /* EventStore.swift in Sources */,
Expand All @@ -1437,6 +1451,7 @@
524576712CC1128600B288E5 /* BatchCreator.swift in Sources */,
52F93B9E2CAE6B3C00168AB4 /* GestureTargetFinder.swift in Sources */,
52D51ABB2CD0E5C9008F30A6 /* MsrMoniterView.swift in Sources */,
52B2A8772D1A790200C6B5CF /* CustomEventData.swift in Sources */,
524576732CC116DD00B288E5 /* BatchStore.swift in Sources */,
52816B6A2CCE399B00B160A4 /* LifecycleCollector.swift in Sources */,
528EAB962C84553500CB1574 /* LifecycleObserver.swift in Sources */,
Expand Down Expand Up @@ -1505,6 +1520,7 @@
52A1A9662CA5AC9900461103 /* Attachment+Extension.swift in Sources */,
52CC63D72C9EC5FA00F7CA0A /* CrashReportingManagerTests.swift in Sources */,
52D3D7452CC6191B004E404B /* HeartbeatTests.swift in Sources */,
52F0C6352D2D46980060FD08 /* CustomEventCollectorTests.swift in Sources */,
52BCF1DD2CB42383003102DF /* MeasureModel.xcdatamodeld in Sources */,
52FA6A8F2CE222360091F089 /* MemoryUsageCalculatorTests.swift in Sources */,
52159E292CC8091E00486F54 /* EventSerializerTests.swift in Sources */,
Expand Down
9 changes: 6 additions & 3 deletions ios/MeasureSDK/AppLaunch/AppLaunchCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ final class BaseAppLaunchCollector: AppLaunchCollector {
type: .coldLaunch,
attributes: nil,
sessionId: nil,
attachments: nil)
attachments: nil,
userDefinedAttributes: nil)
}

func onWarmLaunchCallback(_ data: WarmLaunchData) {
Expand All @@ -59,7 +60,8 @@ final class BaseAppLaunchCollector: AppLaunchCollector {
type: .warmLaunch,
attributes: nil,
sessionId: nil,
attachments: nil)
attachments: nil,
userDefinedAttributes: nil)
}

func onHotLaunchCallback(_ data: HotLaunchData) {
Expand All @@ -68,6 +70,7 @@ final class BaseAppLaunchCollector: AppLaunchCollector {
type: .hotLaunch,
attributes: nil,
sessionId: nil,
attachments: nil)
attachments: nil,
userDefinedAttributes: nil)
}
}
68 changes: 68 additions & 0 deletions ios/MeasureSDK/Attribute/AttributeValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// AttributeValue.swift
// MeasureSDK
//
// Created by Adwin Ross on 04/01/25.
//

import Foundation

/// Represents a value of an attribute. It can be a string, boolean, integer, or double.
public enum AttributeValue {
case string(String)
case boolean(Bool)
case int(Int)
case long(Int64)
case float(Float)
case double(Double)

/// Returns the underlying value as `Any`.
var value: Any {
switch self {
case .string(let value): return value
case .boolean(let value): return value
case .int(let value): return value
case .long(let value): return value
case .float(let value): return value
case .double(let value): return value
}
}

func serialize() -> Any {
switch self {
case .string(let stringValue):
return "\"\(stringValue)\""
default:
return self.value
}
}
}

/// Serializer for `AttributeValue` to handle encoding and decoding.
enum AttributeValueSerializer {
static func serialize(_ value: AttributeValue) -> Any {
switch value {
case .string(let stringValue):
return "\"\(stringValue)\""
default:
return value.value
}
}

static func deserialize(from value: Any) -> AttributeValue? {
if let stringValue = value as? String {
return .string(stringValue)
} else if let boolValue = value as? Bool {
return .boolean(boolValue)
} else if let intValue = value as? Int {
return .int(intValue)
} else if let longValue = value as? Int64 {
return .long(longValue)
} else if let floatValue = value as? Float {
return .float(floatValue)
} else if let doubleValue = value as? Double {
return .double(doubleValue)
}
return nil
}
}
20 changes: 20 additions & 0 deletions ios/MeasureSDK/Config/BaseConfigProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ final class BaseConfigProvider: ConfigProvider {
self.cachedConfig = configLoader.getCachedConfig()
}

var maxUserDefinedAttributesPerEvent: Int {
return getMergedConfig(\.maxUserDefinedAttributesPerEvent)
}

var maxUserDefinedAttributeKeyLength: Int {
return getMergedConfig(\.maxUserDefinedAttributeKeyLength)
}

var maxUserDefinedAttributeValueLength: Int {
return getMergedConfig(\.maxUserDefinedAttributeValueLength)
}

var maxEventNameLength: Int {
return getMergedConfig(\.maxEventNameLength)
}

var customEventNameRegex: String {
return getMergedConfig(\.customEventNameRegex)
}

var maxSessionDurationMs: Number {
return getMergedConfig(\.maxSessionDurationMs)
}
Expand Down
10 changes: 10 additions & 0 deletions ios/MeasureSDK/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ struct Config: InternalConfig, MeasureConfig {
let memoryTrackingIntervalMs: UnsignedNumber
let httpContentTypeAllowlist: [String]
let defaultHttpHeadersBlocklist: [String]
let customEventNameRegex: String
let maxEventNameLength: Int
let maxUserDefinedAttributeKeyLength: Int
let maxUserDefinedAttributeValueLength: Int
let maxUserDefinedAttributesPerEvent: Int

internal init(enableLogging: Bool = DefaultConfig.enableLogging,
trackScreenshotOnCrash: Bool = DefaultConfig.trackScreenshotOnCrash,
Expand All @@ -54,5 +59,10 @@ struct Config: InternalConfig, MeasureConfig {
"Proxy-Authorization",
"WWW-Authenticate",
"X-Api-Key"]
self.customEventNameRegex = "^[a-zA-Z0-9_-]+$"
self.maxEventNameLength = 64 // 64 chars
self.maxUserDefinedAttributeKeyLength = 256 // 256 chars
self.maxUserDefinedAttributeValueLength = 256 // 256 chars
self.maxUserDefinedAttributesPerEvent = 100
}
}
15 changes: 15 additions & 0 deletions ios/MeasureSDK/Config/InternalConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,19 @@ protocol InternalConfig {

/// Default list of HTTP headers to not capture for network request and response.
var defaultHttpHeadersBlocklist: [String] { get }

/// The maximum length of a custom event. Defaults to 64 chars.
var maxEventNameLength: Int { get }

/// The regex to validate a custom event name.
var customEventNameRegex: String { get }

/// The maximum length of user defined attribute key. Defaults to 256 chars.
var maxUserDefinedAttributeKeyLength: Int { get }

/// The maximum length of a user defined attribute value. Defaults to 256 chars.
var maxUserDefinedAttributeValueLength: Int { get }

/// The maximum number of user defined attributes for an event. Defaults to 100.
var maxUserDefinedAttributesPerEvent: Int { get }
}
Loading
Loading