mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
#106: Begin adding the Analytics class from EI.
No UI or SDK support to enable/disable yet and no tracking is implemented.
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// 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 Analytics {
|
||||
// MARK: - Properties
|
||||
|
||||
/// The singleton instance to be used within the Riot target.
|
||||
static let shared = Analytics()
|
||||
|
||||
/// The analytics client to send events with.
|
||||
private var client: AnalyticsClientProtocol = PostHogAnalyticsClient()
|
||||
|
||||
/// The monitoring client to track crashes, issues and performance
|
||||
// private var monitoringClient = SentryMonitoringClient()
|
||||
|
||||
/// The service used to interact with account data settings.
|
||||
private var service: AnalyticsService?
|
||||
|
||||
/// 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.
|
||||
!ElementSettings.shared.hasSeenAnalyticsPrompt && BuildSettings.analyticsConfiguration.isEnabled
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
/// Opts in to analytics tracking with the supplied session.
|
||||
/// - Parameter session: An optional session to use to when reading/generating the analytics ID.
|
||||
/// The session will be ignored if not running.
|
||||
func optIn(with session: UserSessionProtocol) {
|
||||
ElementSettings.shared.enableAnalytics = true
|
||||
startIfEnabled()
|
||||
|
||||
Task { await useAnalyticsSettings(from: session) }
|
||||
}
|
||||
|
||||
/// Stops analytics tracking and calls `reset` to clear any IDs and event queues.
|
||||
func optOut() {
|
||||
ElementSettings.shared.enableAnalytics = false
|
||||
|
||||
// The order is important here. PostHog ignores the reset if stopped.
|
||||
reset()
|
||||
client.stop()
|
||||
// monitoringClient.stop()
|
||||
|
||||
MXLog.debug("Stopped.")
|
||||
}
|
||||
|
||||
/// Starts the analytics client if the user has opted in, otherwise does nothing.
|
||||
func startIfEnabled() {
|
||||
guard ElementSettings.shared.enableAnalytics, !isRunning else { return }
|
||||
|
||||
client.start()
|
||||
// monitoringClient.start()
|
||||
|
||||
// Sanity check in case something went wrong.
|
||||
guard client.isRunning else { return }
|
||||
|
||||
MXLog.debug("Started.")
|
||||
|
||||
// Catch and log crashes
|
||||
// MXLogger.logCrashes(true)
|
||||
// MXLogger.setBuildVersion(ElementInfoPlist.cfBundleShortVersionString)
|
||||
}
|
||||
|
||||
/// Use the analytics settings from the supplied session to configure analytics.
|
||||
/// For now this is only used for (pseudonymous) identification.
|
||||
/// - Parameter session: The session to read analytics settings from.
|
||||
func useAnalyticsSettings(from session: UserSessionProtocol) async {
|
||||
guard
|
||||
ElementSettings.shared.enableAnalytics,
|
||||
!ElementSettings.shared.isIdentifiedForAnalytics
|
||||
else { return }
|
||||
|
||||
let service = AnalyticsService(session: session)
|
||||
self.service = service
|
||||
|
||||
switch await service.settings() {
|
||||
case .success(let settings):
|
||||
identify(with: settings)
|
||||
self.service = nil
|
||||
case .failure:
|
||||
MXLog.error("Failed to use analytics settings. Will continue to run without analytics ID.")
|
||||
self.service = nil
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
// monitoringClient.reset()
|
||||
|
||||
MXLog.debug("Reset.")
|
||||
ElementSettings.shared.isIdentifiedForAnalytics = false
|
||||
|
||||
// Stop collecting crash logs
|
||||
// MXLogger.logCrashes(false)
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
/// Identify (pseudonymously) any future events with the ID from the analytics account data settings.
|
||||
/// - Parameter settings: The settings to use for identification. The ID must be set *before* calling this method.
|
||||
private func identify(with settings: AnalyticsSettings) {
|
||||
guard let id = settings.id else {
|
||||
MXLog.error("identify(with:) called before an ID has been generated.")
|
||||
return
|
||||
}
|
||||
|
||||
client.identify(id: id)
|
||||
MXLog.debug("Identified.")
|
||||
ElementSettings.shared.isIdentifiedForAnalytics = true
|
||||
}
|
||||
|
||||
/// Capture an event in the `client`.
|
||||
/// - Parameter event: The event to capture.
|
||||
private func capture(event: AnalyticsEventProtocol) {
|
||||
client.capture(event)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public tracking methods
|
||||
|
||||
// The following methods are exposed for compatibility with Objective-C as
|
||||
// the `capture` method and the generated events cannot be bridged from Swift.
|
||||
extension Analytics { }
|
||||
|
||||
// MARK: - MXAnalyticsDelegate
|
||||
|
||||
// extension Analytics: MXAnalyticsDelegate {
|
||||
// }
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// A protocol representing an analytics client.
|
||||
protocol AnalyticsClientProtocol {
|
||||
/// Whether the analytics client is currently reporting data or ignoring it.
|
||||
var isRunning: Bool { get }
|
||||
|
||||
/// Starts the analytics client reporting data.
|
||||
func start()
|
||||
|
||||
/// Associate the client with an ID. This is persisted until `reset` is called.
|
||||
/// - Parameter id: The ID to associate with the user.
|
||||
func identify(id: String)
|
||||
|
||||
/// Reset all stored properties and any event queues on the client. Note that
|
||||
/// the client will remain active, but in a fresh unidentified state.
|
||||
func reset()
|
||||
|
||||
/// Stop the analytics client reporting data.
|
||||
func stop()
|
||||
|
||||
/// Send any queued events immediately.
|
||||
func flush()
|
||||
|
||||
/// Capture the supplied analytics event.
|
||||
/// - Parameter event: The event to capture.
|
||||
func capture(_ event: AnalyticsEventProtocol)
|
||||
|
||||
/// Capture the supplied analytics screen event.
|
||||
/// - Parameter event: The screen event to capture.
|
||||
func screen(_ event: AnalyticsScreenProtocol)
|
||||
|
||||
/// Updates any user properties to help with creating cohorts.
|
||||
/// - Parameter userProperties: The user properties to be updated.
|
||||
///
|
||||
/// Only non-nil properties will be updated when calling this method. There might
|
||||
/// be a delay when updating user properties as these are cached to be included
|
||||
/// as part of the next event that gets captured.
|
||||
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright 2022 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
|
||||
|
||||
/// A type that represents how to set up the analytics module in the app.
|
||||
struct AnalyticsConfiguration {
|
||||
/// Whether or not analytics should be enabled.
|
||||
let isEnabled: Bool
|
||||
/// The host to use for PostHog analytics.
|
||||
let host: String
|
||||
/// The public key for submitting analytics.
|
||||
let apiKey: String
|
||||
/// The URL to open with more information about analytics terms.
|
||||
let termsURL: URL
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
enum AnalyticsServiceError: Error {
|
||||
/// The session supplied to the service does not have a state of `MXSessionStateRunning`.
|
||||
case sessionIsNotRunning
|
||||
/// The service failed to get or update the analytics settings event from the user's account data.
|
||||
case accountDataFailure
|
||||
}
|
||||
|
||||
/// A service responsible for handling the `im.vector.analytics` event from the user's account data.
|
||||
class AnalyticsService {
|
||||
let session: UserSessionProtocol
|
||||
|
||||
/// Creates an analytics service with the supplied session.
|
||||
/// - Parameter session: The session to use when reading analytics settings from account data.
|
||||
init(session: UserSessionProtocol) {
|
||||
self.session = session
|
||||
}
|
||||
|
||||
/// The analytics settings for the current user. Calling this method will check whether the settings already
|
||||
/// contain an `id` property and if not, will add one to the account data before calling the completion.
|
||||
/// - Parameter completion: A completion handler that will be called when the request completes.
|
||||
///
|
||||
/// The request will fail if the service's session does not have the `MXSessionStateRunning` state.
|
||||
func settings() async -> Result<AnalyticsSettings, AnalyticsServiceError> {
|
||||
// Only use the session if it is running otherwise we could wipe out an existing analytics ID.
|
||||
fatalWithoutUnreachableCodeWarning()
|
||||
// guard session.state == .running else {
|
||||
// MXLog.warning("Aborting attempt to read analytics settings. The session may not be up-to-date.")
|
||||
// return .failure(.sessionIsNotRunning)
|
||||
// }
|
||||
|
||||
let result: Result<AnalyticsSettings?, ClientProxyError> = await session.clientProxy.accountDataEvent(type: AnalyticsSettings.eventType)
|
||||
switch result {
|
||||
case .failure:
|
||||
return .failure(.accountDataFailure)
|
||||
case .success(let settings):
|
||||
// The id has already be set so we are done here.
|
||||
if let settings = settings, settings.id != nil {
|
||||
return .success(settings)
|
||||
}
|
||||
|
||||
let newSettings = AnalyticsSettings.new(currentEvent: settings)
|
||||
switch await session.clientProxy.setAccountData(content: newSettings, type: AnalyticsSettings.eventType) {
|
||||
case .failure:
|
||||
MXLog.warning("Failed to update analytics settings.")
|
||||
return .failure(.accountDataFailure)
|
||||
case .success:
|
||||
MXLog.debug("Successfully updated analytics settings in account data.")
|
||||
return .success(newSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Silences a warning on some intentionally unreachable code.
|
||||
func fatalWithoutUnreachableCodeWarning() {
|
||||
fatalError("Missing running state detection.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// An analytics settings event from the user's account data.
|
||||
struct AnalyticsSettings: Codable {
|
||||
static let eventType = "im.vector.analytics"
|
||||
|
||||
/// A randomly generated analytics token for this user.
|
||||
/// This is suggested to be a UUID string.
|
||||
let id: String?
|
||||
|
||||
/// Whether the user has opted in on web or not. This is unused on iOS but necessary
|
||||
/// to store here so that it's value is preserved when updating the account data if we
|
||||
/// generated an ID on iOS.
|
||||
///
|
||||
/// `true` if opted in on web, `false` if opted out on web and `nil` if the web prompt is not yet seen.
|
||||
private let webOptIn: Bool?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case webOptIn = "pseudonymousAnalyticsOptIn"
|
||||
}
|
||||
}
|
||||
|
||||
extension AnalyticsSettings {
|
||||
/// Generates a new AnalyticsSettings value (inc an ID if necessary) based upon an
|
||||
/// existing value. This is the only way the type should be created so as to avoid wiping
|
||||
/// out the `webOptIn` value that the user may already have set.
|
||||
///
|
||||
/// **Note:** Please don't pass a `nil` literal to this method.
|
||||
static func new(currentEvent: AnalyticsSettings?) -> AnalyticsSettings {
|
||||
AnalyticsSettings(id: currentEvent?.id ?? UUID().uuidString,
|
||||
webOptIn: currentEvent?.webOptIn)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
extension AnalyticsEvent.JoinedRoom.RoomSize {
|
||||
init?(memberCount: UInt) {
|
||||
switch memberCount {
|
||||
case 2:
|
||||
self = .Two
|
||||
case 3...10:
|
||||
self = .ThreeToTen
|
||||
case 11...100:
|
||||
self = .ElevenToOneHundred
|
||||
case 101...1000:
|
||||
self = .OneHundredAndOneToAThousand
|
||||
case 1001...:
|
||||
self = .MoreThanAThousand
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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 Foundation
|
||||
|
||||
public extension AnalyticsEvent.UserProperties {
|
||||
// Initializer for Element. Strips all Web properties.
|
||||
init(ftueUseCaseSelection: FtueUseCaseSelection?, numFavouriteRooms: Int?, numSpaces: Int?, allChatsActiveFilter: AllChatsActiveFilter?) {
|
||||
self.init(WebMetaSpaceFavouritesEnabled: nil,
|
||||
WebMetaSpaceHomeAllRooms: nil,
|
||||
WebMetaSpaceHomeEnabled: nil,
|
||||
WebMetaSpaceOrphansEnabled: nil,
|
||||
WebMetaSpacePeopleEnabled: nil,
|
||||
allChatsActiveFilter: nil,
|
||||
ftueUseCaseSelection: ftueUseCaseSelection,
|
||||
numFavouriteRooms: numFavouriteRooms,
|
||||
numSpaces: numSpaces)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// 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 PostHog
|
||||
|
||||
extension PHGPostHogConfiguration {
|
||||
static var standard: PHGPostHogConfiguration? {
|
||||
let analyticsConfiguration = BuildSettings.analyticsConfiguration
|
||||
guard analyticsConfiguration.isEnabled else { return nil }
|
||||
|
||||
let postHogConfiguration = PHGPostHogConfiguration(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host)
|
||||
postHogConfiguration.shouldSendDeviceID = false
|
||||
|
||||
return postHogConfiguration
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// An analytics client that reports events to a PostHog server.
|
||||
class PostHogAnalyticsClient: AnalyticsClientProtocol {
|
||||
/// The PHGPostHog object used to report events.
|
||||
private var postHog: PHGPostHog?
|
||||
|
||||
/// Any user properties to be included with the next captured event.
|
||||
private(set) var pendingUserProperties: AnalyticsEvent.UserProperties?
|
||||
|
||||
var isRunning: Bool { postHog?.enabled ?? false }
|
||||
|
||||
func start() {
|
||||
// Only start if analytics have been configured in BuildSettings
|
||||
guard let configuration = PHGPostHogConfiguration.standard else { return }
|
||||
|
||||
if postHog == nil {
|
||||
postHog = PHGPostHog(configuration: configuration)
|
||||
}
|
||||
|
||||
postHog?.enable()
|
||||
}
|
||||
|
||||
func identify(id: String) {
|
||||
if let userProperties = pendingUserProperties {
|
||||
// As user properties overwrite old ones, compactMap the dictionary to avoid resetting any missing properties
|
||||
postHog?.identify(id, properties: userProperties.properties.compactMapValues { $0 })
|
||||
pendingUserProperties = nil
|
||||
} else {
|
||||
postHog?.identify(id)
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
postHog?.reset()
|
||||
pendingUserProperties = nil
|
||||
}
|
||||
|
||||
func stop() {
|
||||
postHog?.disable()
|
||||
|
||||
// As of PostHog 1.4.4, setting the client to nil here doesn't release
|
||||
// it. Keep it around to avoid having multiple instances if the user re-enables
|
||||
}
|
||||
|
||||
func flush() {
|
||||
postHog?.flush()
|
||||
}
|
||||
|
||||
func capture(_ event: AnalyticsEventProtocol) {
|
||||
postHog?.capture(event.eventName, properties: attachUserProperties(to: event.properties))
|
||||
}
|
||||
|
||||
func screen(_ event: AnalyticsScreenProtocol) {
|
||||
postHog?.screen(event.screenName.rawValue, properties: attachUserProperties(to: event.properties))
|
||||
}
|
||||
|
||||
func updateUserProperties(_ userProperties: AnalyticsEvent.UserProperties) {
|
||||
guard let pendingUserProperties = pendingUserProperties else {
|
||||
pendingUserProperties = userProperties
|
||||
return
|
||||
}
|
||||
|
||||
// Merge the updated user properties with the existing ones
|
||||
self.pendingUserProperties = AnalyticsEvent.UserProperties(ftueUseCaseSelection: userProperties.ftueUseCaseSelection ?? pendingUserProperties.ftueUseCaseSelection,
|
||||
numFavouriteRooms: userProperties.numFavouriteRooms ?? pendingUserProperties.numFavouriteRooms,
|
||||
numSpaces: userProperties.numSpaces ?? pendingUserProperties.numSpaces,
|
||||
allChatsActiveFilter: userProperties.allChatsActiveFilter ?? pendingUserProperties.allChatsActiveFilter)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Given a dictionary containing properties from an event, this method will return those properties
|
||||
/// with any pending user properties included under the `$set` key.
|
||||
/// - Parameter properties: A dictionary of properties from an event.
|
||||
/// - Returns: The `properties` dictionary with any user properties included.
|
||||
private func attachUserProperties(to properties: [String: Any]) -> [String: Any] {
|
||||
guard isRunning, let userProperties = pendingUserProperties else { return properties }
|
||||
|
||||
var properties = properties
|
||||
|
||||
// As user properties overwrite old ones via $set, compactMap the dictionary to avoid resetting any missing properties
|
||||
properties["$set"] = userProperties.properties.compactMapValues { $0 }
|
||||
pendingUserProperties = nil
|
||||
return properties
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user