From 28404bf53ed49b3b26cacd6bbf8e87418a62a4a5 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:57:49 -0400 Subject: [PATCH 01/14] Added newe `SurveyButton` class --- .../lib/src/survey_handler.dart | 100 +++++++++++------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index 245da3457f..15a3e0ecb9 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -13,35 +13,7 @@ import 'package:unified_analytics/src/initializer.dart'; import 'constants.dart'; import 'log_handler.dart'; -/// Function to ensure that each survey is still valid by -/// checking the [Survey.startDate] and [Survey.endDate] -/// against the current [clock.now()] date -bool checkSurveyDate(Survey survey) { - if (survey.startDate.isBefore(clock.now()) && - survey.endDate.isAfter(clock.now())) return true; - - return false; -} - -/// Function that takes in a json data structure that is in -/// the form of a list and returns a list of [Survey]s -/// -/// This will also check the survey's dates to make sure it -/// has not expired -List parseSurveysFromJson(List body) => body - .map((element) { - // Error handling to skip any surveys from the remote location - // that fail to parse - try { - return Survey.fromJson(element as Map); - // ignore: avoid_catches_without_on_clauses - } catch (err) { - return null; - } - }) - .whereType() - .where(checkSurveyDate) - .toList(); +enum ButtonAction { accept, dismiss, snooze } class Condition { /// How to query the log file @@ -120,33 +92,30 @@ class PersistedSurvey { class Survey { final String uniqueId; - final String url; final DateTime startDate; final DateTime endDate; final String description; final int dismissForMinutes; - final String moreInfoUrl; final double samplingRate; final List conditionList; + final List surveyButtonList; /// A data class that contains the relevant information for a given /// survey parsed from the survey's metadata file Survey({ required this.uniqueId, - required this.url, required this.startDate, required this.endDate, required this.description, required this.dismissForMinutes, - required this.moreInfoUrl, required this.samplingRate, required this.conditionList, + required this.surveyButtonList, }); /// Parse the contents of the json metadata file hosted externally Survey.fromJson(Map json) : uniqueId = json['uniqueId'] as String, - url = json['url'] as String, startDate = DateTime.parse(json['startDate'] as String), endDate = DateTime.parse(json['endDate'] as String), description = json['description'] as String, @@ -154,7 +123,6 @@ class Survey { dismissForMinutes = json['dismissForMinutes'] is String ? int.parse(json['dismissForMinutes'] as String) : json['dismissForMinutes'] as int, - moreInfoUrl = json['moreInfoURL'] as String, // Handle both string and double fields samplingRate = json['samplingRate'] is String ? double.parse(json['samplingRate'] as String) @@ -162,6 +130,10 @@ class Survey { conditionList = (json['conditions'] as List).map((e) { e as Map; return Condition.fromJson(e); + }).toList(), + surveyButtonList = (json['buttons'] as List).map((e) { + e as Map; + return SurveyButton.fromJson(e); }).toList(); @override @@ -169,18 +141,38 @@ class Survey { final encoder = JsonEncoder.withIndent(' '); return encoder.convert({ 'uniqueId': uniqueId, - 'url': url, 'startDate': startDate.toString(), 'endDate': endDate.toString(), 'description': description, 'dismissForMinutes': dismissForMinutes, - 'moreInfoUrl': moreInfoUrl, 'samplingRate': samplingRate, 'conditionList': conditionList.map((e) => e.toMap()).toList(), }); } } +class SurveyButton { + final String buttonText; + final ButtonAction action; + final String? url; + + SurveyButton({ + required this.buttonText, + required this.action, + this.url, + }); + + SurveyButton.fromJson(Map json) + : buttonText = json['buttonText'] as String, + action = switch (json['action']) { + 'accept' => ButtonAction.accept, + 'dismiss' => ButtonAction.dismiss, + 'snooze' => ButtonAction.snooze, + _ => throw ArgumentError('Action for button not valid') + }, + url = json['url'] as String?; +} + class SurveyHandler { final File _dismissedSurveyFile; @@ -193,6 +185,16 @@ class SurveyHandler { kDismissedSurveyFileName, )); + /// Function to ensure that each survey is still valid by + /// checking the [Survey.startDate] and [Survey.endDate] + /// against the current [clock.now()] date + static bool checkSurveyDate(Survey survey) { + if (survey.startDate.isBefore(clock.now()) && + survey.endDate.isAfter(clock.now())) return true; + + return false; + } + /// Invoking this method will persist the survey's id in /// the local file with either a snooze or permanently dismissed /// indicator @@ -265,6 +267,26 @@ class SurveyHandler { return surveyList; } + /// Function that takes in a json data structure that is in + /// the form of a list and returns a list of [Survey]s + /// + /// This will also check the survey's dates to make sure it + /// has not expired + static List parseSurveysFromJson(List body) => body + .map((element) { + // Error handling to skip any surveys from the remote location + // that fail to parse + try { + return Survey.fromJson(element as Map); + // ignore: avoid_catches_without_on_clauses + } catch (err) { + return null; + } + }) + .whereType() + .where(checkSurveyDate) + .toList(); + /// Fetches the json in string form from the remote location Future _fetchContents() async { final uri = Uri.parse(kContextualSurveyUrl); @@ -311,7 +333,7 @@ class FakeSurveyHandler extends SurveyHandler { // `.fromString()` constructor because the `parseSurveysFromJson` // method already checks their date for (final survey in initializedSurveys) { - if (checkSurveyDate(survey)) { + if (SurveyHandler.checkSurveyDate(survey)) { _fakeInitializedSurveys.add(survey); } } @@ -325,7 +347,7 @@ class FakeSurveyHandler extends SurveyHandler { required String content, }) : super(fs: fs, homeDirectory: homeDirectory) { final body = jsonDecode(content) as List; - for (final fakeSurvey in parseSurveysFromJson(body)) { + for (final fakeSurvey in SurveyHandler.parseSurveysFromJson(body)) { _fakeInitializedSurveys.add(fakeSurvey); } } From 84863bb47f3828e5d57d1bee0d5340f0203436f0 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:11:55 -0400 Subject: [PATCH 02/14] Fix tests --- .../test/events_with_fake_test.dart | 3 +- .../test/survey_handler_test.dart | 138 ++++++++++-------- 2 files changed, 78 insertions(+), 63 deletions(-) diff --git a/pkgs/unified_analytics/test/events_with_fake_test.dart b/pkgs/unified_analytics/test/events_with_fake_test.dart index 03b1b94f03..2c2f79e60b 100644 --- a/pkgs/unified_analytics/test/events_with_fake_test.dart +++ b/pkgs/unified_analytics/test/events_with_fake_test.dart @@ -27,14 +27,13 @@ void main() { /// up from the method to fetch available surveys final testSurvey = Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2022, 1, 1), endDate: DateTime(2022, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, // 100% sample rate conditionList: [], + surveyButtonList: [], ); /// Test event that will need to be sent since surveys won't diff --git a/pkgs/unified_analytics/test/survey_handler_test.dart b/pkgs/unified_analytics/test/survey_handler_test.dart index 28bdf307cc..d1593db95c 100644 --- a/pkgs/unified_analytics/test/survey_handler_test.dart +++ b/pkgs/unified_analytics/test/survey_handler_test.dart @@ -24,38 +24,36 @@ void main() { // range, and one that is not final validSurvey = Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [], + surveyButtonList: [], ); final invalidSurvey = Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2022, 1, 1), endDate: DateTime(2022, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [], + surveyButtonList: [], ); test('expired survey', () { final clock = Clock.fixed(date); withClock(clock, () { - expect(checkSurveyDate(invalidSurvey), false); + expect(SurveyHandler.checkSurveyDate(invalidSurvey), false); }); }); test('valid survey', () { final clock = Clock.fixed(date); withClock(clock, () { - expect(checkSurveyDate(validSurvey), true); + expect(SurveyHandler.checkSurveyDate(validSurvey), true); }); }); }); @@ -112,8 +110,8 @@ void main() { test('valid json', () { withClock(Clock.fixed(DateTime(2023, 6, 15)), () { - final parsedSurveys = - parseSurveysFromJson(jsonDecode(validContents) as List); + final parsedSurveys = SurveyHandler.parseSurveysFromJson( + jsonDecode(validContents) as List); expect(parsedSurveys.length, 1); expect(parsedSurveys.first.conditionList.length, 2); @@ -133,8 +131,8 @@ void main() { test('invalid json', () { withClock(Clock.fixed(DateTime(2023, 6, 15)), () { - final parsedSurveys = - parseSurveysFromJson(jsonDecode(invalidContents) as List); + final parsedSurveys = SurveyHandler.parseSurveysFromJson( + jsonDecode(invalidContents) as List); expect(parsedSurveys.length, 0, reason: 'The condition value is not a ' @@ -200,17 +198,16 @@ void main() { initializedSurveys: [ Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], + surveyButtonList: [], ), ], ), @@ -243,17 +240,16 @@ void main() { initializedSurveys: [ Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2022, 1, 1), endDate: DateTime(2022, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], + surveyButtonList: [], ), ], ), @@ -286,17 +282,16 @@ void main() { initializedSurveys: [ Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], + surveyButtonList: [], ), ], ), @@ -330,21 +325,36 @@ void main() { homeDirectory: homeDirectory, fs: fs, content: ''' [ { - "uniqueId": "uniqueId123", - "url": "url123", - "startDate": "2023-01-01T09:00:00-07:00", - "endDate": "2023-12-31T09:00:00-07:00", - "description": "description123", - "dismissForMinutes": "10", - "moreInfoURL": "moreInfoUrl123", - "samplingRate": "1.0", - "conditions": [ - { - "field": "logFileStats.recordCount", - "operator": ">=", - "value": 50 - } - ] + "uniqueId": "eca0100a-505b-4539-96d0-57235f816cef", + "startDate": "2023-07-01T09:00:00-07:00", + "endDate": "2023-07-30T09:00:00-07:00", + "description": "Help improve Flutter's release builds with this 3-question survey!", + "dismissForMinutes": "7200", + "samplingRate": "0.1", + "conditions": [ + { + "field": "logFileStats.recordCount", + "operator": ">=", + "value": 1000 + } + ], + "buttons": [ + { + "buttonText": "Take Survey", + "action": "accepted", + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2" + }, + { + "buttonText": "Dismiss", + "action": "dismissed", + "url": null + }, + { + "buttonText": "More Info", + "action": "snoozed", + "url": "https://docs.flutter.dev/reference/crash-reporting" + } + ] } ] '''), @@ -361,7 +371,6 @@ void main() { final survey = fetchedSurveys.first; expect(survey.uniqueId, 'uniqueId123'); - expect(survey.url, 'url123'); expect(survey.startDate.year, 2023); expect(survey.startDate.month, 1); expect(survey.startDate.day, 1); @@ -370,7 +379,6 @@ void main() { expect(survey.endDate.day, 31); expect(survey.description, 'description123'); expect(survey.dismissForMinutes, 10); - expect(survey.moreInfoUrl, 'moreInfoUrl123'); expect(survey.samplingRate, 1.0); expect(survey.conditionList.length, 1); @@ -395,21 +403,36 @@ void main() { homeDirectory: homeDirectory, fs: fs, content: ''' [ { - "uniqueId": "xxxxxx", - "url": "xxxxx", + "uniqueId": "eca0100a-505b-4539-96d0-57235f816cef", "startDate": "NOT A REAL DATE", - "endDate": "2023-12-31T09:00:00-07:00", - "description": "xxxxxxx", - "dismissForMinutes": "10BAD", - "moreInfoURL": "xxxxxx", - "samplingRate": "1.0", - "conditions": [ - { - "field": "logFileStats.recordCount", - "operator": ">=", - "value": 50 - } - ] + "endDate": "2023-07-30T09:00:00-07:00", + "description": "Help improve Flutter's release builds with this 3-question survey!", + "dismissForMinutes": "7200", + "samplingRate": "0.1", + "conditions": [ + { + "field": "logFileStats.recordCount", + "operator": ">=", + "value": 1000 + } + ], + "buttons": [ + { + "buttonText": "Take Survey", + "action": "accepted", + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2" + }, + { + "buttonText": "Dismiss", + "action": "dismissed", + "url": null + }, + { + "buttonText": "More Info", + "action": "snoozed", + "url": "https://docs.flutter.dev/reference/crash-reporting" + } + ] } ] '''), @@ -510,17 +533,16 @@ void main() { initializedSurveys: [ Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], + surveyButtonList: [], ), ], ), @@ -569,17 +591,16 @@ void main() { await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { final survey = Survey( uniqueId: 'string2', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 0.6, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], + surveyButtonList: [], ); analytics = Analytics.test( tool: DashTool.flutterTool, @@ -615,17 +636,16 @@ void main() { await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { final survey = Survey( uniqueId: 'string2', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: 10, - moreInfoUrl: 'moreInfoUrl', samplingRate: 0.15, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], + surveyButtonList: [], ); analytics = Analytics.test( tool: DashTool.flutterTool, @@ -663,15 +683,14 @@ void main() { final minutesToSnooze = 30; final surveyToLoad = Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: minutesToSnooze, // Initialized survey with `minutesToSnooze` - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [], + surveyButtonList: [], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { @@ -755,14 +774,13 @@ void main() { final minutesToSnooze = 10; final surveyToLoad = Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: minutesToSnooze, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [], + surveyButtonList: [], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { @@ -822,14 +840,13 @@ void main() { final minutesToSnooze = 10; final surveyToLoad = Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: minutesToSnooze, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [], + surveyButtonList: [], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { @@ -892,14 +909,13 @@ void main() { final minutesToSnooze = 10; final surveyToLoad = Survey( uniqueId: 'uniqueId', - url: 'url', startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', dismissForMinutes: minutesToSnooze, - moreInfoUrl: 'moreInfoUrl', samplingRate: 1.0, conditionList: [], + surveyButtonList: [], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { From 3298a6604b03a4f3c29f24afadd5b6c4607240bb Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:12:04 -0400 Subject: [PATCH 03/14] Add documentation for enums --- .../lib/src/survey_handler.dart | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index 15a3e0ecb9..20a281641c 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -13,7 +13,23 @@ import 'package:unified_analytics/src/initializer.dart'; import 'constants.dart'; import 'log_handler.dart'; -enum ButtonAction { accept, dismiss, snooze } +enum ButtonAction { + /// The user has decided to accept the survey being passed to them + /// + /// This will permanently dismiss the survey from showing up again + accept, + + /// The user has decided to dismiss the survey by clicking the dismiss + /// button being provided the survey + /// + /// This will permanently dismiss the survey from showing up again + dismiss, + + /// If this button is provided by a survey, it will temporarily dismiss + /// the survey from being prompted to the user for a period of time + /// specified in [Survey] field [dismissForMinutes] + snooze, +} class Condition { /// How to query the log file From 814f673adda34bdc856e5f837b8ced102b487b7f Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:31:16 -0400 Subject: [PATCH 04/14] Update sample_rate.dart --- pkgs/unified_analytics/example/sample_rate.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/example/sample_rate.dart b/pkgs/unified_analytics/example/sample_rate.dart index bb3d1b7405..756968a685 100644 --- a/pkgs/unified_analytics/example/sample_rate.dart +++ b/pkgs/unified_analytics/example/sample_rate.dart @@ -52,7 +52,7 @@ Number of iterations = $iterations --- Count of iterations sampled (successes) = $count -Actual sample rate = ${count / iterations} +Actual sample rate = ${(count / iterations).toStringAsFixed(4)} --- Runtime = ${end.difference(start).inMilliseconds}ms From 0cd83489eb1720db8566c5aa9b7bb3f0336bc351 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:02:41 -0400 Subject: [PATCH 05/14] Update tests to check for `SurveyButton` classes --- .../test/survey_handler_test.dart | 140 ++++++++++++------ 1 file changed, 94 insertions(+), 46 deletions(-) diff --git a/pkgs/unified_analytics/test/survey_handler_test.dart b/pkgs/unified_analytics/test/survey_handler_test.dart index d1593db95c..8be0540a27 100644 --- a/pkgs/unified_analytics/test/survey_handler_test.dart +++ b/pkgs/unified_analytics/test/survey_handler_test.dart @@ -63,12 +63,10 @@ void main() { [ { "uniqueId": "xxxxx", - "url": "xxxxx", "startDate": "2023-06-01T09:00:00-07:00", "endDate": "2023-06-30T09:00:00-07:00", "description": "xxxxxxx", "dismissForMinutes": "10", - "moreInfoURL": "xxxxxx", "samplingRate": "1.0", "conditions": [ { @@ -81,7 +79,14 @@ void main() { "operator": "<", "value": 3 } - ] + ], + "buttons": [ + { + "buttonText": "Take Survey", + "action": "accept", + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2" + } + ] } ] '''; @@ -90,12 +95,10 @@ void main() { [ { "uniqueId": "xxxxx", - "url": "xxxxx", "startDate": "2023-06-01T09:00:00-07:00", "endDate": "2023-06-30T09:00:00-07:00", "description": "xxxxxxx", "dismissForMinutes": "10", - "moreInfoURL": "xxxxxx", "samplingRate": "1.0", "conditions": [ { @@ -103,7 +106,14 @@ void main() { "operator": ">=", "value": "1000xxxx" } - ] + ], + "buttons": [ + { + "buttonText": "Take Survey", + "action": "accept", + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2" + } + ] } ] '''; @@ -126,6 +136,8 @@ void main() { expect(secondCondition.field, 'logFileStats.toolCount.flutter-tool'); expect(secondCondition.operatorString, '<'); expect(secondCondition.value, 3); + + expect(parsedSurveys.first.surveyButtonList.length, 1); }); }); @@ -207,7 +219,13 @@ void main() { Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], - surveyButtonList: [], + surveyButtonList: [ + SurveyButton( + buttonText: 'buttonText', + action: ButtonAction.accept, + url: 'http://example.com', + ), + ], ), ], ), @@ -221,6 +239,10 @@ void main() { final fetchedSurveys = await analytics.fetchAvailableSurveys(); expect(fetchedSurveys.length, 1); + + final survey = fetchedSurveys.first; + expect(survey.conditionList.length, 2); + expect(survey.surveyButtonList.length, 1); }); }); @@ -325,33 +347,33 @@ void main() { homeDirectory: homeDirectory, fs: fs, content: ''' [ { - "uniqueId": "eca0100a-505b-4539-96d0-57235f816cef", - "startDate": "2023-07-01T09:00:00-07:00", - "endDate": "2023-07-30T09:00:00-07:00", - "description": "Help improve Flutter's release builds with this 3-question survey!", - "dismissForMinutes": "7200", - "samplingRate": "0.1", + "uniqueId": "uniqueId123", + "startDate": "2023-01-01T09:00:00-07:00", + "endDate": "2023-12-31T09:00:00-07:00", + "description": "description123", + "dismissForMinutes": "10", + "samplingRate": "1.0", "conditions": [ { "field": "logFileStats.recordCount", "operator": ">=", - "value": 1000 + "value": 50 } ], "buttons": [ { "buttonText": "Take Survey", - "action": "accepted", - "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2" + "action": "accept", + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2" }, { "buttonText": "Dismiss", - "action": "dismissed", + "action": "dismiss", "url": null }, { "buttonText": "More Info", - "action": "snoozed", + "action": "snooze", "url": "https://docs.flutter.dev/reference/crash-reporting" } ] @@ -386,6 +408,22 @@ void main() { expect(condition.field, 'logFileStats.recordCount'); expect(condition.operatorString, '>='); expect(condition.value, 50); + + final buttonList = survey.surveyButtonList; + expect(buttonList.length, 3); + expect(buttonList.first.buttonText, 'Take Survey'); + expect(buttonList.first.action, ButtonAction.accept); + expect(buttonList.first.url, + 'https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2'); + + expect(buttonList.elementAt(1).buttonText, 'Dismiss'); + expect(buttonList.elementAt(1).action, ButtonAction.dismiss); + expect(buttonList.elementAt(1).url, isNull); + + expect(buttonList.last.buttonText, 'More Info'); + expect(buttonList.last.action, ButtonAction.snooze); + expect(buttonList.last.url, + 'https://docs.flutter.dev/reference/crash-reporting'); }); }); @@ -403,7 +441,7 @@ void main() { homeDirectory: homeDirectory, fs: fs, content: ''' [ { - "uniqueId": "eca0100a-505b-4539-96d0-57235f816cef", + "uniqueId": "uniqueId123", "startDate": "NOT A REAL DATE", "endDate": "2023-07-30T09:00:00-07:00", "description": "Help improve Flutter's release builds with this 3-question survey!", @@ -419,17 +457,17 @@ void main() { "buttons": [ { "buttonText": "Take Survey", - "action": "accepted", + "action": "accept", "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2" }, { "buttonText": "Dismiss", - "action": "dismissed", + "action": "dismiss", "url": null }, { "buttonText": "More Info", - "action": "snoozed", + "action": "snooze", "url": "https://docs.flutter.dev/reference/crash-reporting" } ] @@ -464,37 +502,41 @@ void main() { [ { "uniqueId": "12345", - "url": "xxxxx", "startDate": "2023-01-01T09:00:00-07:00", "endDate": "2023-12-31T09:00:00-07:00", - "description": "xxxxxxx", - "dismissForMinutes": "10", - "moreInfoURL": "xxxxxx", - "samplingRate": "1.0", - "conditions": [ - { - "field": "logFileStats.recordCount", - "operator": ">=", - "value": 50 - } - ] + "description": "xxxxxxx", + "dismissForMinutes": "10", + "samplingRate": "1.0", + "conditions": [ + { + "field": "logFileStats.recordCount", + "operator": ">=", + "value": 50 + } + ], + "buttons": [] }, { "uniqueId": "67890", - "url": "xxxxx", "startDate": "2023-01-01T09:00:00-07:00", "endDate": "2023-12-31T09:00:00-07:00", - "description": "xxxxxxx", - "dismissForMinutes": "10", - "moreInfoURL": "xxxxxx", - "samplingRate": "1.0", - "conditions": [ - { - "field": "logFileStats.recordCount", - "operator": ">=", - "value": 50 - } - ] + "description": "xxxxxxx", + "dismissForMinutes": "10", + "samplingRate": "1.0", + "conditions": [ + { + "field": "logFileStats.recordCount", + "operator": ">=", + "value": 50 + } + ], + "buttons": [ + { + "buttonText": "More Info", + "action": "snooze", + "url": "https://docs.flutter.dev/reference/crash-reporting" + } + ] } ] '''), @@ -514,6 +556,12 @@ void main() { expect(firstSurvey.uniqueId, '12345'); expect(secondSurvey.uniqueId, '67890'); + + final secondSurveyButtons = secondSurvey.surveyButtonList; + expect(secondSurveyButtons.length, 1); + expect(secondSurveyButtons.first.buttonText, 'More Info'); + expect(secondSurveyButtons.first.action, ButtonAction.snooze); + expect(secondSurveyButtons.first.url, 'https://docs.flutter.dev/reference/crash-reporting'); }); }); From 6d4b9a45a99446187ca20ac9fdc9c461de666628 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:58:15 -0400 Subject: [PATCH 06/14] Remove enum for status of action --- pkgs/unified_analytics/lib/src/analytics.dart | 7 +++-- .../lib/src/survey_handler.dart | 27 ++----------------- .../test/events_with_fake_test.dart | 8 +++--- .../test/survey_handler_test.dart | 16 +++++------ 4 files changed, 17 insertions(+), 41 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart index e1ea307603..02574c5e16 100644 --- a/pkgs/unified_analytics/lib/src/analytics.dart +++ b/pkgs/unified_analytics/lib/src/analytics.dart @@ -221,7 +221,7 @@ abstract class Analytics { /// /// [surveyAccepted] indicates if the user opened the survey if `true` /// or `false` if the user rejects to open it - void dismissSurvey({required Survey survey, required bool surveyAccepted}); + void dismissSurvey({required Survey survey, required String status}); /// Method to fetch surveys from the specified endpoint [kContextualSurveyUrl] /// @@ -454,9 +454,8 @@ class AnalyticsImpl implements Analytics { void close() => _gaClient.close(); @override - void dismissSurvey({required Survey survey, required bool surveyAccepted}) { + void dismissSurvey({required Survey survey, required String status}) { _surveyHandler.dismiss(survey, true); - final status = surveyAccepted ? 'accepted' : 'dismissed'; send(Event.surveyAction(surveyId: survey.uniqueId, status: status)); } @@ -635,7 +634,7 @@ class NoOpAnalytics implements Analytics { void close() {} @override - void dismissSurvey({required Survey survey, required bool surveyAccepted}) {} + void dismissSurvey({required Survey survey, required String status}) {} @override Future> fetchAvailableSurveys() async => const []; diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index 20a281641c..2603996c24 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -13,24 +13,6 @@ import 'package:unified_analytics/src/initializer.dart'; import 'constants.dart'; import 'log_handler.dart'; -enum ButtonAction { - /// The user has decided to accept the survey being passed to them - /// - /// This will permanently dismiss the survey from showing up again - accept, - - /// The user has decided to dismiss the survey by clicking the dismiss - /// button being provided the survey - /// - /// This will permanently dismiss the survey from showing up again - dismiss, - - /// If this button is provided by a survey, it will temporarily dismiss - /// the survey from being prompted to the user for a period of time - /// specified in [Survey] field [dismissForMinutes] - snooze, -} - class Condition { /// How to query the log file /// @@ -169,7 +151,7 @@ class Survey { class SurveyButton { final String buttonText; - final ButtonAction action; + final String action; final String? url; SurveyButton({ @@ -180,12 +162,7 @@ class SurveyButton { SurveyButton.fromJson(Map json) : buttonText = json['buttonText'] as String, - action = switch (json['action']) { - 'accept' => ButtonAction.accept, - 'dismiss' => ButtonAction.dismiss, - 'snooze' => ButtonAction.snooze, - _ => throw ArgumentError('Action for button not valid') - }, + action = json['action'] as String, url = json['url'] as String?; } diff --git a/pkgs/unified_analytics/test/events_with_fake_test.dart b/pkgs/unified_analytics/test/events_with_fake_test.dart index 2c2f79e60b..46f0d62369 100644 --- a/pkgs/unified_analytics/test/events_with_fake_test.dart +++ b/pkgs/unified_analytics/test/events_with_fake_test.dart @@ -110,12 +110,12 @@ void main() { expect(survey.uniqueId, 'uniqueId'); // Simulate the survey being shown - fakeAnalytics.dismissSurvey(survey: survey, surveyAccepted: true); + fakeAnalytics.dismissSurvey(survey: survey, status: 'accept'); expect(fakeAnalytics.sentEvents.length, 2); expect(fakeAnalytics.sentEvents.last.eventName, DashEvent.surveyAction); expect(fakeAnalytics.sentEvents.last.eventData, - {'surveyId': 'uniqueId', 'status': 'accepted'}); + {'surveyId': 'uniqueId', 'status': 'accept'}); }); test('event sent when survey rejected', () async { @@ -131,11 +131,11 @@ void main() { expect(survey.uniqueId, 'uniqueId'); // Simulate the survey being shown - fakeAnalytics.dismissSurvey(survey: survey, surveyAccepted: false); + fakeAnalytics.dismissSurvey(survey: survey, status: 'dismiss'); expect(fakeAnalytics.sentEvents.length, 2); expect(fakeAnalytics.sentEvents.last.eventName, DashEvent.surveyAction); expect(fakeAnalytics.sentEvents.last.eventData, - {'surveyId': 'uniqueId', 'status': 'dismissed'}); + {'surveyId': 'uniqueId', 'status': 'dismiss'}); }); } diff --git a/pkgs/unified_analytics/test/survey_handler_test.dart b/pkgs/unified_analytics/test/survey_handler_test.dart index 8be0540a27..d26f7aeaab 100644 --- a/pkgs/unified_analytics/test/survey_handler_test.dart +++ b/pkgs/unified_analytics/test/survey_handler_test.dart @@ -222,7 +222,7 @@ void main() { surveyButtonList: [ SurveyButton( buttonText: 'buttonText', - action: ButtonAction.accept, + action: 'accept', url: 'http://example.com', ), ], @@ -412,16 +412,16 @@ void main() { final buttonList = survey.surveyButtonList; expect(buttonList.length, 3); expect(buttonList.first.buttonText, 'Take Survey'); - expect(buttonList.first.action, ButtonAction.accept); + expect(buttonList.first.action, 'accept'); expect(buttonList.first.url, 'https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2'); expect(buttonList.elementAt(1).buttonText, 'Dismiss'); - expect(buttonList.elementAt(1).action, ButtonAction.dismiss); + expect(buttonList.elementAt(1).action, 'dismiss'); expect(buttonList.elementAt(1).url, isNull); expect(buttonList.last.buttonText, 'More Info'); - expect(buttonList.last.action, ButtonAction.snooze); + expect(buttonList.last.action, 'snooze'); expect(buttonList.last.url, 'https://docs.flutter.dev/reference/crash-reporting'); }); @@ -560,7 +560,7 @@ void main() { final secondSurveyButtons = secondSurvey.surveyButtonList; expect(secondSurveyButtons.length, 1); expect(secondSurveyButtons.first.buttonText, 'More Info'); - expect(secondSurveyButtons.first.action, ButtonAction.snooze); + expect(secondSurveyButtons.first.action, 'snooze'); expect(secondSurveyButtons.first.url, 'https://docs.flutter.dev/reference/crash-reporting'); }); }); @@ -856,7 +856,7 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - analytics.dismissSurvey(survey: survey, surveyAccepted: true); + analytics.dismissSurvey(survey: survey, status: 'accept'); }); // Moving out a week @@ -922,7 +922,7 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - analytics.dismissSurvey(survey: survey, surveyAccepted: true); + analytics.dismissSurvey(survey: survey, status: 'accept'); }); // Purposefully write invalid json into the persisted file @@ -991,7 +991,7 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - analytics.dismissSurvey(survey: survey, surveyAccepted: true); + analytics.dismissSurvey(survey: survey, status: 'accept'); }); // Moving out a week From f68eb896c36223d4b72a612a88ac2500a0921651 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:04:54 -0400 Subject: [PATCH 07/14] Use `snoozeForMinutes` instead of dismiss --- pkgs/unified_analytics/lib/src/analytics.dart | 2 +- .../lib/src/survey_handler.dart | 12 +++--- pkgs/unified_analytics/lib/src/utils.dart | 2 +- .../test/events_with_fake_test.dart | 2 +- .../test/survey_handler_test.dart | 40 +++++++++---------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart index 02574c5e16..3134cb5731 100644 --- a/pkgs/unified_analytics/lib/src/analytics.dart +++ b/pkgs/unified_analytics/lib/src/analytics.dart @@ -264,7 +264,7 @@ abstract class Analytics { /// /// Calling this will snooze the survey so it won't be shown immediately /// - /// The snooze period is defined within the `dismissForMinutes` + /// The snooze period is defined within the `snoozeForMinutes` /// field in [Survey] void surveyShown(Survey survey); } diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index 2603996c24..f11b8cbeee 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -93,7 +93,7 @@ class Survey { final DateTime startDate; final DateTime endDate; final String description; - final int dismissForMinutes; + final int snoozeForMinutes; final double samplingRate; final List conditionList; final List surveyButtonList; @@ -105,7 +105,7 @@ class Survey { required this.startDate, required this.endDate, required this.description, - required this.dismissForMinutes, + required this.snoozeForMinutes, required this.samplingRate, required this.conditionList, required this.surveyButtonList, @@ -118,9 +118,9 @@ class Survey { endDate = DateTime.parse(json['endDate'] as String), description = json['description'] as String, // Handle both string and integer fields - dismissForMinutes = json['dismissForMinutes'] is String - ? int.parse(json['dismissForMinutes'] as String) - : json['dismissForMinutes'] as int, + snoozeForMinutes = json['snoozeForMinutes'] is String + ? int.parse(json['snoozeForMinutes'] as String) + : json['snoozeForMinutes'] as int, // Handle both string and double fields samplingRate = json['samplingRate'] is String ? double.parse(json['samplingRate'] as String) @@ -142,7 +142,7 @@ class Survey { 'startDate': startDate.toString(), 'endDate': endDate.toString(), 'description': description, - 'dismissForMinutes': dismissForMinutes, + 'snoozeForMinutes': snoozeForMinutes, 'samplingRate': samplingRate, 'conditionList': conditionList.map((e) => e.toMap()).toList(), }); diff --git a/pkgs/unified_analytics/lib/src/utils.dart b/pkgs/unified_analytics/lib/src/utils.dart index 91e4f2a35d..b348b842bc 100644 --- a/pkgs/unified_analytics/lib/src/utils.dart +++ b/pkgs/unified_analytics/lib/src/utils.dart @@ -235,7 +235,7 @@ bool surveySnoozedOrDismissed( final minutesElapsed = clock.now().difference(persistedSurveyObj.timestamp).inMinutes; - return survey.dismissForMinutes > minutesElapsed; + return survey.snoozeForMinutes > minutesElapsed; } /// A UUID generator. diff --git a/pkgs/unified_analytics/test/events_with_fake_test.dart b/pkgs/unified_analytics/test/events_with_fake_test.dart index 46f0d62369..226870fb18 100644 --- a/pkgs/unified_analytics/test/events_with_fake_test.dart +++ b/pkgs/unified_analytics/test/events_with_fake_test.dart @@ -30,7 +30,7 @@ void main() { startDate: DateTime(2022, 1, 1), endDate: DateTime(2022, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 1.0, // 100% sample rate conditionList: [], surveyButtonList: [], diff --git a/pkgs/unified_analytics/test/survey_handler_test.dart b/pkgs/unified_analytics/test/survey_handler_test.dart index d26f7aeaab..c8f9eb42c1 100644 --- a/pkgs/unified_analytics/test/survey_handler_test.dart +++ b/pkgs/unified_analytics/test/survey_handler_test.dart @@ -27,7 +27,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [], surveyButtonList: [], @@ -37,7 +37,7 @@ void main() { startDate: DateTime(2022, 1, 1), endDate: DateTime(2022, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [], surveyButtonList: [], @@ -66,7 +66,7 @@ void main() { "startDate": "2023-06-01T09:00:00-07:00", "endDate": "2023-06-30T09:00:00-07:00", "description": "xxxxxxx", - "dismissForMinutes": "10", + "snoozeForMinutes": "10", "samplingRate": "1.0", "conditions": [ { @@ -98,7 +98,7 @@ void main() { "startDate": "2023-06-01T09:00:00-07:00", "endDate": "2023-06-30T09:00:00-07:00", "description": "xxxxxxx", - "dismissForMinutes": "10", + "snoozeForMinutes": "10", "samplingRate": "1.0", "conditions": [ { @@ -213,7 +213,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), @@ -265,7 +265,7 @@ void main() { startDate: DateTime(2022, 1, 1), endDate: DateTime(2022, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), @@ -307,7 +307,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), @@ -351,7 +351,7 @@ void main() { "startDate": "2023-01-01T09:00:00-07:00", "endDate": "2023-12-31T09:00:00-07:00", "description": "description123", - "dismissForMinutes": "10", + "snoozeForMinutes": "10", "samplingRate": "1.0", "conditions": [ { @@ -400,7 +400,7 @@ void main() { expect(survey.endDate.month, 12); expect(survey.endDate.day, 31); expect(survey.description, 'description123'); - expect(survey.dismissForMinutes, 10); + expect(survey.snoozeForMinutes, 10); expect(survey.samplingRate, 1.0); expect(survey.conditionList.length, 1); @@ -445,7 +445,7 @@ void main() { "startDate": "NOT A REAL DATE", "endDate": "2023-07-30T09:00:00-07:00", "description": "Help improve Flutter's release builds with this 3-question survey!", - "dismissForMinutes": "7200", + "snoozeForMinutes": "7200", "samplingRate": "0.1", "conditions": [ { @@ -505,7 +505,7 @@ void main() { "startDate": "2023-01-01T09:00:00-07:00", "endDate": "2023-12-31T09:00:00-07:00", "description": "xxxxxxx", - "dismissForMinutes": "10", + "snoozeForMinutes": "10", "samplingRate": "1.0", "conditions": [ { @@ -521,7 +521,7 @@ void main() { "startDate": "2023-01-01T09:00:00-07:00", "endDate": "2023-12-31T09:00:00-07:00", "description": "xxxxxxx", - "dismissForMinutes": "10", + "snoozeForMinutes": "10", "samplingRate": "1.0", "conditions": [ { @@ -584,7 +584,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), @@ -642,7 +642,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 0.6, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), @@ -687,7 +687,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: 10, + snoozeForMinutes: 10, samplingRate: 0.15, conditionList: [ Condition('logFileStats.recordCount', '>=', 50), @@ -734,7 +734,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: + snoozeForMinutes: minutesToSnooze, // Initialized survey with `minutesToSnooze` samplingRate: 1.0, conditionList: [], @@ -764,7 +764,7 @@ void main() { expect(fetchedSurveys.length, 1); final survey = fetchedSurveys.first; - expect(survey.dismissForMinutes, minutesToSnooze); + expect(survey.snoozeForMinutes, minutesToSnooze); // We will snooze the survey now and it should not show up // if we fetch surveys again before the minutes to snooze time @@ -825,7 +825,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: minutesToSnooze, + snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], surveyButtonList: [], @@ -891,7 +891,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: minutesToSnooze, + snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], surveyButtonList: [], @@ -960,7 +960,7 @@ void main() { startDate: DateTime(2023, 1, 1), endDate: DateTime(2023, 12, 31), description: 'description', - dismissForMinutes: minutesToSnooze, + snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], surveyButtonList: [], From 74b779108a7ad938c4453a08b8d48eced7a395bc Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 1 Aug 2023 09:30:23 -0400 Subject: [PATCH 08/14] Expose `SurveyButton` --- pkgs/unified_analytics/lib/unified_analytics.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/unified_analytics/lib/unified_analytics.dart b/pkgs/unified_analytics/lib/unified_analytics.dart index b11be53cee..1531eb1425 100644 --- a/pkgs/unified_analytics/lib/unified_analytics.dart +++ b/pkgs/unified_analytics/lib/unified_analytics.dart @@ -7,4 +7,4 @@ export 'src/config_handler.dart' show ToolInfo; export 'src/enums.dart' show DashTool; export 'src/event.dart' show Event; export 'src/log_handler.dart' show LogFileStats; -export 'src/survey_handler.dart' show FakeSurveyHandler, SurveyHandler; +export 'src/survey_handler.dart' show SurveyButton, SurveyHandler; From e5b8a74e351bcd706cae98e5360c94bfb72f634b Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 1 Aug 2023 09:35:24 -0400 Subject: [PATCH 09/14] Fixing documentation for event class --- pkgs/unified_analytics/lib/src/event.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/event.dart b/pkgs/unified_analytics/lib/src/event.dart index 015dbbe312..5b809d992a 100644 --- a/pkgs/unified_analytics/lib/src/event.dart +++ b/pkgs/unified_analytics/lib/src/event.dart @@ -320,8 +320,8 @@ final class Event { /// /// [surveyId] - the unique id for a given survey /// - /// [status] - `'accepted'` if the user accepted the survey, or - /// `'dismissed'` if the user rejected it + /// [status] - the string identifier for a given [SurveyButton] under + /// the `action` field Event.surveyAction({ required String surveyId, required String status, From 313226c7e685f4e92b6b963ebf3e60b4645a7cb3 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 1 Aug 2023 11:59:21 -0400 Subject: [PATCH 10/14] Order members in survey handler --- .../lib/src/survey_handler.dart | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index f11b8cbeee..c47bea608e 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -178,16 +178,6 @@ class SurveyHandler { kDismissedSurveyFileName, )); - /// Function to ensure that each survey is still valid by - /// checking the [Survey.startDate] and [Survey.endDate] - /// against the current [clock.now()] date - static bool checkSurveyDate(Survey survey) { - if (survey.startDate.isBefore(clock.now()) && - survey.endDate.isAfter(clock.now())) return true; - - return false; - } - /// Invoking this method will persist the survey's id in /// the local file with either a snooze or permanently dismissed /// indicator @@ -260,26 +250,6 @@ class SurveyHandler { return surveyList; } - /// Function that takes in a json data structure that is in - /// the form of a list and returns a list of [Survey]s - /// - /// This will also check the survey's dates to make sure it - /// has not expired - static List parseSurveysFromJson(List body) => body - .map((element) { - // Error handling to skip any surveys from the remote location - // that fail to parse - try { - return Survey.fromJson(element as Map); - // ignore: avoid_catches_without_on_clauses - } catch (err) { - return null; - } - }) - .whereType() - .where(checkSurveyDate) - .toList(); - /// Fetches the json in string form from the remote location Future _fetchContents() async { final uri = Uri.parse(kContextualSurveyUrl); @@ -305,6 +275,36 @@ class SurveyHandler { return contents; } + + /// Function to ensure that each survey is still valid by + /// checking the [Survey.startDate] and [Survey.endDate] + /// against the current [clock.now()] date + static bool checkSurveyDate(Survey survey) { + if (survey.startDate.isBefore(clock.now()) && + survey.endDate.isAfter(clock.now())) return true; + + return false; + } + + /// Function that takes in a json data structure that is in + /// the form of a list and returns a list of [Survey]s + /// + /// This will also check the survey's dates to make sure it + /// has not expired + static List parseSurveysFromJson(List body) => body + .map((element) { + // Error handling to skip any surveys from the remote location + // that fail to parse + try { + return Survey.fromJson(element as Map); + // ignore: avoid_catches_without_on_clauses + } catch (err) { + return null; + } + }) + .whereType() + .where(checkSurveyDate) + .toList(); } class FakeSurveyHandler extends SurveyHandler { From 49b80143d20138576b2142a1310e0573a7861a13 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 1 Aug 2023 15:25:43 -0400 Subject: [PATCH 11/14] Refactor to pass button to `surveyInteracted(..)` --- pkgs/unified_analytics/lib/src/analytics.dart | 49 ++++++---- .../lib/src/survey_handler.dart | 11 +++ .../test/events_with_fake_test.dart | 27 +++++- .../test/survey_handler_test.dart | 97 +++++++++++++++---- 4 files changed, 146 insertions(+), 38 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/analytics.dart b/pkgs/unified_analytics/lib/src/analytics.dart index 3134cb5731..dc71e37d67 100644 --- a/pkgs/unified_analytics/lib/src/analytics.dart +++ b/pkgs/unified_analytics/lib/src/analytics.dart @@ -214,15 +214,6 @@ abstract class Analytics { /// that need to be sent off void close(); - /// Method to dismiss a survey permanently - /// - /// Pass a [Survey] instance which can be retrieved from - /// `fetchAvailableSurveys()` - /// - /// [surveyAccepted] indicates if the user opened the survey if `true` - /// or `false` if the user rejects to open it - void dismissSurvey({required Survey survey, required String status}); - /// Method to fetch surveys from the specified endpoint [kContextualSurveyUrl] /// /// Any survey that is returned by this method has already passed @@ -260,6 +251,17 @@ abstract class Analytics { /// collection use `setTelemetry(false)` void suppressTelemetry(); + /// Method to run after interacting with a [Survey] + /// + /// Pass a [Survey] instance which can be retrieved from + /// `fetchAvailableSurveys()` + /// + /// [sureyButton] is the button that was interacted with by the user + void surveyInteracted({ + required Survey survey, + required SurveyButton surveyButton, + }); + /// Method to be called after a survey has been shown to the user /// /// Calling this will snooze the survey so it won't be shown immediately @@ -453,12 +455,6 @@ class AnalyticsImpl implements Analytics { @override void close() => _gaClient.close(); - @override - void dismissSurvey({required Survey survey, required String status}) { - _surveyHandler.dismiss(survey, true); - send(Event.surveyAction(surveyId: survey.uniqueId, status: status)); - } - @override Future> fetchAvailableSurveys() async { final surveysToShow = []; @@ -592,6 +588,20 @@ class AnalyticsImpl implements Analytics { @override void suppressTelemetry() => _telemetrySuppressed = true; + @override + void surveyInteracted({ + required Survey survey, + required SurveyButton surveyButton, + }) { + // Any action, except for 'snooze' will permanently dismiss a given survey + final permanentlyDismissed = surveyButton.action == 'snooze' ? false : true; + _surveyHandler.dismiss(survey, permanentlyDismissed); + send(Event.surveyAction( + surveyId: survey.uniqueId, + status: surveyButton.action, + )); + } + @override void surveyShown(Survey survey) { _surveyHandler.dismiss(survey, false); @@ -633,9 +643,6 @@ class NoOpAnalytics implements Analytics { @override void close() {} - @override - void dismissSurvey({required Survey survey, required String status}) {} - @override Future> fetchAvailableSurveys() async => const []; @@ -651,6 +658,12 @@ class NoOpAnalytics implements Analytics { @override void suppressTelemetry() {} + @override + void surveyInteracted({ + required Survey survey, + required SurveyButton surveyButton, + }) {} + @override void surveyShown(Survey survey) {} } diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index c47bea608e..4e4aaa2da3 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -145,6 +145,7 @@ class Survey { 'snoozeForMinutes': snoozeForMinutes, 'samplingRate': samplingRate, 'conditionList': conditionList.map((e) => e.toMap()).toList(), + 'surveyButtonList': surveyButtonList.map((e) => e.toMap()).toList(), }); } } @@ -152,18 +153,28 @@ class Survey { class SurveyButton { final String buttonText; final String action; + final bool promptRemainsVisible; final String? url; SurveyButton({ required this.buttonText, required this.action, + required this.promptRemainsVisible, this.url, }); SurveyButton.fromJson(Map json) : buttonText = json['buttonText'] as String, action = json['action'] as String, + promptRemainsVisible = json['promptRemainsVisible'] as bool, url = json['url'] as String?; + + Map toMap() => { + 'buttonText': buttonText, + 'action': action, + 'promptRemainsVisible': promptRemainsVisible, + 'url': url, + }; } class SurveyHandler { diff --git a/pkgs/unified_analytics/test/events_with_fake_test.dart b/pkgs/unified_analytics/test/events_with_fake_test.dart index 226870fb18..111ca74d8a 100644 --- a/pkgs/unified_analytics/test/events_with_fake_test.dart +++ b/pkgs/unified_analytics/test/events_with_fake_test.dart @@ -33,7 +33,18 @@ void main() { snoozeForMinutes: 10, samplingRate: 1.0, // 100% sample rate conditionList: [], - surveyButtonList: [], + surveyButtonList: [ + SurveyButton( + buttonText: 'buttonText', + action: 'accept', + promptRemainsVisible: false, + ), + SurveyButton( + buttonText: 'buttonText', + action: 'dismiss', + promptRemainsVisible: false, + ), + ], ); /// Test event that will need to be sent since surveys won't @@ -110,7 +121,12 @@ void main() { expect(survey.uniqueId, 'uniqueId'); // Simulate the survey being shown - fakeAnalytics.dismissSurvey(survey: survey, status: 'accept'); + // + // The first button is the accept button + fakeAnalytics.surveyInteracted( + survey: survey, + surveyButton: survey.surveyButtonList.first, + ); expect(fakeAnalytics.sentEvents.length, 2); expect(fakeAnalytics.sentEvents.last.eventName, DashEvent.surveyAction); @@ -131,7 +147,12 @@ void main() { expect(survey.uniqueId, 'uniqueId'); // Simulate the survey being shown - fakeAnalytics.dismissSurvey(survey: survey, status: 'dismiss'); + // + // The last button is the reject button + fakeAnalytics.surveyInteracted( + survey: survey, + surveyButton: survey.surveyButtonList.last, + ); expect(fakeAnalytics.sentEvents.length, 2); expect(fakeAnalytics.sentEvents.last.eventName, DashEvent.surveyAction); diff --git a/pkgs/unified_analytics/test/survey_handler_test.dart b/pkgs/unified_analytics/test/survey_handler_test.dart index c8f9eb42c1..2dbcd881d4 100644 --- a/pkgs/unified_analytics/test/survey_handler_test.dart +++ b/pkgs/unified_analytics/test/survey_handler_test.dart @@ -84,7 +84,8 @@ void main() { { "buttonText": "Take Survey", "action": "accept", - "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2" + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2", + "promptRemainsVisible": false } ] } @@ -111,7 +112,8 @@ void main() { { "buttonText": "Take Survey", "action": "accept", - "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2" + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2", + "promptRemainsVisible": false } ] } @@ -138,6 +140,8 @@ void main() { expect(secondCondition.value, 3); expect(parsedSurveys.first.surveyButtonList.length, 1); + expect(parsedSurveys.first.surveyButtonList.first.promptRemainsVisible, + false); }); }); @@ -224,6 +228,7 @@ void main() { buttonText: 'buttonText', action: 'accept', url: 'http://example.com', + promptRemainsVisible: false, ), ], ), @@ -239,7 +244,7 @@ void main() { final fetchedSurveys = await analytics.fetchAvailableSurveys(); expect(fetchedSurveys.length, 1); - + final survey = fetchedSurveys.first; expect(survey.conditionList.length, 2); expect(survey.surveyButtonList.length, 1); @@ -364,17 +369,20 @@ void main() { { "buttonText": "Take Survey", "action": "accept", - "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2" + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2", + "promptRemainsVisible": false }, { "buttonText": "Dismiss", "action": "dismiss", - "url": null + "url": null, + "promptRemainsVisible": false }, { "buttonText": "More Info", "action": "snooze", - "url": "https://docs.flutter.dev/reference/crash-reporting" + "url": "https://docs.flutter.dev/reference/crash-reporting", + "promptRemainsVisible": true } ] } @@ -415,19 +423,23 @@ void main() { expect(buttonList.first.action, 'accept'); expect(buttonList.first.url, 'https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2'); + expect(buttonList.first.promptRemainsVisible, false); expect(buttonList.elementAt(1).buttonText, 'Dismiss'); expect(buttonList.elementAt(1).action, 'dismiss'); expect(buttonList.elementAt(1).url, isNull); + expect(buttonList.elementAt(1).promptRemainsVisible, false); expect(buttonList.last.buttonText, 'More Info'); expect(buttonList.last.action, 'snooze'); expect(buttonList.last.url, 'https://docs.flutter.dev/reference/crash-reporting'); + expect(buttonList.last.promptRemainsVisible, true); }); }); test('no survey returned from malformed json', () async { + // The date is not valid for the start date await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { analytics = Analytics.test( tool: DashTool.flutterTool, @@ -458,17 +470,20 @@ void main() { { "buttonText": "Take Survey", "action": "accept", - "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2" + "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2", + "promptRemainsVisible": false }, { "buttonText": "Dismiss", "action": "dismiss", - "url": null + "url": null, + "promptRemainsVisible": false }, { "buttonText": "More Info", "action": "snooze", - "url": "https://docs.flutter.dev/reference/crash-reporting" + "url": "https://docs.flutter.dev/reference/crash-reporting", + "promptRemainsVisible": false } ] } @@ -534,7 +549,8 @@ void main() { { "buttonText": "More Info", "action": "snooze", - "url": "https://docs.flutter.dev/reference/crash-reporting" + "url": "https://docs.flutter.dev/reference/crash-reporting", + "promptRemainsVisible": true } ] } @@ -561,7 +577,10 @@ void main() { expect(secondSurveyButtons.length, 1); expect(secondSurveyButtons.first.buttonText, 'More Info'); expect(secondSurveyButtons.first.action, 'snooze'); - expect(secondSurveyButtons.first.url, 'https://docs.flutter.dev/reference/crash-reporting'); + expect(secondSurveyButtons.first.url, + 'https://docs.flutter.dev/reference/crash-reporting'); + expect(secondSurveyButtons.first.promptRemainsVisible, + true); }); }); @@ -828,7 +847,18 @@ void main() { snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], - surveyButtonList: [], + surveyButtonList: [ + SurveyButton( + buttonText: 'buttonText', + action: 'accept', + promptRemainsVisible: false, + ), + SurveyButton( + buttonText: 'buttonText', + action: 'dismiss', + promptRemainsVisible: false, + ), + ], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { @@ -856,7 +886,10 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - analytics.dismissSurvey(survey: survey, status: 'accept'); + analytics.surveyInteracted( + survey: survey, + surveyButton: survey.surveyButtonList.first, + ); }); // Moving out a week @@ -894,7 +927,18 @@ void main() { snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], - surveyButtonList: [], + surveyButtonList: [ + SurveyButton( + buttonText: 'buttonText', + action: 'accept', + promptRemainsVisible: false, + ), + SurveyButton( + buttonText: 'buttonText', + action: 'dismiss', + promptRemainsVisible: false, + ), + ], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { @@ -922,7 +966,11 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - analytics.dismissSurvey(survey: survey, status: 'accept'); + expect(survey.surveyButtonList.length, 2); + analytics.surveyInteracted( + survey: survey, + surveyButton: survey.surveyButtonList.first, + ); }); // Purposefully write invalid json into the persisted file @@ -963,7 +1011,18 @@ void main() { snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], - surveyButtonList: [], + surveyButtonList: [ + SurveyButton( + buttonText: 'buttonText', + action: 'accept', + promptRemainsVisible: false, + ), + SurveyButton( + buttonText: 'buttonText', + action: 'dismiss', + promptRemainsVisible: false, + ), + ], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { @@ -991,7 +1050,11 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - analytics.dismissSurvey(survey: survey, status: 'accept'); + expect(survey.surveyButtonList.length, 2); + analytics.surveyInteracted( + survey: survey, + surveyButton: survey.surveyButtonList.first, + ); }); // Moving out a week From 8475e53ce1dea2cc7f5a43f151d9ad363e4f8bbb Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 1 Aug 2023 15:33:07 -0400 Subject: [PATCH 12/14] `surveyButtonList` --> `buttonList` renaming --- .../lib/src/survey_handler.dart | 8 ++-- .../test/events_with_fake_test.dart | 6 +-- .../test/survey_handler_test.dart | 44 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pkgs/unified_analytics/lib/src/survey_handler.dart b/pkgs/unified_analytics/lib/src/survey_handler.dart index 4e4aaa2da3..b9cf7aefc7 100644 --- a/pkgs/unified_analytics/lib/src/survey_handler.dart +++ b/pkgs/unified_analytics/lib/src/survey_handler.dart @@ -96,7 +96,7 @@ class Survey { final int snoozeForMinutes; final double samplingRate; final List conditionList; - final List surveyButtonList; + final List buttonList; /// A data class that contains the relevant information for a given /// survey parsed from the survey's metadata file @@ -108,7 +108,7 @@ class Survey { required this.snoozeForMinutes, required this.samplingRate, required this.conditionList, - required this.surveyButtonList, + required this.buttonList, }); /// Parse the contents of the json metadata file hosted externally @@ -129,7 +129,7 @@ class Survey { e as Map; return Condition.fromJson(e); }).toList(), - surveyButtonList = (json['buttons'] as List).map((e) { + buttonList = (json['buttons'] as List).map((e) { e as Map; return SurveyButton.fromJson(e); }).toList(); @@ -145,7 +145,7 @@ class Survey { 'snoozeForMinutes': snoozeForMinutes, 'samplingRate': samplingRate, 'conditionList': conditionList.map((e) => e.toMap()).toList(), - 'surveyButtonList': surveyButtonList.map((e) => e.toMap()).toList(), + 'buttonList': buttonList.map((e) => e.toMap()).toList(), }); } } diff --git a/pkgs/unified_analytics/test/events_with_fake_test.dart b/pkgs/unified_analytics/test/events_with_fake_test.dart index 111ca74d8a..5bf8785cf1 100644 --- a/pkgs/unified_analytics/test/events_with_fake_test.dart +++ b/pkgs/unified_analytics/test/events_with_fake_test.dart @@ -33,7 +33,7 @@ void main() { snoozeForMinutes: 10, samplingRate: 1.0, // 100% sample rate conditionList: [], - surveyButtonList: [ + buttonList: [ SurveyButton( buttonText: 'buttonText', action: 'accept', @@ -125,7 +125,7 @@ void main() { // The first button is the accept button fakeAnalytics.surveyInteracted( survey: survey, - surveyButton: survey.surveyButtonList.first, + surveyButton: survey.buttonList.first, ); expect(fakeAnalytics.sentEvents.length, 2); @@ -151,7 +151,7 @@ void main() { // The last button is the reject button fakeAnalytics.surveyInteracted( survey: survey, - surveyButton: survey.surveyButtonList.last, + surveyButton: survey.buttonList.last, ); expect(fakeAnalytics.sentEvents.length, 2); diff --git a/pkgs/unified_analytics/test/survey_handler_test.dart b/pkgs/unified_analytics/test/survey_handler_test.dart index 2dbcd881d4..b104a82e49 100644 --- a/pkgs/unified_analytics/test/survey_handler_test.dart +++ b/pkgs/unified_analytics/test/survey_handler_test.dart @@ -30,7 +30,7 @@ void main() { snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [], - surveyButtonList: [], + buttonList: [], ); final invalidSurvey = Survey( uniqueId: 'uniqueId', @@ -40,7 +40,7 @@ void main() { snoozeForMinutes: 10, samplingRate: 1.0, conditionList: [], - surveyButtonList: [], + buttonList: [], ); test('expired survey', () { @@ -139,8 +139,8 @@ void main() { expect(secondCondition.operatorString, '<'); expect(secondCondition.value, 3); - expect(parsedSurveys.first.surveyButtonList.length, 1); - expect(parsedSurveys.first.surveyButtonList.first.promptRemainsVisible, + expect(parsedSurveys.first.buttonList.length, 1); + expect(parsedSurveys.first.buttonList.first.promptRemainsVisible, false); }); }); @@ -223,7 +223,7 @@ void main() { Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], - surveyButtonList: [ + buttonList: [ SurveyButton( buttonText: 'buttonText', action: 'accept', @@ -247,7 +247,7 @@ void main() { final survey = fetchedSurveys.first; expect(survey.conditionList.length, 2); - expect(survey.surveyButtonList.length, 1); + expect(survey.buttonList.length, 1); }); }); @@ -276,7 +276,7 @@ void main() { Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], - surveyButtonList: [], + buttonList: [], ), ], ), @@ -318,7 +318,7 @@ void main() { Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], - surveyButtonList: [], + buttonList: [], ), ], ), @@ -417,7 +417,7 @@ void main() { expect(condition.operatorString, '>='); expect(condition.value, 50); - final buttonList = survey.surveyButtonList; + final buttonList = survey.buttonList; expect(buttonList.length, 3); expect(buttonList.first.buttonText, 'Take Survey'); expect(buttonList.first.action, 'accept'); @@ -573,7 +573,7 @@ void main() { expect(firstSurvey.uniqueId, '12345'); expect(secondSurvey.uniqueId, '67890'); - final secondSurveyButtons = secondSurvey.surveyButtonList; + final secondSurveyButtons = secondSurvey.buttonList; expect(secondSurveyButtons.length, 1); expect(secondSurveyButtons.first.buttonText, 'More Info'); expect(secondSurveyButtons.first.action, 'snooze'); @@ -609,7 +609,7 @@ void main() { Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], - surveyButtonList: [], + buttonList: [], ), ], ), @@ -667,7 +667,7 @@ void main() { Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], - surveyButtonList: [], + buttonList: [], ); analytics = Analytics.test( tool: DashTool.flutterTool, @@ -712,7 +712,7 @@ void main() { Condition('logFileStats.recordCount', '>=', 50), Condition('logFileStats.toolCount.flutter-tool', '>', 0), ], - surveyButtonList: [], + buttonList: [], ); analytics = Analytics.test( tool: DashTool.flutterTool, @@ -757,7 +757,7 @@ void main() { minutesToSnooze, // Initialized survey with `minutesToSnooze` samplingRate: 1.0, conditionList: [], - surveyButtonList: [], + buttonList: [], ); await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { @@ -847,7 +847,7 @@ void main() { snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], - surveyButtonList: [ + buttonList: [ SurveyButton( buttonText: 'buttonText', action: 'accept', @@ -888,7 +888,7 @@ void main() { final survey = fetchedSurveys.first; analytics.surveyInteracted( survey: survey, - surveyButton: survey.surveyButtonList.first, + surveyButton: survey.buttonList.first, ); }); @@ -927,7 +927,7 @@ void main() { snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], - surveyButtonList: [ + buttonList: [ SurveyButton( buttonText: 'buttonText', action: 'accept', @@ -966,10 +966,10 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - expect(survey.surveyButtonList.length, 2); + expect(survey.buttonList.length, 2); analytics.surveyInteracted( survey: survey, - surveyButton: survey.surveyButtonList.first, + surveyButton: survey.buttonList.first, ); }); @@ -1011,7 +1011,7 @@ void main() { snoozeForMinutes: minutesToSnooze, samplingRate: 1.0, conditionList: [], - surveyButtonList: [ + buttonList: [ SurveyButton( buttonText: 'buttonText', action: 'accept', @@ -1050,10 +1050,10 @@ void main() { // Dismissing permanently will ensure that this survey is not // shown again final survey = fetchedSurveys.first; - expect(survey.surveyButtonList.length, 2); + expect(survey.buttonList.length, 2); analytics.surveyInteracted( survey: survey, - surveyButton: survey.surveyButtonList.first, + surveyButton: survey.buttonList.first, ); }); From 2ac586f71ac32904308042a16e2dfec761a534d5 Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:57:20 -0400 Subject: [PATCH 13/14] Adding example file for how to use survey handler feature --- .../example/serving_surveys.dart | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 pkgs/unified_analytics/example/serving_surveys.dart diff --git a/pkgs/unified_analytics/example/serving_surveys.dart b/pkgs/unified_analytics/example/serving_surveys.dart new file mode 100644 index 0000000000..104e7ae3fa --- /dev/null +++ b/pkgs/unified_analytics/example/serving_surveys.dart @@ -0,0 +1,153 @@ +import 'package:clock/clock.dart'; +import 'package:file/file.dart'; +import 'package:file/memory.dart'; + +import 'package:unified_analytics/src/constants.dart'; +import 'package:unified_analytics/src/enums.dart'; +import 'package:unified_analytics/src/survey_handler.dart'; +import 'package:unified_analytics/unified_analytics.dart'; + +void main() async { + late final MemoryFileSystem fs; + late final Analytics analytics; + late final Directory home; + // We need to initialize with a fake clock since the surveys have + // a period of time they are valid for + await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { + // Use a memory file system to repeatedly run this example + // file with the test instance + fs = MemoryFileSystem.test(style: FileSystemStyle.posix); + home = fs.directory('home'); + home.createSync(); + + // The purpose of `initialAnalytics` is so that the tool is able to + // send events after its first run; this instance won't be used below + // + // ignore: invalid_use_of_visible_for_testing_member + final initialAnalytics = Analytics.test( + tool: DashTool.flutterTool, + homeDirectory: home, + measurementId: 'measurementId', + apiSecret: 'apiSecret', + dartVersion: 'dartVersion', + fs: fs, + platform: DevicePlatform.macos, + ); + // The below command allows `DashTool.flutterTool` to send telemetry + initialAnalytics.clientShowedMessage(); + + // ignore: invalid_use_of_visible_for_testing_member + analytics = Analytics.test( + tool: DashTool.flutterTool, + homeDirectory: home, + measurementId: 'measurementId', + apiSecret: 'apiSecret', + dartVersion: 'dartVersion', + fs: fs, + platform: DevicePlatform.macos, + surveyHandler: FakeSurveyHandler.fromList( + homeDirectory: home, + fs: fs, + initializedSurveys: [ + Survey( + uniqueId: 'uniqueId', + startDate: DateTime(2023, 1, 1), + endDate: DateTime(2023, 5, 31), + description: 'description', + snoozeForMinutes: 10, + samplingRate: 1.0, + conditionList: [], + buttonList: [ + SurveyButton( + buttonText: 'View Survey', + action: 'accept', + promptRemainsVisible: false, + url: 'http://example.com', + ), + SurveyButton( + buttonText: 'More Info', + action: 'snooze', + promptRemainsVisible: true, + url: 'http://example2.com', + ), + SurveyButton( + buttonText: 'Dismiss Survey', + action: 'dismiss', + promptRemainsVisible: false, + ) + ], + ), + ], + )); + + // Send one event to allow `LogFileStats` to not be null + await analytics.send(Event.hotReloadTime(timeMs: 50)); + }); + + // Each client of this package will be able to fetch all of + // the available surveys with the below method + // + // Sample rate will be applied automatically; it also won't + // fetch any surveys in the snooze period or if they have + // been dismissed + final surveyList = await analytics.fetchAvailableSurveys(); + assert(surveyList.length == 1); + + // Grab the first and only survey to simulate displaying it to a user + final survey = surveyList.first; + print('Simulating displaying the survey with a print below:'); + print('Survey id: ${survey.uniqueId}\n'); + + // Immediately after displaying the survey, the method below + // should be run so that no other clients using this tool will show + // it at the same time + // + // It will "snoozed" when the below is run as well as reported to + // Google Analytics 4 that this survey was shown + analytics.surveyShown(survey); + + // Get the file where this is persisted to show it getting updated + final persistedSurveyFile = home + .childDirectory(kDartToolDirectoryName) + .childFile(kDismissedSurveyFileName); + print('The contents of the json file ' + 'after invoking `analytics.surveyShown(survey);`'); + print('${persistedSurveyFile.readAsStringSync()}\n'); + + // Change the index below to decide which button to simulate pressing + // + // 0 - accept + // 1 - snooze + // 2 - dismiss + final selectedButtonIndex = 1; + assert([0, 1, 2].contains(selectedButtonIndex)); + + // Get the survey button by index that will need to be passed along with + // the survey to simulate an interaction with the survey + final selectedSurveyButton = survey.buttonList[selectedButtonIndex]; + print('The simulated button pressed was: ' + '"${selectedSurveyButton.buttonText}" ' + '(action = ${selectedSurveyButton.action})\n'); + + // The below method will handle whatever action the button + analytics.surveyInteracted( + survey: survey, + surveyButton: selectedSurveyButton, + ); + + // Conditional to check what simulating a popup to stay up + if (selectedSurveyButton.promptRemainsVisible) { + print('***This button has its promptRemainsVisible field set to `true` ' + 'so this simulates what seeing a pop up again would look like***\n'); + } + + print('The contents of the json file ' + 'after invoking ' + '`analytics.surveyInteracted(survey: survey, ' + 'surveyButton: selectedSurveyButton);`'); + print('${persistedSurveyFile.readAsStringSync()}\n'); + + // Demonstrating that the survey doesn't get returned again + print('Attempting to fetch surveys again will result in an empty list'); + print(await analytics.fetchAvailableSurveys()); +} From fccdae12547231b1c49dec88dad89fe28522767b Mon Sep 17 00:00:00 2001 From: eliasyishak <42216813+eliasyishak@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:07:10 -0400 Subject: [PATCH 14/14] Adding conditional check for url to display --- pkgs/unified_analytics/example/serving_surveys.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkgs/unified_analytics/example/serving_surveys.dart b/pkgs/unified_analytics/example/serving_surveys.dart index 104e7ae3fa..fbbe6ea35e 100644 --- a/pkgs/unified_analytics/example/serving_surveys.dart +++ b/pkgs/unified_analytics/example/serving_surveys.dart @@ -135,6 +135,12 @@ void main() async { surveyButton: selectedSurveyButton, ); + // Conditional to check if there is a URl to route to + if (selectedSurveyButton.url != null) { + print('***This button also has a survey URL link ' + 'to route to at "${selectedSurveyButton.url}"***\n'); + } + // Conditional to check what simulating a popup to stay up if (selectedSurveyButton.promptRemainsVisible) { print('***This button has its promptRemainsVisible field set to `true` '