mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-08-26 10:38:13 +08:00
Analytics: Send to backend when possible
This commit is contained in:
@ -6,7 +6,10 @@ import 'package:uuid/uuid.dart';
|
|||||||
|
|
||||||
import 'package:gitjournal/features.dart';
|
import 'package:gitjournal/features.dart';
|
||||||
import 'package:gitjournal/logger/logger.dart';
|
import 'package:gitjournal/logger/logger.dart';
|
||||||
|
import 'device_info.dart';
|
||||||
import 'generated/analytics.pb.dart' as pb;
|
import 'generated/analytics.pb.dart' as pb;
|
||||||
|
import 'network.dart';
|
||||||
|
import 'package_info.dart';
|
||||||
import 'storage.dart';
|
import 'storage.dart';
|
||||||
|
|
||||||
enum Event {
|
enum Event {
|
||||||
@ -114,6 +117,8 @@ class Analytics {
|
|||||||
|
|
||||||
await storage.logEvent(_buildEvent(name, parameters));
|
await storage.logEvent(_buildEvent(name, parameters));
|
||||||
analyticsCallback(name, parameters);
|
analyticsCallback(name, parameters);
|
||||||
|
|
||||||
|
await _sendAnalytics();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setCurrentScreen({required String screenName}) async {
|
Future<void> setCurrentScreen({required String screenName}) async {
|
||||||
@ -138,6 +143,39 @@ class Analytics {
|
|||||||
userFirstTouchTimestamp: null,
|
userFirstTouchTimestamp: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _sendAnalytics() async {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldestEvent = await storage.oldestEvent();
|
||||||
|
if (DateTime.now().difference(oldestEvent) < const Duration(hours: 1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await storage.fetchAll((events) async {
|
||||||
|
var msg = pb.AnalyticsMessage(
|
||||||
|
appId: 'io.gitjournal',
|
||||||
|
deviceInfo: await buildDeviceInfo(),
|
||||||
|
packageInfo: await buildPackageInfo(),
|
||||||
|
events: events,
|
||||||
|
);
|
||||||
|
Log.i("Sending ${events.length} events");
|
||||||
|
var result = await sendAnalytics(msg);
|
||||||
|
if (result.isFailure) {
|
||||||
|
Log.e(
|
||||||
|
"Failed to send Analytics",
|
||||||
|
ex: result.error,
|
||||||
|
stacktrace: result.stackTrace,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("Sent ${events.length} Analytics Events");
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void logEvent(Event event, {Map<String, String> parameters = const {}}) {
|
void logEvent(Event event, {Map<String, String> parameters = const {}}) {
|
||||||
@ -149,3 +187,7 @@ String _eventToString(Event e) {
|
|||||||
var str = e.toString().substring('Event.'.length);
|
var str = e.toString().substring('Event.'.length);
|
||||||
return ReCase(str).snakeCase;
|
return ReCase(str).snakeCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Discard the old analytics, if there are way too many!
|
||||||
|
// TODO: Take network connectivity into account
|
||||||
|
// TODO: Take connection type (wifi vs mobile) into account
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:dart_git/utils/result.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
|
|
||||||
import 'package:gitjournal/analytics/generated/analytics.pbgrpc.dart';
|
import 'package:gitjournal/analytics/generated/analytics.pbgrpc.dart';
|
||||||
import 'generated/analytics.pb.dart' as pb;
|
import 'generated/analytics.pb.dart' as pb;
|
||||||
|
|
||||||
Future<void> main(List<String> args) async {
|
const _url = 'analyticsbackend-wetu2tkdpq-ew.a.run.app';
|
||||||
|
const _port = 444;
|
||||||
|
const _timeout = Duration(seconds: 30);
|
||||||
|
|
||||||
|
Future<Result<void>> sendAnalytics(pb.AnalyticsMessage msg) async {
|
||||||
final channel = ClientChannel(
|
final channel = ClientChannel(
|
||||||
'analyticsbackend-wetu2tkdpq-ew.a.run.app',
|
_url,
|
||||||
port: 443,
|
port: _port,
|
||||||
options: ChannelOptions(
|
options: ChannelOptions(
|
||||||
// credentials: const ChannelCredentials.insecure(),
|
// credentials: const ChannelCredentials.insecure(),
|
||||||
credentials: const ChannelCredentials.secure(),
|
credentials: const ChannelCredentials.secure(),
|
||||||
@ -18,38 +22,21 @@ Future<void> main(List<String> args) async {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final stub = AnalyticsServiceClient(channel);
|
final client = AnalyticsServiceClient(channel);
|
||||||
try {
|
try {
|
||||||
var dt = DateTime.now().add(const Duration(days: -1));
|
var call = client.sendData(
|
||||||
var ev = pb.Event(
|
msg,
|
||||||
name: 'test',
|
options: CallOptions(
|
||||||
date: Int64(dt.millisecondsSinceEpoch ~/ 1000),
|
timeout: _timeout,
|
||||||
params: {'a': 'hello'},
|
compression: const GzipCodec(),
|
||||||
pseudoId: 'id',
|
),
|
||||||
userProperties: {'b': 'c'},
|
|
||||||
sessionID: 'session',
|
|
||||||
);
|
);
|
||||||
|
await call;
|
||||||
var request = AnalyticsMessage(
|
} on Exception catch (e, st) {
|
||||||
appId: 'io.gitjournal',
|
await channel.shutdown();
|
||||||
events: [ev],
|
return Result.fail(e, st);
|
||||||
);
|
|
||||||
print("Sending ${request.toDebugString()}");
|
|
||||||
var call = stub.sendData(
|
|
||||||
request,
|
|
||||||
options: CallOptions(timeout: const Duration(seconds: 10)),
|
|
||||||
);
|
|
||||||
call.headers.then((headers) {
|
|
||||||
print('Received header metadata: $headers');
|
|
||||||
});
|
|
||||||
call.trailers.then((trailers) {
|
|
||||||
print('Received trailer metadata: $trailers');
|
|
||||||
});
|
|
||||||
var response = await call;
|
|
||||||
print('Greeter client received: $response');
|
|
||||||
} catch (e) {
|
|
||||||
print('Caught error: $e');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await channel.shutdown();
|
await channel.shutdown();
|
||||||
|
return Result(null);
|
||||||
}
|
}
|
||||||
|
14
lib/analytics/package_info.dart
Normal file
14
lib/analytics/package_info.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
|
import 'generated/analytics.pb.dart' as pb;
|
||||||
|
|
||||||
|
Future<pb.PackageInfo> buildPackageInfo() async {
|
||||||
|
var info = await PackageInfo.fromPlatform();
|
||||||
|
return pb.PackageInfo(
|
||||||
|
appName: info.appName,
|
||||||
|
packageName: info.packageName,
|
||||||
|
version: info.version,
|
||||||
|
buildNumber: info.buildNumber,
|
||||||
|
buildSignature: info.buildSignature,
|
||||||
|
);
|
||||||
|
}
|
@ -15,7 +15,7 @@ import 'package:quick_actions/quick_actions.dart';
|
|||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:universal_io/io.dart' show Platform;
|
import 'package:universal_io/io.dart' show Directory, Platform;
|
||||||
|
|
||||||
import 'package:gitjournal/analytics/analytics.dart';
|
import 'package:gitjournal/analytics/analytics.dart';
|
||||||
import 'package:gitjournal/analytics/route_observer.dart';
|
import 'package:gitjournal/analytics/route_observer.dart';
|
||||||
@ -131,8 +131,10 @@ class JournalApp extends StatefulWidget {
|
|||||||
|
|
||||||
var supportDir = await getApplicationSupportDirectory();
|
var supportDir = await getApplicationSupportDirectory();
|
||||||
var analyticsStorage = p.join(supportDir.path, 'analytics');
|
var analyticsStorage = p.join(supportDir.path, 'analytics');
|
||||||
|
await Directory(analyticsStorage).create(recursive: true);
|
||||||
|
|
||||||
Log.d("Analytics Collection: $enabled");
|
Log.d("Analytics Collection: $enabled");
|
||||||
|
Log.d("Analytics Storage: $analyticsStorage");
|
||||||
var analytics = Analytics.init(
|
var analytics = Analytics.init(
|
||||||
enable: enabled,
|
enable: enabled,
|
||||||
pref: pref,
|
pref: pref,
|
||||||
|
Reference in New Issue
Block a user