update analytics sending data on message send, instead of listening to onSync stream

pull/1384/head
ggurdin 1 year ago
parent c5187c7639
commit bead112d0d
No known key found for this signature in database
GPG Key ID: A01CB41737CBB478

@ -638,6 +638,13 @@ class ChatController extends State<ChatPageWithRoom>
.then( .then(
(String? msgEventId) async { (String? msgEventId) async {
// #Pangea // #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) { if (previousEdit != null) {
pangeaEditingEvent = previousEdit; pangeaEditingEvent = previousEdit;
} }

@ -934,7 +934,6 @@ class ChatListController extends State<ChatList>
// TODO try not to await so much // TODO try not to await so much
GoogleAnalytics.analyticsUserUpdate(client.userID); GoogleAnalytics.analyticsUserUpdate(client.userID);
await pangeaController.subscriptionController.initialize(); await pangeaController.subscriptionController.initialize();
await pangeaController.myAnalytics.initialize();
pangeaController.afterSyncAndFirstLoginInitialization(context); pangeaController.afterSyncAndFirstLoginInitialization(context);
await pangeaController.inviteBotToExistingSpaces(); await pangeaController.inviteBotToExistingSpaces();
await pangeaController.setPangeaPushRules(); await pangeaController.setPangeaPushRules();

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.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/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_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 /// handles the processing of analytics for
/// 1) messages sent by the user and /// 1) messages sent by the user and
/// 2) constructs used by the user, both in sending messages and doing practice activities /// 2) constructs used by the user, both in sending messages and doing practice activities
class MyAnalyticsController { class MyAnalyticsController extends BaseController {
late PangeaController _pangeaController; late PangeaController _pangeaController;
Timer? _updateTimer; Timer? _updateTimer;
@ -33,27 +34,25 @@ class MyAnalyticsController {
MyAnalyticsController(PangeaController pangeaController) { MyAnalyticsController(PangeaController pangeaController) {
_pangeaController = pangeaController; _pangeaController = pangeaController;
} _refreshAnalyticsIfOutdated();
/// adds the listener that handles when to run automatic updates // Listen to a stream that provides the eventIDs
/// to analytics - either after a certain number of messages sent // of new messages sent by the logged in user
/// received or after a certain amount of time [_timeSinceUpdate] without an update stateStream
Future<void> initialize() async { .where((data) => data is Map && data.containsKey("eventID"))
final lastUpdated = await _refreshAnalyticsIfOutdated(); .listen((data) {
updateAnalyticsTimer(data['eventID']);
// 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);
}); });
} }
/// If analytics haven't been updated in the last day, update them /// If analytics haven't been updated in the last day, update them
Future<DateTime?> _refreshAnalyticsIfOutdated() async { Future<DateTime?> _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 = DateTime? lastUpdated =
await _pangeaController.analytics.myAnalyticsLastUpdated(); await _pangeaController.analytics.myAnalyticsLastUpdated();
final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate); final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate);
@ -68,44 +67,18 @@ class MyAnalyticsController {
Client get _client => _pangeaController.matrixState.client; Client get _client => _pangeaController.matrixState.client;
/// Given an update from sync stream, check if the update contains /// Given an newly sent message, reset the timer
/// messages for which analytics will be saved. If so, reset the timer
/// and add the event ID to the cache of un-added event IDs /// and add the event ID to the cache of un-added event IDs
void updateAnalyticsTimer(SyncUpdate update, DateTime? lastUpdated) { void updateAnalyticsTimer(String newEventId) {
for (final entry in update.rooms!.join!.entries) { addMessageSinceUpdate(newEventId);
final Room room = _client.getRoomById(entry.key)!;
// cancel the last timer that was set on message event and
// get the new events in this sync that are messages // reset it to fire after _minutesBeforeUpdate minutes
final List<Event>? events = entry.value.timeline?.events _updateTimer?.cancel();
?.map((event) => Event.fromMatrixEvent(event, room)) _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () {
.where((event) => hasUserAnalyticsToCache(event, lastUpdated)) debugPrint("timer fired, updating analytics");
.toList(); updateAnalytics();
});
// 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")));
} }
// adds an event ID to the cache of un-added event IDs // adds an event ID to the cache of un-added event IDs
@ -332,7 +305,7 @@ class MyAnalyticsController {
// (allRecentMessages.isNotEmpty || recentActivityRecords.isNotEmpty), // (allRecentMessages.isNotEmpty || recentActivityRecords.isNotEmpty),
// ); // );
if (recentConstructUses.isNotEmpty) { if (recentConstructUses.isNotEmpty || l2AnalyticsLastUpdated == null) {
await analyticsRoom.sendConstructsEvent( await analyticsRoom.sendConstructsEvent(
recentConstructUses, recentConstructUses,
); );

@ -195,6 +195,18 @@ extension AnalyticsRoomExtension on Room {
Future<void> sendConstructsEvent( Future<void> sendConstructsEvent(
List<OneConstructUse> uses, List<OneConstructUse> uses,
) async { ) 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. // 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 // 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 // the size (in bytes) of the current chunk is greater than the max event size, then

Loading…
Cancel
Save