diff --git a/README.md b/README.md index cd209a3..ee3d0bf 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,9 @@ config.defaultUser = "myuser@example.com" You can update the configuration after TelemetryDeck is already initialized. -## Payload +## Parameters -You can also send additional payload data with each signal: +You can also send additional parameters with each signal: ```swift TelemetryDeck.signal("Database.updated", parameters: ["numberOfDatabaseEntries": "3831"]) diff --git a/Sources/TelemetryClient/Presets/TelemetryDeck+Errors.swift b/Sources/TelemetryClient/Presets/TelemetryDeck+Errors.swift index 92e83f0..7cc5329 100644 --- a/Sources/TelemetryClient/Presets/TelemetryDeck+Errors.swift +++ b/Sources/TelemetryClient/Presets/TelemetryDeck+Errors.swift @@ -1,6 +1,6 @@ import Foundation -extension TelemetryDeck { +public extension TelemetryDeck { /// Sends a telemetry signal indicating that an error has occurred. /// /// - Parameters: @@ -10,7 +10,7 @@ extension TelemetryDeck { /// - parameters: Additional parameters to include with the signal. Default is an empty dictionary. /// - floatValue: An optional floating-point value to include with the signal. Default is `nil`. /// - customUserID: An optional custom user identifier. If provided, it overrides the default user identifier from the configuration. Default is `nil`. - public static func errorOccurred( + static func errorOccurred( id: String, category: ErrorCategory? = nil, message: String? = nil, @@ -44,7 +44,7 @@ extension TelemetryDeck { /// - parameters: Additional parameters to include with the signal. Default is an empty dictionary. /// - floatValue: An optional floating-point value to include with the signal. Default is `nil`. /// - customUserID: An optional custom user identifier. If provided, it overrides the default user identifier from the configuration. Default is `nil`. - public static func errorOccurred( + static func errorOccurred( identifiableError: IdentifiableError, category: ErrorCategory = .thrownException, parameters: [String: String] = [:], @@ -71,9 +71,10 @@ extension TelemetryDeck { /// - floatValue: An optional floating-point value to include with the signal. Default is `nil`. /// - customUserID: An optional custom user identifier. If provided, it overrides the default user identifier from the configuration. Default is `nil`. /// - /// - Note: Use this overload if you want to provide a custom `message` parameter. Prefer ``errorOccurred(identifiableError:category:parameters:floatValue:customUserID:)`` to send `error.localizedDescription` as the `message` automatically. + /// - Note: Use this overload if you want to provide a custom `message` parameter. Prefer ``errorOccurred(identifiableError:category:parameters:floatValue:customUserID:)`` to send + /// `error.localizedDescription` as the `message` automatically. @_disfavoredOverload - public static func errorOccurred( + static func errorOccurred( identifiableError: IdentifiableError, category: ErrorCategory = .thrownException, message: String? = nil, diff --git a/Sources/TelemetryClient/Signal.swift b/Sources/TelemetryClient/Signal.swift index 1a14ccc..ead9f27 100644 --- a/Sources/TelemetryClient/Signal.swift +++ b/Sources/TelemetryClient/Signal.swift @@ -11,6 +11,7 @@ import Foundation import TVUIKit #endif +/// Note: only use this when posting to the deprecated V1 ingest API internal struct SignalPostBody: Codable, Equatable { /// When was this signal generated let receivedAt: Date diff --git a/Sources/TelemetryClient/TelemetryClient.swift b/Sources/TelemetryClient/TelemetryClient.swift index f7e7dcd..1851189 100644 --- a/Sources/TelemetryClient/TelemetryClient.swift +++ b/Sources/TelemetryClient/TelemetryClient.swift @@ -205,7 +205,7 @@ public class TelemetryManager { /// Send a Signal to TelemetryDeck, to record that an event has occurred. /// - /// If you specify a payload, it will be sent in addition to the default payload which includes OS Version, App Version, and more. + /// If you specify parameters, they will be sent in addition to the default parameters which include OS Version, App Version, and more. @available(*, deprecated, renamed: "TelemetryDeck.signal(_:parameters:)", message: "This call was renamed to `TelemetryDeck.signal(_:parameters:)`. Please migrate – a fix-it is available.") public static func send(_ signalName: String, with parameters: [String: String] = [:]) { send(signalName, for: nil, floatValue: nil, with: parameters) diff --git a/Sources/TelemetryClient/TelemetryDeck.swift b/Sources/TelemetryClient/TelemetryDeck.swift index bb7fea5..dcdabe2 100644 --- a/Sources/TelemetryClient/TelemetryDeck.swift +++ b/Sources/TelemetryClient/TelemetryDeck.swift @@ -1,5 +1,12 @@ import Foundation +/// This internal singleton keeps track of the last used navigation path so +/// that the `navigate(to:)` function has a source to work off of. +class TelemetryDeckNavigationStatus { + var previousNavigationPath: String? + static let shared = TelemetryDeckNavigationStatus() +} + /// A namespace for TelemetryDeck related functionalities. public enum TelemetryDeck { /// This alias makes it easier to migrate the configuration type into the TelemetryDeck namespace in future versions when deprecated code is fully removed. @@ -33,12 +40,79 @@ public enum TelemetryDeck { TelemetryManager.send(signalName, for: customUserID, floatValue: floatValue, with: parameters) } - /// Do not call this method unless you really know what you're doing. The signals will automatically sync with the server at appropriate times, there's no need to call this. + /// Send a signal that represents a navigation event with a source and a destination + /// + /// This is a convenience method that will internally send a completely normal TelemetryDeck signals with the type + /// `TelemetryDeck.Route.Transition.navigation` and the necessary parameters. + /// + /// Since TelemetryDeck navigation signals need a source and a destination, this method will store the last + /// used destination for use in the `navigate(to:)` method. + /// + /// ## Navigation Paths + /// Navigation Paths are strings that describe a location or view in your application or website. They must be + /// delineated by either `.` or `/` characters. Delineation characters at the beginning and end of the string are + /// ignored. Use the empty string `""` for navigation from outside the app. Examples are `index`, + /// `settings.user.changePassword`, or `/blog/ios-market-share`. + /// + /// - Parameters: + /// - from: The navigation path at the beginning of the navigation event, identifying the view the user is leaving + /// - to: The navigation path at the end of the navigation event, identifying the view the user is arriving at + /// - customUserID: An optional string specifying a custom user identifier. If provided, it will override the default user identifier from the configuration. Default is `nil`. + public static func navigate(from source: String, to destination: String, customUserID: String? = nil) { + TelemetryDeckNavigationStatus.shared.previousNavigationPath = destination + + TelemetryManager.send( + "TelemetryDeck.Navigation.pathChanged", + for: customUserID, + with: [ + "TelemetryDeck.Navigation.schemaVersion": "1", + "TelemetryDeck.Navigation.identifier": "\(source) -> \(destination)", + "TelemetryDeck.Navigation.sourcePath": source, + "TelemetryDeck.Navigation.destinationPath": destination + ] + ) + } + + /// Send a signal that represents a navigation event with a destination and a default source. + /// + /// This is a convenience method that will internally send a completely normal TelemetryDeck signals with the type + /// `TelemetryDeck.Route.Transition.navigation` and the necessary parameters. + /// + /// ## Navigation Paths + /// Navigation Paths are strings that describe a location or view in your application or website. They must be + /// delineated by either `.` or `/` characters. Delineation characters at the beginning and end of the string are + /// ignored. Use the empty string `""` for navigation from outside the app. Examples are `index`, + /// `settings.user.changePassword`, or `/blog/ios-market-share`. + /// + /// ## Automatic Navigation Tracking + /// Since TelemetryDeck navigation signals need a source and a destination, this method will keep track of the last + /// used destination and will automatically insert it as a source the next time you call this method. + /// + /// This is very convenient, but will produce incorrect graphs if you don't call it from every screen in your app. + /// Suppose you have 3 tabs "Home", "User" and "Settings", but only set up navigation in "Home" and "Settings". If + /// a user taps "Home", "User" and "Settings" in that order, that'll produce an incorrect navigation signal with + /// source "Home" and destination "Settings", a path that the user did not take. + /// + /// - Parameters: + /// - to: The navigation path representing the view the user is arriving at. + /// - customUserID: An optional string specifying a custom user identifier. If provided, it will override the default user identifier from the configuration. Default is `nil`. + public static func navigate(to destination: String, customUserID: String? = nil) { + let source = TelemetryDeckNavigationStatus.shared.previousNavigationPath ?? "" + Self.navigate(from: source, to: destination, customUserID: customUserID) + } + + /// Do not call this method unless you really know what you're doing. The signals will automatically sync with + /// the server at appropriate times, there's no need to call this. + /// + /// Use this sparingly and only to indicate a time in your app where a signal was just sent but the user is likely + /// to leave your app and not return again for a long time. /// - /// Use this sparingly and only to indicate a time in your app where a signal was just sent but the user is likely to leave your app and not return again for a long time. + /// This function does not guarantee that the signal cache will be sent right away. Calling this after every + /// ``signal(_:parameters:floatValue:customUserID:)`` will not make data reach our servers faster, so avoid + /// doing that. /// - /// This function does not guarantee that the signal cache will be sent right away. Calling this after every ``signal(_:parameters:floatValue:customUserID:)`` will not make data reach our servers faster, so avoid doing that. - /// But if called at the right time (sparingly), it can help ensure the server doesn't miss important churn data because a user closes your app and doesn't reopen it anytime soon (if at all). + /// But if called at the right time (sparingly), it can help ensure the server doesn't miss important churn + /// data because a user closes your app and doesn't reopen it anytime soon (if at all). public static func requestImmediateSync() { TelemetryManager.requestImmediateSync() }