Skip to content

Commit

Permalink
feat: add preset traffic source attributes (#10)
Browse files Browse the repository at this point in the history
Co-authored-by: zhu-xiaowei <[email protected]>
  • Loading branch information
zhu-xiaowei and zhu-xiaowei authored Apr 11, 2024
1 parent 2996028 commit 8e0891d
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.4.0

* feat: add preset traffic source attributes

## 0.3.0

* feat: add record screen view api
Expand Down
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ analytics.setUserId(null);

```dart
analytics.setUserAttributes({
"userName":"carl",
"userName": "carl",
"userAge": 22
});
```
Expand All @@ -90,23 +90,30 @@ Current login user's attributes will be cached in disk, so the next time app lau
#### Add global attribute

1. Add global attributes when initializing the SDK


The following example code shows how to add traffic source fields as global attributes when initializing the SDK.
```dart
analytics.init({
appId: "your appId",
endpoint: "https://example.com/collect",
globalAttributes: {
"_traffic_source_medium": "Search engine",
"_traffic_source_name": "Summer promotion",
Attr.TRAFFIC_SOURCE_SOURCE: "amazon",
Attr.TRAFFIC_SOURCE_MEDIUM: "cpc",
Attr.TRAFFIC_SOURCE_CAMPAIGN: "summer_promotion",
Attr.TRAFFIC_SOURCE_CAMPAIGN_ID: "summer_promotion_01",
Attr.TRAFFIC_SOURCE_TERM: "running_shoes",
Attr.TRAFFIC_SOURCE_CONTENT: "banner_ad_1",
Attr.TRAFFIC_SOURCE_CLID: "amazon_ad_123",
Attr.TRAFFIC_SOURCE_CLID_PLATFORM: "amazon_ads",
Attr.APP_INSTALL_CHANNEL: "amazon_store"
}
});
```

2. Add global attributes after initializing the SDK
```dart
analytics.addGlobalAttributes({
"_traffic_source_medium": "Search engine",
"_traffic_source_name": "Summer promotion",
Attr.TRAFFIC_SOURCE_MEDIUM: "Search engine",
"level": 10
});
```
Expand Down Expand Up @@ -138,7 +145,8 @@ var itemBook = ClickstreamItem(
analytics.record(
name: "view_item",
attributes: {
"currency": "USD",
Attr.VALUE: 99,
Attr.CURRENCY: "USD"
"event_category": "recommended"
},
items: [itemBook]
Expand Down
3 changes: 3 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ include: package:flutter_lints/flutter.yaml

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
linter:
rules:
// ignore: non_constant_identifier_name
15 changes: 12 additions & 3 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,15 @@ class _MyAppState extends State<MyApp> {
isCompressEvents: false,
sessionTimeoutDuration: 30000,
globalAttributes: {
"channel": "Samsung",
Attr.TRAFFIC_SOURCE_SOURCE: "amazon",
Attr.TRAFFIC_SOURCE_MEDIUM: "cpc",
Attr.TRAFFIC_SOURCE_CAMPAIGN: "summer_promotion",
Attr.TRAFFIC_SOURCE_CAMPAIGN_ID: "summer_promotion_01",
Attr.TRAFFIC_SOURCE_TERM: "running_shoes",
Attr.TRAFFIC_SOURCE_CONTENT: "banner_ad_1",
Attr.TRAFFIC_SOURCE_CLID: "amazon_ad_123",
Attr.TRAFFIC_SOURCE_CLID_PLATFORM: "amazon_ads",
Attr.APP_INSTALL_CHANNEL: "amazon_store",
"Class": 5,
"isTrue": true,
"Score": 24.32
Expand Down Expand Up @@ -180,7 +188,7 @@ class _MyAppState extends State<MyApp> {
title: const Text('addGlobalAttributes'),
onTap: () async {
analytics.addGlobalAttributes({
"channel": "Samsung",
Attr.APP_INSTALL_CHANNEL: "amazon_store",
"Class": 5,
"isTrue": true,
"Score": 24.32
Expand All @@ -193,7 +201,8 @@ class _MyAppState extends State<MyApp> {
leading: const Icon(Icons.delete_rounded),
title: const Text('deleteGlobalAttributes'),
onTap: () async {
analytics.deleteGlobalAttributes(["Score", "channel"]);
analytics.deleteGlobalAttributes(
["Score", Attr.APP_INSTALL_CHANNEL]);
log("deleteGlobalAttributes Score and channel");
},
minLeadingWidth: 0,
Expand Down
16 changes: 16 additions & 0 deletions lib/clickstream_analytics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,19 @@ class ClickstreamAnalytics {
return ClickstreamInterface.instance.enable();
}
}

class Attr {
static const String TRAFFIC_SOURCE_SOURCE = '_traffic_source_source';
static const String TRAFFIC_SOURCE_MEDIUM = '_traffic_source_medium';
static const String TRAFFIC_SOURCE_CAMPAIGN = '_traffic_source_campaign';
static const String TRAFFIC_SOURCE_CAMPAIGN_ID =
'_traffic_source_campaign_id';
static const String TRAFFIC_SOURCE_TERM = '_traffic_source_term';
static const String TRAFFIC_SOURCE_CONTENT = '_traffic_source_content';
static const String TRAFFIC_SOURCE_CLID = '_traffic_source_clid';
static const String TRAFFIC_SOURCE_CLID_PLATFORM =
'_traffic_source_clid_platform';
static const String APP_INSTALL_CHANNEL = '_app_install_channel';
static const String VALUE = '_value';
static const String CURRENCY = '_currency';
}
85 changes: 85 additions & 0 deletions test/clickstream_flutter_platform_interface_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'package:flutter_test/flutter_test.dart';

import 'mock_method_channel.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

MockMethodChannel platform = MockMethodChannel();

test('init method for UnimplementedError', () async {
Map<String, Object?> initConfig = {
'appId': 'testApp',
'endpoint': "",
};
expect(() async {
await platform.init(initConfig);
}, throwsA(isInstanceOf<UnimplementedError>()));
});

test('record method for UnimplementedError', () async {
Map<String, Object?> attributes = {
"category": "shoes",
"currency": "CNY",
"value": 279.9
};
expect(platform.record(attributes),
throwsA(isInstanceOf<UnimplementedError>()));
});

test('setUserId method for UnimplementedError', () async {
Map<String, Object?> attributes = {
"userId": "1234",
};
expect(platform.setUserId(attributes),
throwsA(isInstanceOf<UnimplementedError>()));
});

test('setUserAttributes method for UnimplementedError', () async {
Map<String, Object?> attributes = {"_user_age": 21, "_user_name": "carl"};
expect(platform.setUserAttributes(attributes),
throwsA(isInstanceOf<UnimplementedError>()));
});

test('addGlobalAttributes method for UnimplementedError', () async {
Map<String, Object?> attributes = {
"channel": "Play Store",
"level": 5.1,
"class": 6
};
expect(platform.addGlobalAttributes(attributes),
throwsA(isInstanceOf<UnimplementedError>()));
});

test('deleteGlobalAttributes method for UnimplementedError', () async {
Map<String, Object?> attributes = {
"attributes": ["attr1", "attr2"],
};
expect(platform.deleteGlobalAttributes(attributes),
throwsA(isInstanceOf<UnimplementedError>()));
});

test('updateConfigure method for UnimplementedError', () async {
Map<String, Object?> attributes = {
"appId": "newAppId",
"endpoint": "https://example.com/collect",
};
expect(platform.updateConfigure(attributes),
throwsA(isInstanceOf<UnimplementedError>()));
});

test('flushEvents method for UnimplementedError', () async {
expect(platform.flushEvents(), throwsA(isInstanceOf<UnimplementedError>()));
});

test('disable method for UnimplementedError', () async {
expect(platform.disable(), throwsA(isInstanceOf<UnimplementedError>()));
});

test('enable method for UnimplementedError', () async {
expect(platform.enable(), throwsA(isInstanceOf<UnimplementedError>()));
});
}
39 changes: 32 additions & 7 deletions test/clickstream_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,28 @@ void main() {
appId: 'testApp',
endpoint: "https://example.com/collect",
globalAttributes: {
"channel": "Samsung",
Attr.APP_INSTALL_CHANNEL: "amazon_store",
"Class": 5,
"isTrue": true,
"Score": 24.32
});
expect(result, true);
});

test('init SDK with traffic source using global attributes', () async {
var result = await analytics.init(
appId: 'testApp',
endpoint: "https://example.com/collect",
globalAttributes: {
Attr.TRAFFIC_SOURCE_SOURCE: "amazon",
Attr.TRAFFIC_SOURCE_MEDIUM: "cpc",
Attr.TRAFFIC_SOURCE_CAMPAIGN: "summer_promotion",
Attr.TRAFFIC_SOURCE_CAMPAIGN_ID: "summer_promotion_01",
Attr.TRAFFIC_SOURCE_TERM: "running_shoes",
Attr.TRAFFIC_SOURCE_CONTENT: "banner_ad_1",
Attr.TRAFFIC_SOURCE_CLID: "amazon_ad_123",
Attr.TRAFFIC_SOURCE_CLID_PLATFORM: "amazon_ads",
Attr.APP_INSTALL_CHANNEL: "amazon_store",
"Class": 5,
"isTrue": true,
"Score": 24.32
Expand All @@ -93,7 +114,7 @@ void main() {
isTrackUserEngagementEvents: false,
isTrackAppExceptionEvents: true,
globalAttributes: {
"channel": "Samsung",
Attr.APP_INSTALL_CHANNEL: "amazon_store",
});
expect(result, true);
});
Expand Down Expand Up @@ -125,10 +146,14 @@ void main() {
price: 65,
currency: "USD",
attributes: {"place_of_origin": "USA"});
var result = analytics.record(
name: "cart_view",
attributes: {"_traffic_source_name": "Summer promotion"},
items: [itemBook, itemShoes]);
var result = analytics.record(name: "cart_view", attributes: {
Attr.TRAFFIC_SOURCE_CAMPAIGN: "Summer promotion",
Attr.VALUE: 164,
Attr.CURRENCY: "USD"
}, items: [
itemBook,
itemShoes
]);
expect(result, isNotNull);
});

Expand All @@ -155,7 +180,7 @@ void main() {

test('setGlobalAttributes', () async {
var result = analytics.addGlobalAttributes(
{"channel": "Play Store", "level": 5.1, "class": 6});
{Attr.APP_INSTALL_CHANNEL: "amazon_store", "level": 5.1, "class": 6});
var result1 = analytics.addGlobalAttributes({});
expect(result, isNotNull);
expect(result1, isNotNull);
Expand Down
63 changes: 63 additions & 0 deletions test/mock_method_channel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'package:clickstream_analytics/clickstream_analytics_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

/// An implementation of [ClickstreamFlutterPlatform] that uses method channels.
class MockMethodChannel extends ClickstreamInterface {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('clickstream_flutter');

@override
Future<bool> init(Map<String, Object?> configure) async {
return super.init(configure);
}

@override
Future<void> record(Map<String, Object?> attributes) async {
return super.record(attributes);
}

@override
Future<void> setUserId(Map<String, Object?> userId) async {
return super.setUserId(userId);
}

@override
Future<void> setUserAttributes(Map<String, Object?> attributes) async {
return super.setUserAttributes(attributes);
}

@override
Future<void> addGlobalAttributes(Map<String, Object?> attributes) async {
return super.addGlobalAttributes(attributes);
}

@override
Future<void> deleteGlobalAttributes(Map<String, Object?> attributes) async {
return super.deleteGlobalAttributes(attributes);
}

@override
Future<void> updateConfigure(Map<String, Object?> configure) async {
return super.updateConfigure(configure);
}

@override
Future<void> flushEvents() async {
return super.flushEvents();
}

@override
Future<void> disable() async {
return super.disable();
}

@override
Future<void> enable() async {
return super.enable();
}
}

0 comments on commit 8e0891d

Please sign in to comment.