mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Add specific methods to track analytics and test generated event types.
This commit is contained in:
parent
50dea9843b
commit
23555b00fd
12 changed files with 234 additions and 49 deletions
|
@ -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)")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func trackIdentityServerAccepted(granted: Bool) {
|
||||
// Do we still want to track this?
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MXAnalyticsDelegate
|
||||
extension Analytics: MXAnalyticsDelegate {
|
||||
@objc func trackDuration(_ seconds: TimeInterval, category: String, name: String) {
|
||||
// postHog?.capture("\(category):\(name)", properties: ["duration": seconds])
|
||||
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)
|
||||
}
|
||||
|
||||
@objc func trackValue(_ value: NSNumber, category: String, name: String) {
|
||||
// postHog?.capture("\(category):\(name)", properties: ["value": value])
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
43
Riot/Managers/Analytics/DictionaryConvertable.swift
Normal file
43
Riot/Managers/Analytics/DictionaryConvertable.swift
Normal file
|
@ -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
|
||||
}
|
||||
}
|
74
Riot/Managers/Analytics/EventExtensions.swift
Normal file
74
Riot/Managers/Analytics/EventExtensions.swift
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
45
Riot/Managers/Analytics/Generated/GeneratedEvents.swift
Normal file
45
Riot/Managers/Analytics/Generated/GeneratedEvents.swift
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -2395,7 +2395,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
launchAnimationContainerView = launchLoadingView;
|
||||
|
||||
[MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:kMXAnalyticsStartupLaunchScreen
|
||||
category:kMXAnalyticsStartupCategory];
|
||||
category:kMXAnalyticsStartupCategory];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -134,6 +134,8 @@
|
|||
|
||||
NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNotification";
|
||||
NSNotificationName const RoomGroupCallTileTappedNotification = @"RoomGroupCallTileTappedNotification";
|
||||
NSString * const RoomAnalyticsNotificationsCategory = @"notifications";
|
||||
NSString * const RoomAnalyticsNotificationsTimeToDisplayContent = @"timelineDisplay";
|
||||
const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
||||
|
||||
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
|
||||
|
@ -610,8 +612,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
|
|||
[self.roomDataSource reload];
|
||||
[LegacyAppDelegate theDelegate].lastNavigatedRoomIdFromPush = nil;
|
||||
|
||||
notificationTaskProfile = [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:Analytics.NotificationsTimeToDisplayContent
|
||||
category:Analytics.NotificationsCategory];
|
||||
notificationTaskProfile = [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:RoomAnalyticsNotificationsTimeToDisplayContent
|
||||
category:RoomAnalyticsNotificationsCategory];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
|
|||
|
||||
func serviceTermsModalScreenCoordinatorDidAccept(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
|
||||
if serviceTerms.serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.shared.trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: Analytics.ContactsIdentityServerAccepted)
|
||||
Analytics.shared.trackIdentityServerAccepted(granted: true)
|
||||
}
|
||||
|
||||
self.delegate?.serviceTermsModalCoordinatorDidAccept(self)
|
||||
|
@ -119,7 +119,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
|
|||
|
||||
func serviceTermsModalScreenCoordinatorDidDecline(_ coordinator: ServiceTermsModalScreenCoordinatorType) {
|
||||
if serviceTerms.serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.shared.trackValue(1, category: MXKAnalyticsCategory.contacts.rawValue, name: Analytics.ContactsIdentityServerAccepted)
|
||||
Analytics.shared.trackIdentityServerAccepted(granted: false)
|
||||
disableIdentityServer()
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ extension ServiceTermsModalCoordinator: ServiceTermsModalScreenCoordinatorDelega
|
|||
extension ServiceTermsModalCoordinator: UIAdaptivePresentationControllerDelegate {
|
||||
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
||||
if serviceTerms.serviceType == MXServiceTypeIdentityService {
|
||||
Analytics.shared.trackValue(0, category: MXKAnalyticsCategory.contacts.rawValue, name: Analytics.ContactsIdentityServerAccepted)
|
||||
Analytics.shared.trackIdentityServerAccepted(granted: false)
|
||||
}
|
||||
|
||||
self.delegate?.serviceTermsModalCoordinatorDidDismissInteractively(self)
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#import "RoomInputToolbarView.h"
|
||||
#import "NSArray+Element.h"
|
||||
#import "ShareItemSender.h"
|
||||
#import "DecryptionFailure.h"
|
||||
|
||||
// MatrixKit common imports, shared with all targets
|
||||
#import "MatrixKit-Bridging-Header.h"
|
||||
|
|
Loading…
Reference in a new issue