Add specific methods to track analytics and test generated event types.

This commit is contained in:
Doug
2021-11-24 10:43:22 +00:00
parent 72f75cc36d
commit 84fdd7585c
12 changed files with 234 additions and 49 deletions
+46 -19
View File
@@ -112,29 +112,56 @@ import PostHog
func log(event: String) {
postHog?.capture(event)
}
}
// MARK: - Legacy compatibility
extension Analytics {
#warning("Use enums instead")
static let NotificationsCategory = "notifications"
static let NotificationsTimeToDisplayContent = "timelineDisplay"
static let ContactsIdentityServerAccepted = "identityServerAccepted"
static let PerformanceCategory = "Performance"
static let MetricsCategory = "Metrics"
@objc func trackScreen(_ screenName: String) {
func trackScreen(_ screenName: String) {
// postHog?.capture("screen:\(screenName)")
}
}
extension Analytics: MXAnalyticsDelegate {
@objc func trackDuration(_ seconds: TimeInterval, category: String, name: String) {
// postHog?.capture("\(category):\(name)", properties: ["duration": seconds])
func trackE2EEError(_ reason: DecryptionFailureReason, count: Int) {
for _ in 0..<count {
let event = AnalyticsEvent.Error(domain: .E2EE, name: reason.errorName, context: nil)
postHog?.capture("\(type(of: event).self)", properties: event.dictionary)
}
}
@objc func trackValue(_ value: NSNumber, category: String, name: String) {
// postHog?.capture("\(category):\(name)", properties: ["value": value])
func trackIdentityServerAccepted(granted: Bool) {
// Do we still want to track this?
}
}
// MARK: - MXAnalyticsDelegate
extension Analytics: MXAnalyticsDelegate {
func trackDuration(_ seconds: TimeInterval, category: String, name: String) { }
func trackCallStarted(_ call: MXCall) {
let event = AnalyticsEvent.CallStarted(placed: !call.isIncoming,
isVideo: call.isVideoCall,
numParticipants: Int(call.room.summary.membersCount.joined))
postHog?.capture("\(type(of: event).self)", properties: event.dictionary)
}
func trackCallEnded(_ call: MXCall) {
let event = AnalyticsEvent.CallEnded(placed: !call.isIncoming,
isVideo: call.isVideoCall,
durationMs: Int(call.duration),
numParticipants: Int(call.room.summary.membersCount.joined))
postHog?.capture("\(type(of: event).self)", properties: event.dictionary)
}
func trackCallError(_ call: MXCall, with reason: __MXCallHangupReason) {
let callEvent = AnalyticsEvent.CallError(placed: !call.isIncoming,
isVideo: call.isVideoCall,
numParticipants: Int(call.room.summary.membersCount.joined))
let event = AnalyticsEvent.Error(domain: .VOIP, name: reason.errorName, context: nil)
postHog?.capture("\(type(of: callEvent).self)", properties: callEvent.dictionary)
postHog?.capture("\(type(of: event).self)", properties: event.dictionary)
}
func trackContactsAccessGranted(_ granted: Bool) {
// Do we still want to track this?
}
}
+6 -8
View File
@@ -19,14 +19,12 @@
/**
Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI.
*/
struct DecryptionFailureReasonStruct
{
__unsafe_unretained NSString * const unspecified;
__unsafe_unretained NSString * const olmKeysNotSent;
__unsafe_unretained NSString * const olmIndexError;
__unsafe_unretained NSString * const unexpected;
typedef NS_ENUM(NSInteger, DecryptionFailureReason) {
DecryptionFailureReasonUnspecified,
DecryptionFailureReasonOlmKeysNotSent,
DecryptionFailureReasonOlmIndexError,
DecryptionFailureReasonUnexpected
};
extern const struct DecryptionFailureReasonStruct DecryptionFailureReason;
/**
`DecryptionFailure` represents a decryption failure.
@@ -46,6 +44,6 @@ extern const struct DecryptionFailureReasonStruct DecryptionFailureReason;
/**
Decryption failure reason.
*/
@property (nonatomic) NSString *reason;
@property (nonatomic) DecryptionFailureReason reason;
@end
@@ -16,13 +16,6 @@
#import "DecryptionFailure.h"
const struct DecryptionFailureReasonStruct DecryptionFailureReason = {
.unspecified = @"unspecified_error",
.olmKeysNotSent = @"olm_keys_not_sent_error",
.olmIndexError = @"olm_index_error",
.unexpected = @"unexpected_error"
};
@implementation DecryptionFailure
- (instancetype)init
@@ -18,6 +18,7 @@
#import "DecryptionFailure.h"
@class Analytics;
@import MatrixSDK;
@interface DecryptionFailureTracker : NSObject
@@ -32,7 +33,7 @@
/**
The delegate object to receive analytics events.
*/
@property (nonatomic, weak) id<MXAnalyticsDelegate> delegate;
@property (nonatomic, weak) Analytics *delegate;
/**
Report an event unable to decrypt.
@@ -15,6 +15,7 @@
*/
#import "DecryptionFailureTracker.h"
#import "GeneratedInterface-Swift.h"
// Call `checkFailures` every `CHECK_INTERVAL`
@@ -97,20 +98,20 @@ NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure";
switch (event.decryptionError.code)
{
case MXDecryptingErrorUnknownInboundSessionIdCode:
decryptionFailure.reason = DecryptionFailureReason.olmKeysNotSent;
decryptionFailure.reason = DecryptionFailureReasonOlmKeysNotSent;
break;
case MXDecryptingErrorOlmCode:
decryptionFailure.reason = DecryptionFailureReason.olmIndexError;
decryptionFailure.reason = DecryptionFailureReasonOlmIndexError;
break;
case MXDecryptingErrorEncryptionNotEnabledCode:
case MXDecryptingErrorUnableToDecryptCode:
decryptionFailure.reason = DecryptionFailureReason.unexpected;
decryptionFailure.reason = DecryptionFailureReasonUnexpected;
break;
default:
decryptionFailure.reason = DecryptionFailureReason.unspecified;
decryptionFailure.reason = DecryptionFailureReasonUnspecified;
break;
}
@@ -152,17 +153,17 @@ NSString *const kDecryptionFailureTrackerAnalyticsCategory = @"e2e.failure";
if (failuresToTrack.count)
{
// Sort failures by error reason
NSMutableDictionary<NSString*, NSNumber*> *failuresCounts = [NSMutableDictionary dictionary];
NSMutableDictionary<NSNumber*, NSNumber*> *failuresCounts = [NSMutableDictionary dictionary];
for (DecryptionFailure *failure in failuresToTrack)
{
failuresCounts[failure.reason] = @(failuresCounts[failure.reason].unsignedIntegerValue + 1);
failuresCounts[@(failure.reason)] = @(failuresCounts[@(failure.reason)].unsignedIntegerValue + 1);
}
MXLogDebug(@"[DecryptionFailureTracker] trackFailures: %@", failuresCounts);
for (NSString *reason in failuresCounts)
for (NSNumber *reason in failuresCounts)
{
[_delegate trackValue:failuresCounts[reason] category:kDecryptionFailureTrackerAnalyticsCategory name:reason];
[self.delegate trackE2EEError:reason.integerValue count:failuresCounts[reason].integerValue];
}
}
}
@@ -0,0 +1,43 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol DictionaryConvertible {
var dictionary: [String: Any] { get }
}
extension DictionaryConvertible {
var dictionary: [String: Any] {
let mirror = Mirror(reflecting: self)
let dict: [String: Any] = Dictionary(uniqueKeysWithValues: mirror.children
.compactMap { (label: String?, value: Any) in
guard let label = label else { return nil }
if let value = value as? NSCoding {
return (label, value)
}
if let value = value as? CustomStringConvertible {
return (label, value.description)
}
return nil
})
return dict
}
}
@@ -0,0 +1,74 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
// MARK: -Events
extension AnalyticsEvent.Error: DictionaryConvertible { }
extension AnalyticsEvent.CallStarted: DictionaryConvertible { }
extension AnalyticsEvent.CallEnded: DictionaryConvertible { }
extension AnalyticsEvent.CallError: DictionaryConvertible { }
// MARK: - Enums
extension AnalyticsEvent.ErrorDomain: CustomStringConvertible {
var description: String { rawValue }
}
extension AnalyticsEvent.ErrorName: CustomStringConvertible {
var description: String { rawValue }
}
// MARK: - Helpers
extension __MXCallHangupReason {
var errorName: AnalyticsEvent.ErrorName {
switch self {
case .userHangup:
return .VoipUserHangup
case .inviteTimeout:
return .VoipInviteTimeout
case .iceFailed:
return .VoipIceFailed
case .iceTimeout:
return .VoipIceTimeout
case .userMediaFailed:
return .VoipUserMediaFailed
case .unknownError:
return .UnknownError
default:
return .UnknownError
}
}
}
extension DecryptionFailureReason {
var errorName: AnalyticsEvent.ErrorName {
switch self {
case .unspecified:
return .OlmUnspecifiedError
case .olmKeysNotSent:
return .OlmKeysNotSentError
case .olmIndexError:
return .OlmIndexError
case .unexpected:
return .UnknownError
default:
return .UnknownError
}
}
}
@@ -0,0 +1,45 @@
import Foundation
struct AnalyticsEvent {
struct Error {
let domain: ErrorDomain
let name: ErrorName
let context: String?
}
enum ErrorDomain: String {
case E2EE
case VOIP
}
enum ErrorName: String {
case UnknownError
case OlmIndexError
case OlmKeysNotSentError
case OlmUnspecifiedError
case VoipUserHangup
case VoipIceFailed
case VoipInviteTimeout
case VoipIceTimeout
case VoipUserMediaFailed
}
struct CallStarted {
let placed: Bool
let isVideo: Bool
let numParticipants: Int
}
struct CallEnded {
let placed: Bool
let isVideo: Bool
let durationMs: Int
let numParticipants: Int
}
struct CallError {
let placed: Bool
let isVideo: Bool
let numParticipants: Int
}
}