Last active
February 24, 2025 13:04
-
-
Save glowcap/a7f192ae044e0f2150e8bdd19dc20923 to your computer and use it in GitHub Desktop.
AIChat app analytic events setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Basing Modules/Logs, Analytics, Crashlytics/Adding Events (1/4) | |
/* | |
The idea here is to eliminate a majority of the duplication while keeping the same event output. | |
The final result being what is shown below | |
*/ | |
// MARK: Analytic Events | |
enum Event: LoggableEvent, EventEnum { | |
case existingAuthStart | |
case existingAuthFail(error: Error) | |
case anonAuthStart | |
case anonAuthSuccess | |
case anonAuthFail(error: Error) | |
// note: I changed LoggableEvent.eventName -> LoggableEvent.name | |
var name: String { | |
// example output: AppView_ExistingAuth_Start | |
eventName(viewName: AppView.viewName()) | |
} | |
var parameters: [String: Any]? { | |
switch self { | |
case .existingAuthFail(let error), | |
.anonAuthFail(let error): | |
error.eventParameters | |
default: nil | |
} | |
} | |
var type: LogType { | |
switch self { | |
case .existingAuthFail, .anonAuthFail: | |
.severe | |
default: | |
.analytic | |
} | |
} | |
} | |
// To get the view name, we'll create an extension of View | |
extension View { | |
// A static function to get the name of the view | |
nonisolated | |
static func viewName() -> String { | |
String(describing: Self.self) | |
} | |
} | |
// Next we'll want to split the enum name (anonAuthStart -> ["anon", "Auth", "Start"] | |
// this can be done with RegexBuilder and a String extension | |
import RexgexBuilder | |
extension String { | |
func splitByCapitals() -> [String] { | |
let regex = Regex { | |
NegativeLookahead { // Equivalent to (?<!^) | |
Anchor.startOfLine | |
} | |
Capture { | |
("A"..."Z") | |
} | |
} | |
// Replace matches with a space followed by the matched capital letter | |
let modifiedString = self.replacing(regex) { match in | |
// Insert a space before each match to separate by in the next step | |
" \(match.1)" | |
} | |
// Separate the modified string by spaces | |
return modifiedString.components(separatedBy: " ") | |
} | |
} | |
// Now that we have an array, we'll want to join it back with an underscore | |
// before the result ["anon", "Auth", "Start"] -> "anonAuth_Start" | |
// It's important to note here that the result could be one or more words, | |
// so we'll need to account for that. We can do this in an extension of Array. | |
extension Array where Element == String { | |
/// Joins elements with underscore before the last number of elements provided | |
/// - Parameter last: Number of elements after the underscore. Defaults to `1` | |
/// - Returns: Joined string with underscore before the last number of elements | |
/// - Note: An empty array returns an empty string. If last count is greater than or | |
/// equal to the count, returns all elements joined prefixed with an underscore. | |
func underscore(last: Int = 1) -> String { | |
/// return an empty string if array is empty | |
guard !isEmpty else { return "" } | |
/// return array joined if only one element or last value is greater than count | |
guard count != 1, count > last | |
else { return "_\(self.joined())" } | |
/// get index for underscore | |
let lastIndex = self.count - last | |
/// join prefix elements | |
let prefixStr = lastIndex - 1 == 0 | |
? self[0] | |
: self[0...lastIndex - last].joined() | |
/// join suffix elements | |
let suffixStr = last > 1 | |
? self[lastIndex...self.count - 1].joined() | |
: self[lastIndex] | |
return prefixStr + "_" + suffixStr | |
} | |
} | |
// underscoreLast(_:) gives us the flexibility needed to | |
// do "dataFetch_Success" or "dataFetch_RetrySuccess" | |
// The last step is to build out the EventEnum protocol; starting | |
// with the public function definition | |
protocol EventEnum { | |
func eventName(viewName: String, underscoreLast last: Int) -> String | |
} | |
// Next, we'll add an extension with the function implentation that | |
// joins the viewName with the eventName that keeps the length below | |
// 40 characters (FB Analytics requirement). Though we're still missing the | |
// `eventName(underscoreLast:)` and `trimmedEventName(viewName:, eventName:)` | |
// methods. | |
extension EventEnum { | |
func eventName(viewName: String, underscoreLast last: Int = 1) -> String { | |
let eventName = eventName(underscoreLast: last) | |
let trimmedEvent = trimmedEventName(viewName: viewName, eventName: eventName) | |
return trimmedEvent | |
} | |
} | |
// Finally, we will the missing private functions to the EventEnum extension. | |
extension EventEnum { | |
// This first one gets the enum case name, applies the split, capitalizes the first | |
// word of the enum, and joins it with an underscore in where we | |
// determined it to be. | |
/// Creates event name with capitalized values and an underscore between the event and result. | |
/// - Parameter last: number of words after the underscore. Defaults to `1` | |
/// - Returns: Returns self's name with each word capitalized and an underscore before the last count. | |
private func eventName(underscoreLast last: Int = 1) -> String { | |
let mirror = Mirror(reflecting: self) | |
/// This should always get the enum case name. Fallback will include associated value | |
let name = mirror.children.first?.label ?? String(describing: self) | |
/// capitalizing all, but only really needs to be applied to the first element | |
return name.splitByCapitals().map({ $0.capitalized }).underscore(last: last) | |
} | |
// This last one puts the name togther and checks the length. If the length is | |
// greater than 40, it trims the view name (ex: MyLongNameView_Fetch_Success -> MyLongNam_Fetch_Success). | |
// There are probably better ways to do this like using RegEx to drop vowels in the | |
// screen name, but I'll leave that up to you | |
/// Trims an event name to 40 characters by clipping characters from the view name | |
/// - Parameters: | |
/// - viewName: Name of the event owner view | |
/// - eventName: Name of the event | |
/// - Returns: Event name limited to 40 characters | |
private func trimmedEventName(viewName: String, eventName: String) -> String { | |
let maxLength = 40 | |
let nameSuffix = "_" + eventName | |
var viewAndEventName = viewName + nameSuffix | |
if viewAndEventName.count > maxLength { | |
let charsToTrim = viewAndEventName.count - maxLength | |
let viewNameTrimmed = viewName.clipped(max: viewName.count - charsToTrim) // clipped(max:) is custom. See below | |
viewAndEventName = viewNameTrimmed + nameSuffix | |
} | |
return viewAndEventName | |
} | |
} | |
// Now every enum that conforms to EventEnum can leverage eventName(viewName:, underscoreLast:) | |
// The function `clipped(max:)` is a custom method from the course. It returns a prefix of a given string |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Enhancement
Updated
eventName(viewName:, underscoreLast last:)
to trim to 40 characters by clipping the screen name