From 23555b00fd4f77db03817b65a1f165f26d9f1411 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 24 Nov 2021 10:43:22 +0000 Subject: [PATCH] Add specific methods to track analytics and test generated event types. --- Riot/Managers/Analytics/Analytics.swift | 65 +++++++++++----- Riot/Managers/Analytics/DecryptionFailure.h | 14 ++-- Riot/Managers/Analytics/DecryptionFailure.m | 7 -- .../Analytics/DecryptionFailureTracker.h | 3 +- .../Analytics/DecryptionFailureTracker.m | 17 +++-- .../Analytics/DictionaryConvertable.swift | 43 +++++++++++ Riot/Managers/Analytics/EventExtensions.swift | 74 +++++++++++++++++++ .../Analytics/Generated/GeneratedEvents.swift | 45 +++++++++++ Riot/Modules/Application/LegacyAppDelegate.m | 2 +- Riot/Modules/Room/RoomViewController.m | 6 +- .../Modal/ServiceTermsModalCoordinator.swift | 6 +- Riot/SupportingFiles/Riot-Bridging-Header.h | 1 + 12 files changed, 234 insertions(+), 49 deletions(-) create mode 100644 Riot/Managers/Analytics/DictionaryConvertable.swift create mode 100644 Riot/Managers/Analytics/EventExtensions.swift create mode 100644 Riot/Managers/Analytics/Generated/GeneratedEvents.swift diff --git a/Riot/Managers/Analytics/Analytics.swift b/Riot/Managers/Analytics/Analytics.swift index d1c36a391..ba6a55ec3 100644 --- a/Riot/Managers/Analytics/Analytics.swift +++ b/Riot/Managers/Analytics/Analytics.swift @@ -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.. delegate; +@property (nonatomic, weak) Analytics *delegate; /** Report an event unable to decrypt. diff --git a/Riot/Managers/Analytics/DecryptionFailureTracker.m b/Riot/Managers/Analytics/DecryptionFailureTracker.m index 56521eb58..d34ba0017 100644 --- a/Riot/Managers/Analytics/DecryptionFailureTracker.m +++ b/Riot/Managers/Analytics/DecryptionFailureTracker.m @@ -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 *failuresCounts = [NSMutableDictionary dictionary]; + NSMutableDictionary *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]; } } } diff --git a/Riot/Managers/Analytics/DictionaryConvertable.swift b/Riot/Managers/Analytics/DictionaryConvertable.swift new file mode 100644 index 000000000..c8bc925b4 --- /dev/null +++ b/Riot/Managers/Analytics/DictionaryConvertable.swift @@ -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 + } +} diff --git a/Riot/Managers/Analytics/EventExtensions.swift b/Riot/Managers/Analytics/EventExtensions.swift new file mode 100644 index 000000000..718c71eba --- /dev/null +++ b/Riot/Managers/Analytics/EventExtensions.swift @@ -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 + } + } +} diff --git a/Riot/Managers/Analytics/Generated/GeneratedEvents.swift b/Riot/Managers/Analytics/Generated/GeneratedEvents.swift new file mode 100644 index 000000000..11100e091 --- /dev/null +++ b/Riot/Managers/Analytics/Generated/GeneratedEvents.swift @@ -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 + } +} diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index a85fe1e2b..4357bec9b 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -2395,7 +2395,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni launchAnimationContainerView = launchLoadingView; [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:kMXAnalyticsStartupLaunchScreen - category:kMXAnalyticsStartupCategory]; + category:kMXAnalyticsStartupCategory]; } } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 5fb98c36e..bcc330c61 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -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 ()