diff --git a/analysis_options.yaml b/analysis_options.yaml index 545d5cc..a0480da 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,6 +2,6 @@ analyzer: strong-mode: true linter: rules: - #- annotate_overrides + - annotate_overrides - empty_constructor_bodies - empty_statements diff --git a/changelog.md b/changelog.md index 2a74305..ac47b61 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ - expose the `Analytics.applicationName` and `Analytics.applicationVersion` properties - make it easier for clients to extend the `AnalyticsIO` class +- allow for custom parameters when sending a screenView ## 3.1.1 - make Analytics.clientId available immediately diff --git a/lib/src/usage_impl.dart b/lib/src/usage_impl.dart index e8e10d2..c0b9ae5 100644 --- a/lib/src/usage_impl.dart +++ b/lib/src/usage_impl.dart @@ -60,8 +60,11 @@ class AnalyticsImpl implements Analytics { static const String _defaultAnalyticsUrl = 'https://www.google-analytics.com/collect'; + @override final String trackingId; + @override final String applicationName; + @override final String applicationVersion; final PersistentProperties properties; @@ -72,6 +75,7 @@ class AnalyticsImpl implements Analytics { final List _futures = []; + @override AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut; String _url; @@ -91,6 +95,7 @@ class AnalyticsImpl implements Analytics { bool _firstRun; + @override bool get firstRun { if (_firstRun == null) { _firstRun = properties['firstRun'] == null; @@ -103,9 +108,7 @@ class AnalyticsImpl implements Analytics { return _firstRun; } - /** - * Will analytics data be sent? - */ + @override bool get enabled { bool optIn = analyticsOpt == AnalyticsOpt.optIn; return optIn @@ -113,13 +116,12 @@ class AnalyticsImpl implements Analytics { : properties['enabled'] != false; } - /** - * Enable or disable sending of analytics data. - */ + @override set enabled(bool value) { properties['enabled'] = value; } + @override Future sendScreenView(String viewName, {Map parameters}) { Map args = {'cd': viewName}; if (parameters != null) { @@ -128,8 +130,9 @@ class AnalyticsImpl implements Analytics { return _sendPayload('screenview', args); } - Future sendEvent(String category, String action, {String label, int value, - Map parameters}) { + @override + Future sendEvent(String category, String action, + {String label, int value, Map parameters}) { Map args = {'ec': category, 'ea': action}; if (label != null) args['el'] = label; if (value != null) args['ev'] = value; @@ -139,11 +142,13 @@ class AnalyticsImpl implements Analytics { return _sendPayload('event', args); } + @override Future sendSocial(String network, String action, String target) { Map args = {'sn': network, 'sa': action, 'st': target}; return _sendPayload('social', args); } + @override Future sendTiming(String variableName, int time, {String category, String label}) { Map args = {'utv': variableName, 'utt': time}; @@ -152,12 +157,14 @@ class AnalyticsImpl implements Analytics { return _sendPayload('timing', args); } + @override AnalyticsTimer startTimer(String variableName, {String category, String label}) { return new AnalyticsTimer(this, variableName, category: category, label: label); } + @override Future sendException(String description, {bool fatal}) { // We trim exceptions to a max length; google analytics will apply it's own // truncation, likely around 150 chars or so. @@ -181,8 +188,10 @@ class AnalyticsImpl implements Analytics { return _sendPayload('exception', args); } + @override dynamic getSessionValue(String param) => _variableMap[param]; + @override void setSessionValue(String param, dynamic value) { if (value == null) { _variableMap.remove(param); @@ -191,8 +200,10 @@ class AnalyticsImpl implements Analytics { } } + @override Stream> get onSend => _sendController.stream; + @override Future waitForLastPing({Duration timeout}) { Future f = Future.wait(_futures).catchError((e) => null); diff --git a/lib/src/usage_impl_html.dart b/lib/src/usage_impl_html.dart index 6861db7..0072dc3 100644 --- a/lib/src/usage_impl_html.dart +++ b/lib/src/usage_impl_html.dart @@ -35,6 +35,7 @@ class HtmlPostHandler extends PostHandler { HtmlPostHandler({Function this.mockRequestor}); + @override Future sendPost(String url, Map parameters) { int viewportWidth = document.documentElement.clientWidth; int viewportHeight = document.documentElement.clientHeight; @@ -59,8 +60,10 @@ class HtmlPersistentProperties extends PersistentProperties { _map = JSON.decode(str); } + @override dynamic operator [](String key) => _map[key]; + @override void operator []=(String key, dynamic value) { if (value == null) { _map.remove(key); @@ -71,5 +74,6 @@ class HtmlPersistentProperties extends PersistentProperties { window.localStorage[name] = JSON.encode(_map); } + @override void syncSettings() {} } diff --git a/lib/src/usage_impl_io.dart b/lib/src/usage_impl_io.dart index ad87e91..9ad366a 100644 --- a/lib/src/usage_impl_io.dart +++ b/lib/src/usage_impl_io.dart @@ -79,6 +79,7 @@ class IOPostHandler extends PostHandler { IOPostHandler({HttpClient this.mockClient}) : _userAgent = _createUserAgent(); + @override Future sendPost(String url, Map parameters) async { String data = postEncode(parameters); @@ -120,8 +121,10 @@ class IOPersistentProperties extends PersistentProperties { syncSettings(); } + @override dynamic operator [](String key) => _map[key]; + @override void operator []=(String key, dynamic value) { if (value == null && !_map.containsKey(key)) return; if (_map[key] == value) return; @@ -137,6 +140,7 @@ class IOPersistentProperties extends PersistentProperties { } catch (_) {} } + @override void syncSettings() { try { String contents = _file.readAsStringSync(); diff --git a/lib/usage.dart b/lib/usage.dart index d516172..52e9f46 100644 --- a/lib/usage.dart +++ b/lib/usage.dart @@ -83,7 +83,7 @@ abstract class Analytics { /** * Sends a screen view hit to Google Analytics. - * + * * [parameters] can be any analytics key/value pair. Useful * for custom dimensions, etc. */ @@ -92,12 +92,12 @@ abstract class Analytics { /** * Sends an Event hit to Google Analytics. [label] specifies the event label. * [value] specifies the event value. Values must be non-negative. - * + * * [parameters] can be any analytics key/value pair. Useful * for custom dimensions, etc. */ - Future sendEvent(String category, String action, {String label, int value, - Map parameters}); + Future sendEvent(String category, String action, + {String label, int value, Map parameters}); /** * Sends a Social hit to Google Analytics. [network] specifies the social @@ -225,8 +225,11 @@ class AnalyticsTimer { * stand-in for that will never ping the GA server, or as a mock in test code. */ class AnalyticsMock implements Analytics { + @override String get trackingId => 'UA-0'; + @override String get applicationName => 'mock-app'; + @override String get applicationVersion => '1.0.0'; final bool logCalls; @@ -243,35 +246,40 @@ class AnalyticsMock implements Analytics { */ AnalyticsMock([this.logCalls = false]); + @override bool get firstRun => false; + @override AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut; + @override bool enabled = true; @override String get clientId => '00000000-0000-4000-0000-000000000000'; + @override Future sendScreenView(String viewName, {Map parameters}) { parameters ??= {}; parameters['viewName'] = viewName; return _log('screenView', parameters); } - Future sendEvent(String category, String action, {String label, int value, - Map parameters}) { + @override + Future sendEvent(String category, String action, + {String label, int value, Map parameters}) { parameters ??= {}; - return _log('event', { - 'category': category, - 'action': action, - 'label': label, - 'value': value - }..addAll(parameters)); + return _log( + 'event', + {'category': category, 'action': action, 'label': label, 'value': value} + ..addAll(parameters)); } + @override Future sendSocial(String network, String action, String target) => _log('social', {'network': network, 'action': action, 'target': target}); + @override Future sendTiming(String variableName, int time, {String category, String label}) { return _log('timing', { @@ -282,21 +290,27 @@ class AnalyticsMock implements Analytics { }); } + @override AnalyticsTimer startTimer(String variableName, {String category, String label}) { return new AnalyticsTimer(this, variableName, category: category, label: label); } + @override Future sendException(String description, {bool fatal}) => _log('exception', {'description': description, 'fatal': fatal}); + @override dynamic getSessionValue(String param) => null; + @override void setSessionValue(String param, dynamic value) {} + @override Stream> get onSend => _sendController.stream; + @override Future waitForLastPing({Duration timeout}) => new Future.value(); Future _log(String hitType, Map m) { diff --git a/readme.md b/readme.md index c9a92c7..6d216e8 100644 --- a/readme.md +++ b/readme.md @@ -112,9 +112,7 @@ For more information, please see the Google Analytics Measurement Protocol ## Contributing -Run the tests with `pub run test`. - -Analyze the code with `dartanalyzer lib/*.dart test/*.dart`. +Test can be run using `pub run test`. ## Issues and bugs diff --git a/test/src/common.dart b/test/src/common.dart index 4ce7b17..2ace7eb 100644 --- a/test/src/common.dart +++ b/test/src/common.dart @@ -34,18 +34,22 @@ class MockProperties extends PersistentProperties { if (props != null) this.props.addAll(props); } + @override dynamic operator [](String key) => props[key]; + @override void operator []=(String key, dynamic value) { props[key] = value; } + @override void syncSettings() {} } class MockPostHandler extends PostHandler { List> sentValues = []; + @override Future sendPost(String url, Map parameters) { sentValues.add(parameters); diff --git a/test/usage_impl_io_test.dart b/test/usage_impl_io_test.dart index bce9d03..8889077 100644 --- a/test/usage_impl_io_test.dart +++ b/test/usage_impl_io_test.dart @@ -52,39 +52,52 @@ void defineTests() { } class MockHttpClient implements HttpClient { + @override String userAgent; int sendCount = 0; int writeCount = 0; bool closed = false; + + @override Future postUrl(Uri url) { return new Future.value(new MockHttpClientRequest(this)); } + @override noSuchMethod(Invocation invocation) {} } class MockHttpClientRequest implements HttpClientRequest { final MockHttpClient client; + MockHttpClientRequest(this.client); + + @override void write(Object obj) { client.writeCount++; } + @override Future close() { client.closed = true; return new Future.value(new MockHttpClientResponse(client)); } + @override noSuchMethod(Invocation invocation) {} } class MockHttpClientResponse implements HttpClientResponse { final MockHttpClient client; + MockHttpClientResponse(this.client); + + @override Future/**/ drain/**/([/*=E*/ futureValue]) { client.sendCount++; return new Future.value(); } + @override noSuchMethod(Invocation invocation) {} } diff --git a/test/usage_test.dart b/test/usage_test.dart index 9d2a4e0..37500a9 100644 --- a/test/usage_test.dart +++ b/test/usage_test.dart @@ -16,7 +16,8 @@ void defineTests() { mock.sendScreenView('main'); mock.sendScreenView('withParameters', parameters: {'cd1': 'custom'}); mock.sendEvent('files', 'save'); - mock.sendEvent('eventWithParameters', 'save', parameters: {'cd1': 'custom'}); + mock.sendEvent('eventWithParameters', 'save', + parameters: {'cd1': 'custom'}); mock.sendSocial('g+', 'plus', 'userid'); mock.sendTiming('compile', 123); mock.startTimer('compile').finish();