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(
(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;
}

@ -934,7 +934,6 @@ class ChatListController extends State<ChatList>
// 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();

@ -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<void> 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<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 =
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<Event>? 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,
);

@ -195,6 +195,18 @@ extension AnalyticsRoomExtension on Room {
Future<void> sendConstructsEvent(
List<OneConstructUse> 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

Loading…
Cancel
Save