From bead112d0d3616799ce47ff217b172549d0ecbe3 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 29 Jul 2024 15:25:30 -0400 Subject: [PATCH] update analytics sending data on message send, instead of listening to onSync stream --- lib/pages/chat/chat.dart | 7 ++ lib/pages/chat_list/chat_list.dart | 1 - .../controllers/my_analytics_controller.dart | 83 +++++++------------ .../room_analytics_extension.dart | 12 +++ 4 files changed, 47 insertions(+), 56 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index f4ab25c6f..4cbcc8abf 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -638,6 +638,13 @@ class ChatController extends State .then( (String? msgEventId) async { // #Pangea + // There's a listen in my_analytics_controller that decides when to auto-update + // analytics based on when / how many messages the logged in user send. This + // stream sends the data for newly sent messages. + if (msgEventId != null) { + pangeaController.myAnalytics.setState(data: {'eventID': msgEventId}); + } + if (previousEdit != null) { pangeaEditingEvent = previousEdit; } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 8f5515fc4..77546522c 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -934,7 +934,6 @@ class ChatListController extends State // TODO try not to await so much GoogleAnalytics.analyticsUserUpdate(client.userID); await pangeaController.subscriptionController.initialize(); - await pangeaController.myAnalytics.initialize(); pangeaController.afterSyncAndFirstLoginInitialization(context); await pangeaController.inviteBotToExistingSpaces(); await pangeaController.setPangeaPushRules(); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index a13397e86..20c9a2803 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; @@ -17,7 +18,7 @@ import 'package:sentry_flutter/sentry_flutter.dart'; /// handles the processing of analytics for /// 1) messages sent by the user and /// 2) constructs used by the user, both in sending messages and doing practice activities -class MyAnalyticsController { +class MyAnalyticsController extends BaseController { late PangeaController _pangeaController; Timer? _updateTimer; @@ -33,27 +34,25 @@ class MyAnalyticsController { MyAnalyticsController(PangeaController pangeaController) { _pangeaController = pangeaController; - } - - /// adds the listener that handles when to run automatic updates - /// to analytics - either after a certain number of messages sent - /// received or after a certain amount of time [_timeSinceUpdate] without an update - Future initialize() async { - final lastUpdated = await _refreshAnalyticsIfOutdated(); - - // listen for new messages and updateAnalytics timer - // we are doing this in an attempt to update analytics when activitiy is low - // both in messages sent by this client and other clients that you're connected with - // doesn't account for messages sent by other clients that you're not connected with - _client.onSync.stream - .where((SyncUpdate update) => update.rooms?.join != null) - .listen((update) { - updateAnalyticsTimer(update, lastUpdated); + _refreshAnalyticsIfOutdated(); + + // Listen to a stream that provides the eventIDs + // of new messages sent by the logged in user + stateStream + .where((data) => data is Map && data.containsKey("eventID")) + .listen((data) { + updateAnalyticsTimer(data['eventID']); }); } /// If analytics haven't been updated in the last day, update them Future _refreshAnalyticsIfOutdated() async { + /// wait for the initial sync to finish, so the + /// timeline data from analytics rooms is accurate + if (_client.prevBatch == null) { + await _client.onSync.stream.first; + } + DateTime? lastUpdated = await _pangeaController.analytics.myAnalyticsLastUpdated(); final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate); @@ -68,44 +67,18 @@ class MyAnalyticsController { Client get _client => _pangeaController.matrixState.client; - /// Given an update from sync stream, check if the update contains - /// messages for which analytics will be saved. If so, reset the timer + /// Given an newly sent message, reset the timer /// and add the event ID to the cache of un-added event IDs - void updateAnalyticsTimer(SyncUpdate update, DateTime? lastUpdated) { - for (final entry in update.rooms!.join!.entries) { - final Room room = _client.getRoomById(entry.key)!; - - // get the new events in this sync that are messages - final List? events = entry.value.timeline?.events - ?.map((event) => Event.fromMatrixEvent(event, room)) - .where((event) => hasUserAnalyticsToCache(event, lastUpdated)) - .toList(); - - // add their event IDs to the cache of un-added event IDs - if (events == null || events.isEmpty) continue; - for (final event in events) { - addMessageSinceUpdate(event.eventId); - } - - // cancel the last timer that was set on message event and - // reset it to fire after _minutesBeforeUpdate minutes - _updateTimer?.cancel(); - _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () { - debugPrint("timer fired, updating analytics"); - updateAnalytics(); - }); - } - } - - // checks if event from sync update is a message that should have analytics - bool hasUserAnalyticsToCache(Event event, DateTime? lastUpdated) { - return event.senderId == _client.userID && - (lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) && - event.type == EventTypes.Message && - event.messageType == MessageTypes.Text && - !(event.eventId.contains("web") && - !(event.eventId.contains("android")) && - !(event.eventId.contains("iOS"))); + void updateAnalyticsTimer(String newEventId) { + addMessageSinceUpdate(newEventId); + + // cancel the last timer that was set on message event and + // reset it to fire after _minutesBeforeUpdate minutes + _updateTimer?.cancel(); + _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () { + debugPrint("timer fired, updating analytics"); + updateAnalytics(); + }); } // adds an event ID to the cache of un-added event IDs @@ -332,7 +305,7 @@ class MyAnalyticsController { // (allRecentMessages.isNotEmpty || recentActivityRecords.isNotEmpty), // ); - if (recentConstructUses.isNotEmpty) { + if (recentConstructUses.isNotEmpty || l2AnalyticsLastUpdated == null) { await analyticsRoom.sendConstructsEvent( recentConstructUses, ); diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index dabbd65bf..c09e01a65 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -195,6 +195,18 @@ extension AnalyticsRoomExtension on Room { Future sendConstructsEvent( List uses, ) async { + // It's possible that the user has no info to send yet, but to prevent trying + // to load the data over and over again, we'll sometimes send an empty event to + // indicate that we have checked and there was no data. + if (uses.isEmpty) { + final constructsModel = ConstructAnalyticsModel(uses: []); + await sendEvent( + constructsModel.toJson(), + type: PangeaEventTypes.construct, + ); + return; + } + // these events can get big, so we chunk them to prevent hitting the max event size. // go through each of the uses being sent and add them to the current chunk until // the size (in bytes) of the current chunk is greater than the max event size, then