Skip to content

Commit

Permalink
Merge pull request #75 from dart-lang/refactor_api
Browse files Browse the repository at this point in the history
refactor api
  • Loading branch information
devoncarew committed Apr 26, 2016
2 parents 2705d63 + 2d7f5a6 commit 775bffd
Show file tree
Hide file tree
Showing 17 changed files with 113 additions and 73 deletions.
1 change: 1 addition & 0 deletions .analysis_options
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ analyzer:
enableConditionalDirectives: true
exclude:
- example/flutter_example/**
- lib/src/usage_impl_flutter.dart
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: dart
dart:
- stable
- dev
sudo: false

Expand Down
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 2.2.0
- added `Analytics.firstRun`
- added `Analytics.enabled`
- removed `Analytics.optIn`

## 2.1.0
- added `Analytics.getSessionValue()`
- added `Analytics.onSend`
Expand Down
1 change: 0 additions & 1 deletion example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ Future<Analytics> getAnalytics() async {
if (_analytics == null || _lastUa != _ua()) {
_lastUa = _ua();
_analytics = await Analytics.create(_lastUa, 'Test app', '1.0');
_analytics.optIn = true;
_analytics.sendScreenView(window.location.pathname);
}

Expand Down
6 changes: 3 additions & 3 deletions example/flutter_example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class _FlutterDemoState extends State<FlutterDemo> {

void _handleOptIn(bool value) {
setState(() {
config.ga.optIn = value;
config.ga.enabled = value;
});
}

Expand All @@ -54,9 +54,9 @@ class _FlutterDemoState extends State<FlutterDemo> {
child: new Text("Button pressed $_times times.")
),
new ListItem(
onTap: () => _handleOptIn(!config.ga.optIn),
onTap: () => _handleOptIn(!config.ga.enabled),
leading: new Checkbox(
value: config.ga.optIn,
value: config.ga.enabled,
onChanged: _handleOptIn
),
title: new Text("Opt in to analytics")
Expand Down
1 change: 0 additions & 1 deletion example/ga.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ Future main(List args) async {
String ua = args.isEmpty ? DEFAULT_UA : args.first;

Analytics ga = await Analytics.create(ua, 'ga_test', '1.0');
ga.optIn = true;

ga.sendScreenView('home').then((_) {
return ga.sendScreenView('files');
Expand Down
47 changes: 30 additions & 17 deletions lib/src/usage_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library usage_impl;

import 'dart:async';
import 'dart:math' as math;

Expand Down Expand Up @@ -63,7 +61,9 @@ class ThrottlingBucket {
class AnalyticsImpl implements Analytics {
static const String _defaultAnalyticsUrl = 'https://www.google-analytics.com/collect';

/// Tracking ID / Property ID.
/**
* Tracking ID / Property ID.
*/
final String trackingId;

final PersistentProperties properties;
Expand All @@ -74,6 +74,8 @@ class AnalyticsImpl implements Analytics {

final List<Future> _futures = [];

AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;

String _url;

StreamController<Map<String, dynamic>> _sendController = new StreamController.broadcast(sync: true);
Expand All @@ -94,39 +96,50 @@ class AnalyticsImpl implements Analytics {
_url = analyticsUrl ?? _defaultAnalyticsUrl;
}

bool get optIn => properties['optIn'] == true;
bool _firstRun;

set optIn(bool value) {
properties['optIn'] = value;
bool get firstRun {
if (_firstRun == null) {
_firstRun = properties['firstRun'] == null;
properties['firstRun'] = false;
}

return _firstRun;
}

/**
* Will analytics data be sent?
*/
bool get enabled {
bool optIn = analyticsOpt == AnalyticsOpt.optIn;
return optIn ? properties['enabled'] == true : properties['enabled'] != false;
}

bool get hasSetOptIn => properties['optIn'] != null;
/**
* Enable or disable sending of analytics data.
*/
set enabled(bool value) {
properties['enabled'] = value;
}

Future sendScreenView(String viewName) {
Map<String, dynamic> args = {'cd': viewName};
return _sendPayload('screenview', args);
}

Future sendEvent(String category, String action, {String label, int value}) {
if (!optIn) return new Future.value();

Map<String, dynamic> args = {'ec': category, 'ea': action};
if (label != null) args['el'] = label;
if (value != null) args['ev'] = value;
return _sendPayload('event', args);
}

Future sendSocial(String network, String action, String target) {
if (!optIn) return new Future.value();

Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target};
return _sendPayload('social', args);
}

Future sendTiming(String variableName, int time, {String category,
String label}) {
if (!optIn) return new Future.value();

Future sendTiming(String variableName, int time, {String category, String label}) {
Map<String, dynamic> args = {'utv': variableName, 'utt': time};
if (label != null) args['utl'] = label;
if (category != null) args['utc'] = category;
Expand All @@ -139,8 +152,6 @@ class AnalyticsImpl implements Analytics {
}

Future sendException(String description, {bool fatal}) {
if (!optIn) return new Future.value();

// In order to ensure that the client of this API is not sending any PII
// data, we strip out any stack trace that may reference a path on the
// user's drive (file:/...).
Expand Down Expand Up @@ -206,6 +217,8 @@ class AnalyticsImpl implements Analytics {
* 'transaction', 'item', 'social', 'exception', and 'timing'.
*/
Future _sendPayload(String hitType, Map<String, dynamic> args) {
if (!enabled) return new Future.value();

if (_bucket.removeDrop()) {
_initClientId();

Expand Down
2 changes: 0 additions & 2 deletions lib/src/usage_impl_default.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library usage_impl;

import 'dart:async';

import '../usage.dart';
Expand Down
2 changes: 0 additions & 2 deletions lib/src/usage_impl_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library usage_impl_io;

import 'dart:async';
import 'dart:convert' show JSON;
import 'dart:io';
Expand Down
2 changes: 0 additions & 2 deletions lib/src/usage_impl_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library usage_impl_html;

import 'dart:async';
import 'dart:convert' show JSON;
import 'dart:html';
Expand Down
2 changes: 0 additions & 2 deletions lib/src/usage_impl_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library usage_impl_io;

import 'dart:async';
import 'dart:convert' show JSON;
import 'dart:io';
Expand Down
4 changes: 3 additions & 1 deletion lib/src/uuid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// A UUID generator library.
/**
* A UUID generator library.
*/
library usage.uuid;

import 'dart:math' show Random;
Expand Down
53 changes: 42 additions & 11 deletions lib/usage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,39 @@ abstract class Analytics {
String applicationName,
String applicationVersion, {
String analyticsUrl
}) => impl.createAnalytics(trackingId, applicationName, applicationVersion,
analyticsUrl: analyticsUrl);
}) {
return impl.createAnalytics(
trackingId,
applicationName,
applicationVersion,
analyticsUrl: analyticsUrl
);
}

/**
* Tracking ID / Property ID.
*/
String get trackingId;

/**
* Whether the user has opt-ed in to additional analytics.
* Is this the first time the tool has run?
*/
bool get optIn;
bool get firstRun;

set optIn(bool value);
/**
* Whether the [Analytics] instance is configured in an opt-in or opt-out manner.
*/
AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;

/**
* Whether the [optIn] value has been explicitly set (either `true` or
* `false`).
* Will analytics data be sent.
*/
bool get hasSetOptIn;
bool get enabled;

/**
* Enable or disable sending of analytics data.
*/
set enabled(bool value);

/**
* Sends a screen view hit to Google Analytics.
Expand Down Expand Up @@ -151,6 +164,18 @@ abstract class Analytics {
Future waitForLastPing({Duration timeout});
}

enum AnalyticsOpt {
/**
* Users must opt-in before any analytics data is sent.
*/
optIn,

/**
* Users must opt-out for analytics data to not be sent.
*/
optOut
}

/**
* An object, returned by [Analytics.startTimer], that is used to measure an
* asynchronous process.
Expand Down Expand Up @@ -198,10 +223,10 @@ class AnalyticsMock implements Analytics {
String get trackingId => 'UA-0';
final bool logCalls;

bool optIn = false;
bool hasSetOptIn = true;

/// Events are never added to this controller for the mock implementation.
/**
* Events are never added to this controller for the mock implementation.
*/
StreamController<Map<String, dynamic>> _sendController = new StreamController.broadcast();

/**
Expand All @@ -210,6 +235,12 @@ class AnalyticsMock implements Analytics {
*/
AnalyticsMock([this.logCalls = false]);

bool get firstRun => false;

AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;

bool enabled = true;

Future sendScreenView(String viewName) =>
_log('screenView', {'viewName': viewName});

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.

name: usage
version: 2.1.0
version: 2.2.0
description: A Google Analytics wrapper for both command-line, web, and Flutter apps.
homepage: https://github.com/dart-lang/usage
author: Dart Team <misc@dartlang.org>
Expand Down
23 changes: 9 additions & 14 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ And call some analytics code:
final String UA = ...;
Analytics ga = await Analytics.create(UA, 'ga_test', '1.0');
ga.optIn = true;
ga.sendScreenView('home');
ga.sendException('foo exception');
Expand All @@ -61,21 +60,17 @@ ga.sendTiming('readTime', 20);

## When do we send analytics data?

We use an opt-in method for sending analytics information. There are essentially
three states for when we send information:
You can use this library in an opt-in manner or an opt-out one. It defaults to
opt-out - data will be sent to Google Analytics unless the user explicitly
opts-out. The mode can be adjusted by changing the value of the
`Analytics.analyticsOpt` field.

*Sending screen views* If the user has not opted in, the library will only send
information about screen views. This allows tools to do things like version
checks, but does not send any additional information.
*Opt-out* In opt-out mode, if the user does not explicitly opt-out of collecting
analytics (`Analytics.enabled = false`), the usage library will send usage data.

*Opt-in* If the user opts-in to analytics collection the library sends all
requested analytics info. This includes screen views, events, timing
information, and exceptions.

*Opt-ing out* In order to not send analytics information, either do not call the
analytics methods, or create and use the `AnalyticsMock` class. This provides
an instance you can use in place of a real analytics object but each analytics
method is a no-op.
*Opt-in* In opt-in mode, no data will be sent until the user explicitly opt-in
to collection (`Analytics.enabled = true`). This includes screen views, events,
timing information, and exceptions.

## Other info

Expand Down
20 changes: 12 additions & 8 deletions test/src/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import 'dart:async';
import 'package:test/test.dart';
import 'package:usage/src/usage_impl.dart';

AnalyticsImplMock createMock({bool setOptIn: true}) =>
new AnalyticsImplMock('UA-0', setOptIn: setOptIn);
AnalyticsImplMock createMock({ Map<String, dynamic> props }) =>
new AnalyticsImplMock('UA-0', props: props);

void was(Map m, String type) => expect(m['t'], type);
void has(Map m, String key) => expect(m[key], isNotNull);
Expand All @@ -20,19 +20,23 @@ class AnalyticsImplMock extends AnalyticsImpl {
MockProperties get mockProperties => properties;
MockPostHandler get mockPostHandler => postHandler;

AnalyticsImplMock(String trackingId, {bool setOptIn: true}) :
super(trackingId, new MockProperties(), new MockPostHandler(),
applicationName: 'Test App', applicationVersion: '0.1') {
if (setOptIn) optIn = true;
}
AnalyticsImplMock(String trackingId, { Map<String, dynamic> props }) : super(
trackingId,
new MockProperties(props),
new MockPostHandler(),
applicationName: 'Test App',
applicationVersion: '0.1'
);

Map<String, dynamic> get last => mockPostHandler.last;
}

class MockProperties extends PersistentProperties {
Map<String, dynamic> props = {};

MockProperties() : super('mock');
MockProperties([Map<String, dynamic> props]) : super('mock') {
if (props != null) this.props.addAll(props);
}

dynamic operator[](String key) => props[key];

Expand Down

0 comments on commit 775bffd

Please sign in to comment.