Skip to content

Commit 8e0891d

Browse files
zhu-xiaoweizhu-xiaowei
andauthored
feat: add preset traffic source attributes (#10)
Co-authored-by: zhu-xiaowei <xiaoweii@amazom.com>
1 parent 2996028 commit 8e0891d

File tree

8 files changed

+230
-17
lines changed

8 files changed

+230
-17
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.4.0
2+
3+
* feat: add preset traffic source attributes
4+
15
## 0.3.0
26

37
* feat: add record screen view api

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ analytics.setUserId(null);
8080

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

9292
1. Add global attributes when initializing the SDK
93-
93+
94+
The following example code shows how to add traffic source fields as global attributes when initializing the SDK.
9495
```dart
9596
analytics.init({
9697
appId: "your appId",
9798
endpoint: "https://example.com/collect",
9899
globalAttributes: {
99-
"_traffic_source_medium": "Search engine",
100-
"_traffic_source_name": "Summer promotion",
100+
Attr.TRAFFIC_SOURCE_SOURCE: "amazon",
101+
Attr.TRAFFIC_SOURCE_MEDIUM: "cpc",
102+
Attr.TRAFFIC_SOURCE_CAMPAIGN: "summer_promotion",
103+
Attr.TRAFFIC_SOURCE_CAMPAIGN_ID: "summer_promotion_01",
104+
Attr.TRAFFIC_SOURCE_TERM: "running_shoes",
105+
Attr.TRAFFIC_SOURCE_CONTENT: "banner_ad_1",
106+
Attr.TRAFFIC_SOURCE_CLID: "amazon_ad_123",
107+
Attr.TRAFFIC_SOURCE_CLID_PLATFORM: "amazon_ads",
108+
Attr.APP_INSTALL_CHANNEL: "amazon_store"
101109
}
102110
});
103111
```
104112

105113
2. Add global attributes after initializing the SDK
106114
```dart
107115
analytics.addGlobalAttributes({
108-
"_traffic_source_medium": "Search engine",
109-
"_traffic_source_name": "Summer promotion",
116+
Attr.TRAFFIC_SOURCE_MEDIUM: "Search engine",
110117
"level": 10
111118
});
112119
```
@@ -138,7 +145,8 @@ var itemBook = ClickstreamItem(
138145
analytics.record(
139146
name: "view_item",
140147
attributes: {
141-
"currency": "USD",
148+
Attr.VALUE: 99,
149+
Attr.CURRENCY: "USD"
142150
"event_category": "recommended"
143151
},
144152
items: [itemBook]

analysis_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ include: package:flutter_lints/flutter.yaml
22

33
# Additional information about this file can be found at
44
# https://dart.dev/guides/language/analysis-options
5+
linter:
6+
rules:
7+
// ignore: non_constant_identifier_name

example/lib/main.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,15 @@ class _MyAppState extends State<MyApp> {
4242
isCompressEvents: false,
4343
sessionTimeoutDuration: 30000,
4444
globalAttributes: {
45-
"channel": "Samsung",
45+
Attr.TRAFFIC_SOURCE_SOURCE: "amazon",
46+
Attr.TRAFFIC_SOURCE_MEDIUM: "cpc",
47+
Attr.TRAFFIC_SOURCE_CAMPAIGN: "summer_promotion",
48+
Attr.TRAFFIC_SOURCE_CAMPAIGN_ID: "summer_promotion_01",
49+
Attr.TRAFFIC_SOURCE_TERM: "running_shoes",
50+
Attr.TRAFFIC_SOURCE_CONTENT: "banner_ad_1",
51+
Attr.TRAFFIC_SOURCE_CLID: "amazon_ad_123",
52+
Attr.TRAFFIC_SOURCE_CLID_PLATFORM: "amazon_ads",
53+
Attr.APP_INSTALL_CHANNEL: "amazon_store",
4654
"Class": 5,
4755
"isTrue": true,
4856
"Score": 24.32
@@ -180,7 +188,7 @@ class _MyAppState extends State<MyApp> {
180188
title: const Text('addGlobalAttributes'),
181189
onTap: () async {
182190
analytics.addGlobalAttributes({
183-
"channel": "Samsung",
191+
Attr.APP_INSTALL_CHANNEL: "amazon_store",
184192
"Class": 5,
185193
"isTrue": true,
186194
"Score": 24.32
@@ -193,7 +201,8 @@ class _MyAppState extends State<MyApp> {
193201
leading: const Icon(Icons.delete_rounded),
194202
title: const Text('deleteGlobalAttributes'),
195203
onTap: () async {
196-
analytics.deleteGlobalAttributes(["Score", "channel"]);
204+
analytics.deleteGlobalAttributes(
205+
["Score", Attr.APP_INSTALL_CHANNEL]);
197206
log("deleteGlobalAttributes Score and channel");
198207
},
199208
minLeadingWidth: 0,

lib/clickstream_analytics.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,19 @@ class ClickstreamAnalytics {
119119
return ClickstreamInterface.instance.enable();
120120
}
121121
}
122+
123+
class Attr {
124+
static const String TRAFFIC_SOURCE_SOURCE = '_traffic_source_source';
125+
static const String TRAFFIC_SOURCE_MEDIUM = '_traffic_source_medium';
126+
static const String TRAFFIC_SOURCE_CAMPAIGN = '_traffic_source_campaign';
127+
static const String TRAFFIC_SOURCE_CAMPAIGN_ID =
128+
'_traffic_source_campaign_id';
129+
static const String TRAFFIC_SOURCE_TERM = '_traffic_source_term';
130+
static const String TRAFFIC_SOURCE_CONTENT = '_traffic_source_content';
131+
static const String TRAFFIC_SOURCE_CLID = '_traffic_source_clid';
132+
static const String TRAFFIC_SOURCE_CLID_PLATFORM =
133+
'_traffic_source_clid_platform';
134+
static const String APP_INSTALL_CHANNEL = '_app_install_channel';
135+
static const String VALUE = '_value';
136+
static const String CURRENCY = '_currency';
137+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'package:flutter_test/flutter_test.dart';
5+
6+
import 'mock_method_channel.dart';
7+
8+
void main() {
9+
TestWidgetsFlutterBinding.ensureInitialized();
10+
11+
MockMethodChannel platform = MockMethodChannel();
12+
13+
test('init method for UnimplementedError', () async {
14+
Map<String, Object?> initConfig = {
15+
'appId': 'testApp',
16+
'endpoint': "",
17+
};
18+
expect(() async {
19+
await platform.init(initConfig);
20+
}, throwsA(isInstanceOf<UnimplementedError>()));
21+
});
22+
23+
test('record method for UnimplementedError', () async {
24+
Map<String, Object?> attributes = {
25+
"category": "shoes",
26+
"currency": "CNY",
27+
"value": 279.9
28+
};
29+
expect(platform.record(attributes),
30+
throwsA(isInstanceOf<UnimplementedError>()));
31+
});
32+
33+
test('setUserId method for UnimplementedError', () async {
34+
Map<String, Object?> attributes = {
35+
"userId": "1234",
36+
};
37+
expect(platform.setUserId(attributes),
38+
throwsA(isInstanceOf<UnimplementedError>()));
39+
});
40+
41+
test('setUserAttributes method for UnimplementedError', () async {
42+
Map<String, Object?> attributes = {"_user_age": 21, "_user_name": "carl"};
43+
expect(platform.setUserAttributes(attributes),
44+
throwsA(isInstanceOf<UnimplementedError>()));
45+
});
46+
47+
test('addGlobalAttributes method for UnimplementedError', () async {
48+
Map<String, Object?> attributes = {
49+
"channel": "Play Store",
50+
"level": 5.1,
51+
"class": 6
52+
};
53+
expect(platform.addGlobalAttributes(attributes),
54+
throwsA(isInstanceOf<UnimplementedError>()));
55+
});
56+
57+
test('deleteGlobalAttributes method for UnimplementedError', () async {
58+
Map<String, Object?> attributes = {
59+
"attributes": ["attr1", "attr2"],
60+
};
61+
expect(platform.deleteGlobalAttributes(attributes),
62+
throwsA(isInstanceOf<UnimplementedError>()));
63+
});
64+
65+
test('updateConfigure method for UnimplementedError', () async {
66+
Map<String, Object?> attributes = {
67+
"appId": "newAppId",
68+
"endpoint": "https://example.com/collect",
69+
};
70+
expect(platform.updateConfigure(attributes),
71+
throwsA(isInstanceOf<UnimplementedError>()));
72+
});
73+
74+
test('flushEvents method for UnimplementedError', () async {
75+
expect(platform.flushEvents(), throwsA(isInstanceOf<UnimplementedError>()));
76+
});
77+
78+
test('disable method for UnimplementedError', () async {
79+
expect(platform.disable(), throwsA(isInstanceOf<UnimplementedError>()));
80+
});
81+
82+
test('enable method for UnimplementedError', () async {
83+
expect(platform.enable(), throwsA(isInstanceOf<UnimplementedError>()));
84+
});
85+
}

test/clickstream_flutter_test.dart

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,28 @@ void main() {
7272
appId: 'testApp',
7373
endpoint: "https://example.com/collect",
7474
globalAttributes: {
75-
"channel": "Samsung",
75+
Attr.APP_INSTALL_CHANNEL: "amazon_store",
76+
"Class": 5,
77+
"isTrue": true,
78+
"Score": 24.32
79+
});
80+
expect(result, true);
81+
});
82+
83+
test('init SDK with traffic source using global attributes', () async {
84+
var result = await analytics.init(
85+
appId: 'testApp',
86+
endpoint: "https://example.com/collect",
87+
globalAttributes: {
88+
Attr.TRAFFIC_SOURCE_SOURCE: "amazon",
89+
Attr.TRAFFIC_SOURCE_MEDIUM: "cpc",
90+
Attr.TRAFFIC_SOURCE_CAMPAIGN: "summer_promotion",
91+
Attr.TRAFFIC_SOURCE_CAMPAIGN_ID: "summer_promotion_01",
92+
Attr.TRAFFIC_SOURCE_TERM: "running_shoes",
93+
Attr.TRAFFIC_SOURCE_CONTENT: "banner_ad_1",
94+
Attr.TRAFFIC_SOURCE_CLID: "amazon_ad_123",
95+
Attr.TRAFFIC_SOURCE_CLID_PLATFORM: "amazon_ads",
96+
Attr.APP_INSTALL_CHANNEL: "amazon_store",
7697
"Class": 5,
7798
"isTrue": true,
7899
"Score": 24.32
@@ -93,7 +114,7 @@ void main() {
93114
isTrackUserEngagementEvents: false,
94115
isTrackAppExceptionEvents: true,
95116
globalAttributes: {
96-
"channel": "Samsung",
117+
Attr.APP_INSTALL_CHANNEL: "amazon_store",
97118
});
98119
expect(result, true);
99120
});
@@ -125,10 +146,14 @@ void main() {
125146
price: 65,
126147
currency: "USD",
127148
attributes: {"place_of_origin": "USA"});
128-
var result = analytics.record(
129-
name: "cart_view",
130-
attributes: {"_traffic_source_name": "Summer promotion"},
131-
items: [itemBook, itemShoes]);
149+
var result = analytics.record(name: "cart_view", attributes: {
150+
Attr.TRAFFIC_SOURCE_CAMPAIGN: "Summer promotion",
151+
Attr.VALUE: 164,
152+
Attr.CURRENCY: "USD"
153+
}, items: [
154+
itemBook,
155+
itemShoes
156+
]);
132157
expect(result, isNotNull);
133158
});
134159

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

156181
test('setGlobalAttributes', () async {
157182
var result = analytics.addGlobalAttributes(
158-
{"channel": "Play Store", "level": 5.1, "class": 6});
183+
{Attr.APP_INSTALL_CHANNEL: "amazon_store", "level": 5.1, "class": 6});
159184
var result1 = analytics.addGlobalAttributes({});
160185
expect(result, isNotNull);
161186
expect(result1, isNotNull);

test/mock_method_channel.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'package:clickstream_analytics/clickstream_analytics_platform_interface.dart';
5+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter/services.dart';
7+
8+
/// An implementation of [ClickstreamFlutterPlatform] that uses method channels.
9+
class MockMethodChannel extends ClickstreamInterface {
10+
/// The method channel used to interact with the native platform.
11+
@visibleForTesting
12+
final methodChannel = const MethodChannel('clickstream_flutter');
13+
14+
@override
15+
Future<bool> init(Map<String, Object?> configure) async {
16+
return super.init(configure);
17+
}
18+
19+
@override
20+
Future<void> record(Map<String, Object?> attributes) async {
21+
return super.record(attributes);
22+
}
23+
24+
@override
25+
Future<void> setUserId(Map<String, Object?> userId) async {
26+
return super.setUserId(userId);
27+
}
28+
29+
@override
30+
Future<void> setUserAttributes(Map<String, Object?> attributes) async {
31+
return super.setUserAttributes(attributes);
32+
}
33+
34+
@override
35+
Future<void> addGlobalAttributes(Map<String, Object?> attributes) async {
36+
return super.addGlobalAttributes(attributes);
37+
}
38+
39+
@override
40+
Future<void> deleteGlobalAttributes(Map<String, Object?> attributes) async {
41+
return super.deleteGlobalAttributes(attributes);
42+
}
43+
44+
@override
45+
Future<void> updateConfigure(Map<String, Object?> configure) async {
46+
return super.updateConfigure(configure);
47+
}
48+
49+
@override
50+
Future<void> flushEvents() async {
51+
return super.flushEvents();
52+
}
53+
54+
@override
55+
Future<void> disable() async {
56+
return super.disable();
57+
}
58+
59+
@override
60+
Future<void> enable() async {
61+
return super.enable();
62+
}
63+
}

0 commit comments

Comments
 (0)