Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into add-doctor-events-f…
Browse files Browse the repository at this point in the history
…or-flutter
  • Loading branch information
eliasyishak committed Oct 24, 2023
2 parents f143f2c + e3dd149 commit a66c60e
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 55 deletions.
4 changes: 2 additions & 2 deletions pkgs/unified_analytics/example/unified_analytics_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ void main() async {
final hotReloadEvent = Event.hotReloadTime(timeMs: runTime);

// Make a call to the [Analytics] api to send the data
await analytics.send(hotReloadEvent);
analytics.send(hotReloadEvent);

// Close the client connection on exit
analytics.close();
await analytics.close();
}
38 changes: 27 additions & 11 deletions pkgs/unified_analytics/lib/src/analytics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,13 @@ abstract class Analytics {
///
/// Prevents the tool from hanging when if there are still requests
/// that need to be sent off.
void close();
///
/// Providing [delayDuration] in milliseconds will allow the instance
/// to wait the provided time before closing the http connection. Keeping
/// the connection open for some time will allow any pending events that
/// are waiting to be sent to the Google Analytics server. Default value
/// of 250 ms applied.
Future<void> close({int delayDuration = kDelayDuration});

/// Method to fetch surveys from the endpoint [kContextualSurveyUrl].
///
Expand All @@ -248,7 +254,7 @@ abstract class Analytics {
/// ```dart
/// analytics.send(Event.memory(periodSec: 123));
/// ```
Future<Response>? send(Event event);
void send(Event event);

/// Pass a boolean to either enable or disable telemetry and make
/// the necessary changes in the persisted configuration file.
Expand Down Expand Up @@ -326,6 +332,10 @@ class AnalyticsImpl implements Analytics {
/// Telemetry suppression flag that is set via [Analytics.suppressTelemetry].
bool _telemetrySuppressed = false;

/// The list of futures that will contain all of the send events
/// from the [GAClient].
final _futures = <Future<Response>>[];

AnalyticsImpl({
required this.tool,
required Directory homeDirectory,
Expand Down Expand Up @@ -472,7 +482,13 @@ class AnalyticsImpl implements Analytics {
}

@override
void close() => _gaClient.close();
Future<void> close({int delayDuration = kDelayDuration}) async {
await Future.wait(_futures).timeout(
Duration(milliseconds: delayDuration),
onTimeout: () => [],
);
_gaClient.close();
}

@override
Future<List<Survey>> fetchAvailableSurveys() async {
Expand Down Expand Up @@ -543,8 +559,8 @@ class AnalyticsImpl implements Analytics {
LogFileStats? logFileStats() => _logHandler.logFileStats();

@override
Future<Response>? send(Event event) {
if (!okToSend) return null;
void send(Event event) {
if (!okToSend) return;

// Construct the body of the request
final body = generateRequestBody(
Expand All @@ -558,8 +574,9 @@ class AnalyticsImpl implements Analytics {

_logHandler.save(data: body);

// Pass to the google analytics client to send
return _gaClient.sendData(body);
final gaClientFuture = _gaClient.sendData(body);
_futures.add(gaClientFuture);
gaClientFuture.whenComplete(() => _futures.remove(gaClientFuture));
}

@override
Expand Down Expand Up @@ -670,8 +687,8 @@ class FakeAnalytics extends AnalyticsImpl {
);

@override
Future<Response>? send(Event event) {
if (!okToSend) return null;
void send(Event event) {
if (!okToSend) return;

// Construct the body of the request
final body = generateRequestBody(
Expand All @@ -688,7 +705,6 @@ class FakeAnalytics extends AnalyticsImpl {
// Using this list to validate that events are being sent
// for internal methods in the `Analytics` instance
sentEvents.add(event);
return _gaClient.sendData(body);
}
}

Expand Down Expand Up @@ -730,7 +746,7 @@ class NoOpAnalytics implements Analytics {
void clientShowedMessage() {}

@override
void close() {}
Future<void> close({int delayDuration = kDelayDuration}) async {}

@override
Future<List<Survey>> fetchAvailableSurveys() async => const <Survey>[];
Expand Down
52 changes: 41 additions & 11 deletions pkgs/unified_analytics/lib/src/asserts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,33 @@ void checkBody(Map<String, Object?> body) {
// alpha-numeric characters and underscores, and must start
// with an alphabetic character
if (eventName.length > 40) {
throw AnalyticsException('Limit event names to 40 chars or less');
throw AnalyticsException(
'Limit event names to 40 chars or less\n'
'Event name: "$eventName" is too long',
);
}
if (!alphaNumericPattern.hasMatch(eventName)) {
throw AnalyticsException(
'Event name can only have alphanumeric chars and underscores');
'Event name can only have alphanumeric chars and underscores\n'
'Event name: "$eventName" contains invalid characters',
);
}
if (!alphabeticPattern.hasMatch(eventName[0])) {
throw AnalyticsException('Event name first char must be alphabetic char');
throw AnalyticsException(
'Event name first char must be alphabetic char\n'
'Event name: "$eventName" must begin with a valid character',
);
}

final eventParams = eventMap['params'] as Map<String, Object?>;

// GA4 Limitation:
// Events can have a maximum of 25 parameters
if (eventParams.length > 25) {
throw AnalyticsException('Limit params for each event to less than 25');
throw AnalyticsException(
'Limit params for each event to less than 25\n'
'Event: "$eventName" has too many parameters',
);
}

// Loop through each of the event parameters
Expand All @@ -75,24 +86,34 @@ void checkBody(Map<String, Object?> body) {
value is double ||
value is bool)) {
throw AnalyticsException(
'Values for event params have to be String, int, double, or bool');
'Values for event params have to be String, int, double, or bool\n'
'Value for "$key" is not a valid type for event: "$eventName"',
);
}

// GA4 Limitation:
// Parameter names (including item parameters) must be 40 characters
// or fewer, may only contain alpha-numeric characters and underscores,
// and must start with an alphabetic character
if (key.length > 40) {
throw AnalyticsException('Limit event param names to 40 chars or less');
throw AnalyticsException(
'Limit event param names to 40 chars or less\n'
'The key: "$key" under the event: "$eventName" is too long',
);
}
if (!alphaNumericPattern.hasMatch(key)) {
throw AnalyticsException(
'Event param name can only have alphanumeric chars and underscores',
'Event param name can only have alphanumeric chars and underscores\n'
'The key: "$key" under the event: "$eventName" contains '
'invalid characters',
);
}
if (!alphabeticPattern.hasMatch(key[0])) {
throw AnalyticsException(
'Event param name first char must be alphabetic char');
'Event param name first char must be alphabetic char\n'
'The key: "$key" under the event: "$eventName" must begin '
'in a valid character',
);
}

// GA4 Limitation:
Expand All @@ -102,7 +123,9 @@ void checkBody(Map<String, Object?> body) {
value as String;
if (value.length > 100) {
throw AnalyticsException(
'Limit characters in event param value to 100 chars or less');
'Limit characters in event param value to 100 chars or less\n'
'Value for "$key" is too long, value="$value"',
);
}
}
}
Expand All @@ -122,15 +145,19 @@ void checkBody(Map<String, Object?> body) {
// GA4 Limitation:
// User property names must be 24 characters or fewer
if (key.length > 24) {
throw AnalyticsException('Limit user property names to 24 chars or less');
throw AnalyticsException('Limit user property names to 24 chars or less\n'
'The user property key: "$key" is too long');
}

// GA4 Limitation:
// User property values must be 36 characters or fewer
final userPropValue = value['value'];
if (userPropValue is String && userPropValue.length > 36) {
throw AnalyticsException(
'Limit user property values to 36 chars or less');
'Limit user property values to 36 chars or less\n'
'For the user property key "$key", the value "${value['value']}" '
'is too long',
);
}
}
}
Expand All @@ -139,4 +166,7 @@ class AnalyticsException implements Exception {
final String message;

AnalyticsException(this.message);

@override
String toString() => 'AnalyticsException: $message';
}
4 changes: 4 additions & 0 deletions pkgs/unified_analytics/lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ const String kContextualSurveyUrl =
/// will be located.
const String kDartToolDirectoryName = '.dart-tool';

/// The default time to wait before closing the http connection to allow for
/// pending events to be sent.
const int kDelayDuration = 250;

/// Name of the file where we persist dismissed survey ids.
const String kDismissedSurveyFileName =
'dart-flutter-telemetry-dismissed-surveys.json';
Expand Down
47 changes: 35 additions & 12 deletions pkgs/unified_analytics/test/asserts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ void main() {
'user_properties': <String, Object?>{}
};

final expectedErrorMessage = 'Limit event names to 40 chars or less';
final expectedErrorMessage = 'Limit event names to 40 chars or less\n'
'Event name: '
'"hot_reload_timehot_reload_timehot_reload_timehot_reload_time"'
' is too long';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -93,7 +96,8 @@ void main() {
};

final expectedErrorMessage =
'Event name can only have alphanumeric chars and underscores';
'Event name can only have alphanumeric chars and underscores\n'
'Event name: "hot_reload_time!!" contains invalid characters';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -114,7 +118,8 @@ void main() {
};

final expectedErrorMessage =
'Event name first char must be alphabetic char';
'Event name first char must be alphabetic char\n'
'Event name: "2hot_reload_time" must begin with a valid character';
expect(
() => checkBody(body),
throwsA(predicate(
Expand Down Expand Up @@ -142,7 +147,8 @@ void main() {
// Add the params to the first event in the body
((body['events'] as List).first as Map)['params'] = params;

final expectedErrorMessage = 'Limit params for each event to less than 25';
final expectedErrorMessage = 'Limit params for each event to less than 25\n'
'Event: "hot_reload_time" has too many parameters';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -168,7 +174,8 @@ void main() {
};

final expectedErrorMessage =
'Values for event params have to be String, int, double, or bool';
'Values for event params have to be String, int, double, or bool\n'
'Value for "count" is not a valid type for event: "hot_reload_time"';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -194,7 +201,8 @@ void main() {
};

final expectedErrorMessage =
'Values for event params have to be String, int, double, or bool';
'Values for event params have to be String, int, double, or bool\n'
'Value for "count" is not a valid type for event: "hot_reload_time"';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -214,7 +222,9 @@ void main() {
'user_properties': <String, Object?>{}
};

final expectedErrorMessage = 'Limit event param names to 40 chars or less';
final expectedErrorMessage = 'Limit event param names to 40 chars or less\n'
'The key: "time_mstime_mstime_mstime_mstime_mstime_ms" '
'under the event: "hot_reload_time" is too long';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -235,7 +245,10 @@ void main() {
};

final expectedErrorMessage =
'Event param name can only have alphanumeric chars and underscores';
'Event param name can only have alphanumeric chars and underscores\n'
'The key: "time_ns!" under the event: "hot_reload_time" contains '
'invalid characters';

expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -258,7 +271,9 @@ void main() {
};

final expectedErrorMessage =
'Event param name first char must be alphabetic char';
'Event param name first char must be alphabetic char\n'
'The key: "22time_ns" under the event: "hot_reload_time" must begin '
'in a valid character';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -285,7 +300,12 @@ void main() {
};

final expectedErrorMessage =
'Limit characters in event param value to 100 chars or less';
'Limit characters in event param value to 100 chars or less\n'
'Value for "time_ns" is too long, value="'
'dsfjlksdjfajlfdsfjlks'
'djfajlfdsfjlksdjfajlfdsfjlksdjfaj'
'lfdsfjlksdjfajlfdsfjlksdjfajlfdsf'
'jlksdjfajlfdsfjlksdjfajlf"';
expect(
() => checkBody(body),
throwsA(predicate(
Expand Down Expand Up @@ -332,7 +352,8 @@ void main() {
};

final expectedErrorMessage =
'Limit user property names to 24 chars or less';
'Limit user property names to 24 chars or less\n'
'The user property key: "testtesttesttesttesttesttest" is too long';
expect(
() => checkBody(body),
throwsA(predicate(
Expand All @@ -357,7 +378,9 @@ void main() {
};

final expectedErrorMessage =
'Limit user property values to 36 chars or less';
'Limit user property values to 36 chars or less\n'
'For the user property key "test", the value '
'"testtesttesttesttesttesttesttesttesttest" is too long';
expect(
() => checkBody(body),
throwsA(predicate(
Expand Down
Loading

0 comments on commit a66c60e

Please sign in to comment.