Skip to content

Commit

Permalink
fix: push notification flush events (#5215)
Browse files Browse the repository at this point in the history
fix: push notification flush events (#5215)
  • Loading branch information
tyllark authored Aug 14, 2024
1 parent 221cf61 commit cf904dc
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
/// [Campaigns](https://docs.aws.amazon.com/pinpoint/latest/userguide/campaigns.html)
/// [Journeys](https://docs.aws.amazon.com/pinpoint/latest/userguide/journeys.html)
/// {@endtemplate}
@Deprecated('this enum will be private in the next major version')
enum PinpointEventSource {
campaign('campaign'),
journey('journey');

@Deprecated('this enum will be private in the next major version')
const PinpointEventSource(this.name);

final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/endpoi
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_client/event_client.dart';
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_client/queued_item_store/dart_queued_item_store.dart';
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/session_manager.dart';
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/stoppable_timer.dart';
import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/app_lifecycle_provider.dart';
import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/cached_events_path_provider.dart';
import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/device_context_info_provider.dart';
Expand Down
1 change: 1 addition & 0 deletions packages/aws_common/lib/aws_common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ export 'src/util/json.dart';
export 'src/util/print.dart';
export 'src/util/recase.dart';
export 'src/util/serializable.dart';
export 'src/util/stoppable_timer.dart';
export 'src/util/stream.dart';
export 'src/util/uuid.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
/// {@template amplify_analytics_pinpoint_dart.stoppable_timer}
/// A Timer that can be stopped and started again.
/// {@endtemplate}
@protected
class StoppableTimer {
/// {@macro amplify_analytics_pinpoint_dart.stoppable_timer}
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

/// {@template amplify_core.push.pinpoint_event_source}
/// The source of a push notification.
///
/// Pinpoint offers two ways of sending push notifications to users campaigns and journeys.
///
/// See also:
/// [Campaigns](https://docs.aws.amazon.com/pinpoint/latest/userguide/campaigns.html)
/// [Journeys](https://docs.aws.amazon.com/pinpoint/latest/userguide/journeys.html)
/// {@endtemplate}
enum PinpointEventTypeSource {
/// [campaign] represents a push notification originating from a campaign
/// [Campaign Events](https://docs.aws.amazon.com/pinpoint/latest/developerguide/event-streams-data-campaign.html)
campaign('_campaign'),

/// [journey] represents a push notification originating from a journey
/// [Journey Events](https://docs.aws.amazon.com/pinpoint/latest/developerguide/event-streams-data-journey.html)
journey('_journey');

const PinpointEventTypeSource(this.name);

/// [name] contains the source prefix for event_type attributes
final String name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:amplify_core/amplify_core.dart';
// ignore: implementation_imports
import 'package:amplify_core/src/config/amplify_outputs/notifications/notifications_outputs.dart';
import 'package:amplify_push_notifications_pinpoint/src/event_info_type.dart';
import 'package:amplify_push_notifications_pinpoint/src/pinpoint_event_type_source.dart';
import 'package:amplify_secure_storage/amplify_secure_storage.dart';
import 'package:flutter/widgets.dart';

Expand All @@ -27,12 +28,18 @@ final AmplifyLogger _logger = AmplifyLogger.category(Category.pushNotifications)
/// [init] method has to be called before other methods can be used.
/// Once initialized, it can [registerDevice], [recordNotificationEvent]
/// & [identifyUser] with Pinpoint.
///
/// To release any initialized resources [dispose] should be called.
/// {@endtemplate}
class PinpointProvider implements ServiceProviderClient {
/// {@macro amplify_push_notifications_pinpoint.pinpoint_provider}
late AnalyticsClient _analyticsClient;

/// Periodic timer for flushing events made public for testing
@visibleForTesting
late final StoppableTimer autoEventSubmitter;

static const _androidCampaignIdKey = 'pinpoint.campaign.campaign_id';
static const _androidCampaignActivityIdKey =
'pinpoint.campaign.campaign_activity_id';
Expand Down Expand Up @@ -92,6 +99,12 @@ class PinpointProvider implements ServiceProviderClient {
authProvider: authProvider,
);

autoEventSubmitter = StoppableTimer(
duration: const Duration(seconds: 10),
callback: _flushEvents,
onError: (e) => _logger.warn('Exception in events auto flush', e),
);

_isInitialized = true;
}
} on Exception catch (e) {
Expand All @@ -104,6 +117,10 @@ class PinpointProvider implements ServiceProviderClient {
}
}

Future<void> _flushEvents() {
return _analyticsClient.eventClient.flushEvents();
}

@override
Future<void> identifyUser({
required String userId,
Expand Down Expand Up @@ -209,14 +226,14 @@ class PinpointProvider implements ServiceProviderClient {
}) {
final data = notification.data;
final analyticsProperties = CustomProperties();
var source = PinpointEventSource.campaign.name;
var source = PinpointEventTypeSource.campaign.name;
var campaign = <String, String>{};
var journey = <String, String>{};
var pinpointData = <Object?, Object?>{};

// Android payload contain pinpoint.campaign.* format
if (data.containsKey(_androidCampaignIdKey)) {
source = PinpointEventSource.campaign.name;
source = PinpointEventTypeSource.campaign.name;
campaign['campaign_id'] = data[_androidCampaignIdKey] as String;
if (data.containsKey(_androidCampaignActivityIdKey)) {
campaign['campaign_activity_id'] =
Expand All @@ -239,15 +256,15 @@ class PinpointProvider implements ServiceProviderClient {

// iOS payload conatin a nested map of pinpoint, campaign, * format
if (pinpointData.containsKey('campaign')) {
source = PinpointEventSource.campaign.name;
source = PinpointEventTypeSource.campaign.name;
campaign = Map<String, String>.from(
pinpointData['campaign'] as Map<Object?, Object?>,
);
}

// Common way of represting journeys both on Android and iOS payloads
if (pinpointData.containsKey('journey')) {
source = PinpointEventSource.journey.name;
source = PinpointEventTypeSource.journey.name;
journey = Map<String, String>.from(
pinpointData['journey'] as Map<Object?, Object?>,
);
Expand All @@ -274,4 +291,10 @@ class PinpointProvider implements ServiceProviderClient {
return ChannelType.apns;
}
}

/// Cleans up and releases resources retained by this object.
/// This includes but is not limited to periodic timers for flushing events.
void dispose() {
autoEventSubmitter.stop();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_
import 'package:amplify_core/src/config/amplify_outputs/notifications/amazon_pinpoint_channel.dart';
import 'package:amplify_core/src/config/amplify_outputs/notifications/notifications_outputs.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:amplify_push_notifications_pinpoint/src/pinpoint_event_type_source.dart';
import 'package:amplify_push_notifications_pinpoint/src/pinpoint_provider.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
Expand Down Expand Up @@ -142,7 +143,7 @@ void main() {
final properties = res.properties;
final source = res.source;
expect(properties.attributes.containsKey('journey_id'), isTrue);
expect(source, equals(PinpointEventSource.journey.name));
expect(source, equals(PinpointEventTypeSource.journey.name));
});

test(
Expand All @@ -154,7 +155,7 @@ void main() {
final properties = res.properties;
final source = res.source;
expect(properties.attributes.containsKey('campaign_id'), isTrue);
expect(source, equals(PinpointEventSource.campaign.name));
expect(source, equals(PinpointEventTypeSource.campaign.name));
});
});

Expand Down Expand Up @@ -197,6 +198,32 @@ void main() {
);
});

test('flush events timer initialized', () async {
when(
() => mockAmplifyAuthProviderRepository.getAuthProvider(
APIAuthorizationType.iam.authProviderToken,
),
).thenReturn(awsIamAmplifyAuthProvider);
when(
() => mockAnalyticsClient.init(
pinpointAppId: any(named: 'pinpointAppId'),
region: any(named: 'region'),
authProvider: any(named: 'authProvider'),
),
).thenAnswer((realInvocation) async {});

await pinpointProvider.init(
config: notificationsPinpointConfig,
authProviderRepo: mockAmplifyAuthProviderRepository,
analyticsClient: mockAnalyticsClient,
);

expect(
pinpointProvider.autoEventSubmitter.duration,
const Duration(seconds: 10),
);
});

test('identifyUser should run successfully', () async {
when(
() => mockAmplifyAuthProviderRepository.getAuthProvider(
Expand Down Expand Up @@ -408,7 +435,7 @@ void main() {
verify(
() => mockEventClient.recordEvent(
eventType:
'${PinpointEventSource.campaign.name}.${PinpointEventType.foregroundMessageReceived.name}',
'${PinpointEventTypeSource.campaign.name}.${PinpointEventType.foregroundMessageReceived.name}',
properties: any(named: 'properties'),
),
);
Expand Down

0 comments on commit cf904dc

Please sign in to comment.