Files
letro-ios/ElementX/Sources/Services/Analytics/AnalyticsService.swift
Michael Kaye a1b66c170f Performance tests (#1301)
* Add signposts to performance tests.

- Update flow to include support for the migration screen.

* If the welcome screen shows, click on the button.

* Ensure a clean simulator each run.

* Add accessibility identifier for migration screen if required.

* Handle walking into the room and back out again.

* use iphone 14 pro to match what's used in xcode.

* Remove ApplicationTests as duplicated in LoginTests.

We measure app startup time in LoginTests as part of the flow - we may as well avoid spending 60s doing only that measurement in ApplicationTests

* Sleep 10s, the ui is otherwise showing up in random order.

* Revert "Remove ApplicationTests as duplicated in LoginTests."

This reverts commit 8670710315bcd0d6c3c3046f534b32b4c728b837.

* Update script to parse out correct values from results file.

* Allow cancellation of password prompt in any order.

* Remove test timeout, performance tests will always take a while.

* Adjust parsing further

* Remove ApplicationTests.

* Move to a more elegant way to wait for something to disappear.

* Linting.

* Fix unit tests.

---------

Co-authored-by: Doug <douglase@element.io>
Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
2023-07-11 10:32:24 +01:00

184 lines
7.2 KiB
Swift

//
// 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 AnalyticsEvents
import PostHog
/// A class responsible for managing a variety of analytics clients
/// and sending events through these clients.
///
/// Events may include user activity, or app health data such as crashes,
/// non-fatal issues and performance. `Analytics` class serves as a façade
/// to all these use cases.
///
/// ## Creating Analytics Events
///
/// Events are managed in a shared repo for all Element clients https://github.com/matrix-org/matrix-analytics-events
/// To add a new event create a PR to that repo with the new/updated schema. Once merged
/// into `main`, update the AnalyticsEvents Swift package in `project.yml`.
///
class AnalyticsService {
/// The analytics client to send events with.
private let client: AnalyticsClientProtocol
private let appSettings: AppSettings
private let bugReportService: BugReportServiceProtocol
/// A signpost client for performance testing the app. This client doesn't respect the
/// `isRunning` state or behave any differently when `start`/`reset` are called.
let signpost = Signposter()
init(client: AnalyticsClientProtocol, appSettings: AppSettings, bugReportService: BugReportServiceProtocol) {
self.client = client
self.appSettings = appSettings
self.bugReportService = bugReportService
}
/// Whether or not the object is enabled and sending events to the server.
var isRunning: Bool { client.isRunning }
/// Whether to show the user the analytics opt in prompt.
var shouldShowAnalyticsPrompt: Bool {
// Only show the prompt once, and when analytics are enabled in BuildSettings.
appSettings.analyticsConsentState == .unknown && appSettings.analyticsConfiguration.isEnabled
}
var isEnabled: Bool {
appSettings.analyticsConsentState == .optedIn
}
/// Opts in to analytics tracking with the supplied user session.
func optIn() {
appSettings.analyticsConsentState = .optedIn
startIfEnabled()
}
/// Stops analytics tracking and calls `reset` to clear any IDs and event queues.
func optOut() {
appSettings.analyticsConsentState = .optedOut
// The order is important here. PostHog ignores the reset if stopped.
reset()
client.stop()
bugReportService.stop()
MXLog.info("Stopped.")
}
/// Starts the analytics client if the user has opted in, otherwise does nothing.
func startIfEnabled() {
guard isEnabled, !isRunning else { return }
client.start(analyticsConfiguration: appSettings.analyticsConfiguration)
bugReportService.start()
// Sanity check in case something went wrong.
guard client.isRunning else { return }
MXLog.info("Started.")
}
/// Resets the any IDs and event queues in the analytics client. This method should
/// be called on sign-out to maintain opt-in status, whilst ensuring the next
/// account used isn't associated with the previous one.
/// Note: **MUST** be called before stopping PostHog or the reset is ignored.
func reset() {
client.reset()
bugReportService.reset()
MXLog.info("Reset.")
}
/// Reset the consent state for analytics
func resetConsentState() {
MXLog.warning("Resetting consent state for analytics.")
appSettings.analyticsConsentState = .unknown
}
/// Flushes the event queue in the analytics client, uploading all pending events.
/// Normally events are sent in batches. Call this method when you need an event
/// to be sent immediately.
func forceUpload() {
client.flush()
}
// MARK: - Private
/// Capture an event in the `client`.
/// - Parameter event: The event to capture.
private func capture(event: AnalyticsEventProtocol) {
MXLog.debug("\(event)")
client.capture(event)
}
}
// MARK: - Public tracking methods
extension AnalyticsService {
/// Track the presentation of a screen
/// - Parameter screen: The screen that was shown
/// - Parameter duration: An optional value representing how long the screen was shown for in milliseconds.
func track(screen: AnalyticsScreen, duration milliseconds: Int? = nil) {
MXLog.debug("\(screen)")
let event = AnalyticsEvent.MobileScreen(durationMs: milliseconds, screenName: screen.screenName)
client.screen(event)
}
/// Track the creation of a room
/// - Parameter isDM: true if the created room is a direct message, false otherwise
func trackCreatedRoom(isDM: Bool) {
capture(event: AnalyticsEvent.CreatedRoom(isDM: isDM))
}
/// Track the composer
/// - Parameters:
/// - inThread: whether the composer is used in a Thread
/// - isEditing: whether the composer is used to edit a message
/// - isReply: whether the composer is used to reply a message
/// - locationType: the type of the shared location
/// - startsThread: whether the composer is used to start a new thread
func trackComposer(inThread: Bool,
isEditing: Bool,
isReply: Bool,
locationType: AnalyticsLocationType? = nil,
startsThread: Bool?) {
capture(event: AnalyticsEvent.Composer(inThread: inThread,
isEditing: isEditing,
isLocation: locationType != nil,
isReply: isReply,
locationType: locationType.map { .init($0) },
startsThread: startsThread))
}
/// Track the presentation of a room
/// - Parameters:
/// - isDM: whether the room is a direct message
/// - isSpace: whether the room is a space
func trackViewRoom(isDM: Bool, isSpace: Bool) {
capture(event: AnalyticsEvent.ViewRoom(activeSpace: nil, isDM: isDM, isSpace: isSpace, trigger: nil, viaKeyboard: nil))
}
/// Track the action of joining a room
/// - Parameters:
/// - isDM: whether the room is a direct message
/// - isSpace: whether the room is a space
/// - activeMemberCount: the number of active members in the room
func trackJoinedRoom(isDM: Bool, isSpace: Bool, activeMemberCount: UInt) {
guard let roomSize = AnalyticsEvent.JoinedRoom.RoomSize(memberCount: activeMemberCount) else {
MXLog.error("invalid room size")
return
}
capture(event: AnalyticsEvent.JoinedRoom(isDM: isDM, isSpace: isSpace, roomSize: roomSize, trigger: nil))
}
}