Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(analytics): Legacy data migration of Pinpoint Endpoint ID #2489

Merged
merged 9 commits into from Jan 11, 2023

Conversation

fjnoyp
Copy link
Contributor

@fjnoyp fjnoyp commented Dec 14, 2022

Issue #, if available:

Description of changes:

Add code to extract the previous Analytics Pinpoint Endpoint ID stored in Android or iOS.

Users upgrading to our next release won't have their Pinpoint Endpoint regenerated and can keep pinpoint session on a given user device between the upgrade.

Testing

Before, when upgrading Analytics, a new Pinpoint ID would be assigned. This causes the AWS Pinpoint console to register a new endpoint and therefore increment the 'daily active endpoint' in the analytics dashboard. This is problematic because Pinpoint is now detecting a new user.

With this change, when upgrading and running the same Analytics app on the same device, the 'daily active endpoint' number should NOT increment.

You can test this by modifying the Analytics example app pubspec.yaml to use 0.6.1 and to remove the local path dependencies. You will also need to delete the pubspec_overrides.yaml generated by aft bootstrap. Then modify example app's main.dart to simply add the analytics/auth plugins (in dart first there are new initialization options). Run the app and verify the AWS console registers your session and 'daily active endpoint' increases by 1. Afterwards, undo the previous changes and run the Analytics dart first version. The code will now retrieve the old pinpoint id, and thus 'daily active endpoint' will not increment.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@fjnoyp fjnoyp requested a review from a team as a code owner December 14, 2022 22:50
@fjnoyp fjnoyp changed the base branch from next to feat/next/analytics_integ_test_new December 14, 2022 22:53
@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from 92a8e87 to eaf5117 Compare December 14, 2022 23:06
@fjnoyp
Copy link
Contributor Author

fjnoyp commented Dec 14, 2022

I based this on the other PR for integration tests because that PR refactors several parts of Analytics that this PR relies on (primarily with configure of the plugin).

@fjnoyp fjnoyp changed the title Feat/next/analytics migrate new new feat(analytics): Legacy data migration of Pinpoint Endpoint ID Dec 14, 2022
}

override fun initialize(pinpointAppId: String, result: Messages.Result<Void>){
sharedPrefs = context.getSharedPreferences(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialize inline or add check if already initialized

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Dillon might be suggesting is to combine initialize and getEndpointId, which makes sense to me since it looks like init is always called right before getEndpointId. You can check if sharedPrefs has been initialized, and if not, do so. For example:

var sharedPrefs: SharedPreferences? = null

fun getEndpointId(pinpointAppId: String, result: Messages.Result<String?>){    
    sharedPrefs = sharedPrefs ?: context.getSharedPreferences(
        "${pinpointAppId}$PINPOINT_SHARED_PREFS_SUFFIX",
        Context.MODE_PRIVATE
    )
    result.success(sharedPrefs!!.getString(UNIQUE_ID_KEY, null))
}

@fjnoyp fjnoyp requested review from dnys1 and a team December 19, 2022 18:05
@fjnoyp fjnoyp force-pushed the feat/next/analytics_integ_test_new branch from c1944f7 to 28b5a8c Compare December 19, 2022 21:01
@fjnoyp fjnoyp self-assigned this Dec 21, 2022
@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from 93fbbc0 to 084eae2 Compare December 21, 2022 21:17
@fjnoyp fjnoyp force-pushed the feat/next/analytics_integ_test_new branch from a5743b7 to bb3545f Compare December 27, 2022 19:07
@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from 7590ceb to 9864a80 Compare December 27, 2022 19:12
Comment on lines 41 to 54
private func data(forKey key: String) -> Data? {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: KeychainReader.pinpointAttrService,
kSecAttrAccount: key,
kSecReturnData: true
] as CFDictionary

var result: AnyObject?
let status = SecItemCopyMatching(query, &result)

guard status != errSecItemNotFound else {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you find out if there was any specific reason secure_storage wasn't working? The code here is similar to _getValue() in secure_storage. One difference I see is that secure_storage sets an explicit value for kSecAttrAccessible, which is not done here. This means it will fallback on the default, which might be what the SDK uses. I can look into what impact that has in accessing data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for helping here, looks like this was resolved.

}) : super(
keyValueStore: keyValueStore ??
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be endpointInfoStore? It seems like this was renamed in some places and not in others.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, getting build errors here which prevent testing, seen in integ tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks resolved. I did a rebase and appear to have missed a commit somehow ...

),
)
@HostApi()
abstract class PigeonDataProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was marked as resolved but I think still applies

static const String _endpointGlobalAttrsKey = 'EndpointGlobalAttributesKey';
static const String _endpointGlobalMetricsKey = 'EndpointGlobalMetricsKey';

static const String _endpointInformationCurrentVersionValue = '0';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this? Can you add a comment? Would an enum make more sense here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  /// Version of metadata stored into the `endpointInfoStore`
  /// If stored data changes, increment this value
  static const String _endpointInformationCurrentVersionValue = '0';

Added the above comments. The initial idea is to track if we have read legacy data from native.

If so, we store this value into secure storage to avoid further reads from legacy data.

In case of future changes, if we need to change legacy read behavior to read other values we would increment this value and store that instead. Having this value stored before allows us to quickly know what the storage state is (either no value, "0", "1", etc)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I would suggest using an enum for this as we did in Auth. It provides a nice way to document this.

/// The credential store version.
enum CredentialStoreVersion {
/// No credential store version is present.
///
/// Either the credential store has never been initialized on this device, or
/// the credentials are stored in a legacy format.
none,
/// The initial implementation of Credential in Amplify Flutter Auth.
v1,
}

Base automatically changed from feat/next/analytics_integ_test_new to next December 27, 2022 21:18
@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from 9864a80 to 5b12a78 Compare December 27, 2022 22:12
@codecov-commenter
Copy link

codecov-commenter commented Dec 27, 2022

Codecov Report

Merging #2489 (5b12a78) into next (b9d6d08) will increase coverage by 40.34%.
The diff coverage is n/a.

@@             Coverage Diff             @@
##             next    #2489       +/-   ##
===========================================
+ Coverage   56.19%   96.53%   +40.34%     
===========================================
  Files         115       13      -102     
  Lines        6987     1182     -5805     
===========================================
- Hits         3926     1141     -2785     
+ Misses       3061       41     -3020     
Flag Coverage Δ
android-unit-tests ?
flutter-unit-tests ?
ios-unit-tests 96.53% <ø> (+1.20%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...ib/src/widgets/form_fields/sign_in_form_field.dart
...src/graphql/model_helpers/model_subscriptions.dart
...widgets/form_fields/reset_password_form_field.dart
...mplify_authenticator/lib/src/utils/breakpoint.dart
...src/graphql/web_socket/types/web_socket_types.dart
...mplify_authenticator/lib/src/state/auth_state.dart
...or/lib/src/widgets/authenticator_input_config.dart
.../ios/unit_tests/amplify_flutter_exampleTests.swift
...nticator/lib/src/widgets/authenticator_banner.dart
...mplify_authenticator/lib/src/widgets/progress.dart
... and 92 more

Copy link
Contributor

@ragingsquirrel3 ragingsquirrel3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with other comments that would be good to see platform/pigeon stuff consolidated as well as comment about secure storage detail before reviewing again. Biggest question was how non android/ios platforms should avoid the legacy stuff as it did not seem correct when I tested on MacOS (I did not try other platforms).

revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
channel: stable

project_type: app
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Shouldn't this be for type: plugin like the other plugins? How was this file created?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks good catch there. It appears this was created on the flutter create . command not sure how/why it was modified since then though. It should be project_type plugin so I changed it. Looks to be used by flutter when performing upgrades so making this change should be fine.

Used this as a resource in case you're curious to learn more about what metadata is: https://dev.to/swimmingkiim/what-is-metadata-file-in-flutter-project-38m3

}) : super(
keyValueStore: keyValueStore ??
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, getting build errors here which prevent testing, seen in integ tests

await _legacyNativeDataProvider?.initialize(appId);
retrievedEndpointId = await _legacyNativeDataProvider?.getEndpointId();

retrievedEndpointId ??= const Uuid().v1();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Why use this version of uuid instead of uuid() from aws_common? If there is specific requirement for that could we have a comment here saying why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks there is no reason, wasn't aware of this method in core package.

class FlutterLegacyNativeDataProvider implements LegacyNativeDataProvider {
/// {@macro amplify_analytics_pinpoint.flutter_legacy_native_data_provider}
factory FlutterLegacyNativeDataProvider() {
final provider = Platform.isIOS ? DataProviderIos() : DataProviderAndroid();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Maybe I'm missing something here, but is this using DataProviderAndroid for all non-ios io platforms (desktop windows, macos, linux)? When I ran example app on macos I got this error:

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(channel-error, Unable to establish connection on channel., null, null)
#0      PigeonDataProvider.initialize (package:amplify_analytics_pinpoint/src/legacy_native_data_provider/pigeon_data_provider.android.g.dart:40:7)
<asynchronous suspension>
#1      AmplifyAnalyticsPinpointDart.configure (package:amplify_analytics_pinpoint_dart/src/analytics_plugin_impl.dart:169:7)
<asynchronous suspension>
#2      Future.wait.<anonymous closure> (dart:async/future.dart:522:21)
<asynchronous suspension>
#3      AmplifyClassImpl.configurePlatform (package:amplify_core/src/amplify_class_impl.dart:73:5)
<asynchronous suspension>
#4      AmplifyClass.configure (package:amplify_core/src/amplify_class.dart:107:7)
<asynchronous suspension>
#5      _MyAppState._configureAmplify (package:amplify_analytics_pinpoint_example/main.dart:70:7)
<asynchronous suspension>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks good catch, I'll need to fix this then

key: _endpointInformationVersionKey,
value: _endpointInformationCurrentVersionValue,
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Should this block only execute on ios/android like:

if (!zIsWeb && (Platform.isAndroid || Platform.isIOS)) {
}

I mentioned in another comment I got an error from here on macos but I wonder if it would simplify things just to stop this logic far upstream (here) to simplify non ios/android platforms as much as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks yes updated the logic

@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from 5b12a78 to 5df46c6 Compare December 29, 2022 21:44
@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from 5df46c6 to 0371b05 Compare December 29, 2022 21:45
@fjnoyp
Copy link
Contributor Author

fjnoyp commented Dec 29, 2022

Do not merge until PR fixes to secure storage are in:

bfeae2a

That PR allows us to properly extract legacy ios data using the secure storage plugin instead of custom ios code.

packages/analytics/amplify_analytics_pinpoint/pubspec.yaml Outdated Show resolved Hide resolved

analyzer:
exclude:
- "**/**/**/*.g.dart"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

**/**/**/ should be reducible to **/

Suggested change
- "**/**/**/*.g.dart"
- "**/*.g.dart"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging with next was unsuccessful. Please fix and address Jordan's comment:

analyzer: 
  errors:
    implementation_imports: error #TODO(equartey): Remove when lint is enforced project-wide    
  exclude:
    - "**/*.g.dart"

static const String _endpointGlobalAttrsKey = 'EndpointGlobalAttributesKey';
static const String _endpointGlobalMetricsKey = 'EndpointGlobalMetricsKey';

static const String _endpointInformationCurrentVersionValue = '0';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I would suggest using an enum for this as we did in Auth. It provides a nice way to document this.

/// The credential store version.
enum CredentialStoreVersion {
/// No credential store version is present.
///
/// Either the credential store has never been initialized on this device, or
/// the credentials are stored in a legacy format.
none,
/// The initial implementation of Credential in Amplify Flutter Auth.
v1,
}

Comment on lines 163 to 164
await _legacyNativeDataProvider?.initialize(appId);
retrievedEndpointId = await _legacyNativeDataProvider?.getEndpointId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these two functions are called twice (here & lines 153/154 above). Is that on purpose? I think they both get called during data migration. Since this is a one time event, that is not the end of the world, but it is a little confusing to follow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having a bit of a hard time following this. I believe you want to achieve something like:

  • Check version
  • If version is null, attempt migration (one time event)
    • read ID from legacy storage
    • if non-null, write to new storage
    • update version to '0' to prevent migration on future app inits
  • Read version from storage
  • If null, generate a new one
    • generate new ID
    • write to storage

This is an example of a flow that I think is easier to follow.

    // Attempt migration if version is null.
    if (endpointInformationVersion == null) {
      await _legacyNativeDataProvider?.initialize(appId);
      final legacyEndpointId = await _legacyNativeDataProvider?.getEndpointId();
      // Migrate legacy data if it is non-null
      if (legacyEndpointId != null) {
        await _endpointInfoStore.write(
          key: endpointIdStorageKey,
          value: legacyEndpointId,
        );
      }
      // Update the version to prevent future legacy data migrations.
      await _endpointInfoStore.write(
        key: _endpointInformationVersionKey,
        value: _endpointInformationCurrentVersionValue,
      );
    }

    // Read the existing ID.
    final currentEndpointId = await _endpointInfoStore.read(
      key: endpointIdStorageKey,
    );

    final String fixedEndpointId;

    // Generate a new ID if one does not exist.
    if (currentEndpointId == null) {
      fixedEndpointId = uuid();
      await _endpointInfoStore.write(
        key: endpointIdStorageKey,
        value: fixedEndpointId,
      );
    } else {
      fixedEndpointId = currentEndpointId;
    }

This will result in reading the ID twice in the scenario where there is data to migrate (first from legacy storage, and then from the new storage), but since it is a one time event I think that is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright it seems like a bit longer of a solution and possibly more complex because there are now 2 conditionals but I see how some of the code before was implicit. I think it's good that the read call no longer requires a force cast, so if something goes wrong and currentEndpointId is null we no everything will still work and no crash will occur.

@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from 2c63b22 to d6a194e Compare January 4, 2023 06:47
}

override fun initialize(pinpointAppId: String, result: Messages.Result<Void>){
sharedPrefs = context.getSharedPreferences(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Dillon might be suggesting is to combine initialize and getEndpointId, which makes sense to me since it looks like init is always called right before getEndpointId. You can check if sharedPrefs has been initialized, and if not, do so. For example:

var sharedPrefs: SharedPreferences? = null

fun getEndpointId(pinpointAppId: String, result: Messages.Result<String?>){    
    sharedPrefs = sharedPrefs ?: context.getSharedPreferences(
        "${pinpointAppId}$PINPOINT_SHARED_PREFS_SUFFIX",
        Context.MODE_PRIVATE
    )
    result.success(sharedPrefs!!.getString(UNIQUE_ID_KEY, null))
}

Copy link
Contributor

@ragingsquirrel3 ragingsquirrel3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved assuming web crash fixed per comments regarding !zIsWeb.

@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from d5d91c1 to 87006ed Compare January 10, 2023 06:59

analyzer:
exclude:
- "**/**/**/*.g.dart"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging with next was unsuccessful. Please fix and address Jordan's comment:

analyzer: 
  errors:
    implementation_imports: error #TODO(equartey): Remove when lint is enforced project-wide    
  exclude:
    - "**/*.g.dart"

Comment on lines 337 to 372
if (endpointInformationVersion == null) {
final legacyEndpointId =
await legacyDataProvider?.getEndpointId(pinpointAppId);
// Migrate legacy data if it is non-null
if (legacyEndpointId != null) {
await store.write(
key: endpointIdStorageKey,
value: legacyEndpointId,
);
}
// Update the version to prevent future legacy data migrations.
await store.write(
key: EndpointStoreKey.version.name,
value: EndpointStoreVersion.v1.name,
);
}

// Read the existing ID.
final currentEndpointId = await store.read(
key: endpointIdStorageKey,
);

final String fixedEndpointId;

// Generate a new ID if one does not exist.
if (currentEndpointId == null) {
fixedEndpointId = uuid();
await store.write(
key: endpointIdStorageKey,
value: fixedEndpointId,
);
} else {
fixedEndpointId = currentEndpointId;
}

return fixedEndpointId;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the goal is to arrive at a fixed endpoint ID, use a single variable to convey that journey. This also cleans up the logic branches and saves you a read from the DB in some cases.

Suggested change
if (endpointInformationVersion == null) {
final legacyEndpointId =
await legacyDataProvider?.getEndpointId(pinpointAppId);
// Migrate legacy data if it is non-null
if (legacyEndpointId != null) {
await store.write(
key: endpointIdStorageKey,
value: legacyEndpointId,
);
}
// Update the version to prevent future legacy data migrations.
await store.write(
key: EndpointStoreKey.version.name,
value: EndpointStoreVersion.v1.name,
);
}
// Read the existing ID.
final currentEndpointId = await store.read(
key: endpointIdStorageKey,
);
final String fixedEndpointId;
// Generate a new ID if one does not exist.
if (currentEndpointId == null) {
fixedEndpointId = uuid();
await store.write(
key: endpointIdStorageKey,
value: fixedEndpointId,
);
} else {
fixedEndpointId = currentEndpointId;
}
return fixedEndpointId;
String? fixedEndpointId;
if (endpointInformationVersion == null) {
final legacyEndpointId =
await legacyDataProvider?.getEndpointId(pinpointAppId);
// Migrate legacy data if it is non-null
if (legacyEndpointId != null) {
fixedEndpointId = legacyEndpointId;
await store.write(
key: endpointIdStorageKey,
value: legacyEndpointId,
);
}
// Update the version to prevent future legacy data migrations.
await store.write(
key: EndpointStoreKey.version.name,
value: EndpointStoreVersion.v1.name,
);
}
// Read the existing ID.
fixedEndpointId ??= await store.read(
key: endpointIdStorageKey,
);
// Generate a new ID if one does not exist.
if (fixedEndpointId == null) {
fixedEndpointId = uuid();
await store.write(
key: endpointIdStorageKey,
value: fixedEndpointId,
);
}
return fixedEndpointId;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah while you were gone I had a single variable implementation beforehand as well. Your suggestion is close to what I had before.

But previous comments suggested a different approach.

This is clearly a bit subjective, let's use your suggestion here.

cc @Jordan-Nelson

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments are not in conflict with Jordan's. He was confused about the duplication of logic and unnecessary reads/writes.

fjnoyp and others added 7 commits January 10, 2023 15:53
Extract endpoint id from Amplify iOS and Android Analytics
…/kotlin/com/amazonaws/amplify/amplify_analytics_pinpoint/amplify_analytics_pinpoint/AmplifyAnalyticsPinpointPlugin.kt

Co-authored-by: Jordan Nelson <nejrd@amazon.com>
….yaml

Co-authored-by: Jordan Nelson <nejrd@amazon.com>
Co-authored-by: Jordan Nelson <nejrd@amazon.com>
@fjnoyp fjnoyp force-pushed the feat/next/analytics_migrate_new_new branch from e285a2b to f2f9e28 Compare January 10, 2023 23:53
@fjnoyp fjnoyp merged commit fc67365 into next Jan 11, 2023
@fjnoyp fjnoyp deleted the feat/next/analytics_migrate_new_new branch January 11, 2023 22:14
dnys1 pushed a commit to Jordan-Nelson/amplify-flutter that referenced this pull request Jan 30, 2023
- fix(auth)!: Fetch Auth Session offline behavior ([aws-amplify#2585](aws-amplify#2585))

- fix(api): do not include null values in ModelMutations.create ([aws-amplify#2504](aws-amplify#2504))
- fix(api): model helpers use targetNames in schemas with CPK enabled ([aws-amplify#2559](aws-amplify#2559))
- fix(auth): Clear credentials before redirect on Web ([aws-amplify#2603](aws-amplify#2603))
- fix(auth): Refresh token in non-state machine calls ([aws-amplify#2572](aws-amplify#2572))
- fix(authenticator): ARB syntax ([aws-amplify#2560](aws-amplify#2560))
- fix(aws_common): AWSFile contentType getter should not throw exception
- fix(datastore): prevent unhandled exception crashing App rebuilding sync expression
- fix(storage): incorrect transferred bytes emitted from upload task

- feat(analytics): Legacy data migration of Pinpoint Endpoint ID ([aws-amplify#2489](aws-amplify#2489))
- feat(smithy_aws): add copyWith to S3ClientConfig
- feat(storage): allow configuring transfer acceleration

Updated-Components: Amplify Flutter, Amplify Dart, Amplify UI, DB Common, Secure Storage, AWS Common, Smithy, Worker Bee
dnys1 pushed a commit to Jordan-Nelson/amplify-flutter that referenced this pull request Jan 30, 2023
- fix(auth)!: Fetch Auth Session offline behavior ([aws-amplify#2585](aws-amplify#2585))

- fix(api): do not include null values in ModelMutations.create ([aws-amplify#2504](aws-amplify#2504))
- fix(api): model helpers use targetNames in schemas with CPK enabled ([aws-amplify#2559](aws-amplify#2559))
- fix(auth): Clear credentials before redirect on Web ([aws-amplify#2603](aws-amplify#2603))
- fix(auth): Refresh token in non-state machine calls ([aws-amplify#2572](aws-amplify#2572))
- fix(authenticator): ARB syntax ([aws-amplify#2560](aws-amplify#2560))
- fix(aws_common): AWSFile contentType getter should not throw exception
- fix(datastore): prevent unhandled exception crashing App rebuilding sync expression
- fix(storage): incorrect transferred bytes emitted from upload task

- feat(analytics): Legacy data migration of Pinpoint Endpoint ID ([aws-amplify#2489](aws-amplify#2489))
- feat(smithy_aws): add copyWith to S3ClientConfig
- feat(storage): allow configuring transfer acceleration

Updated-Components: Amplify Flutter, Amplify Dart, Amplify UI, DB Common, Secure Storage, AWS Common, Smithy, Worker Bee
Jordan-Nelson added a commit that referenced this pull request Jan 30, 2023
* chore(repo): Update component definition

Adds `amplify_api_dart` to the mix and creates components for DB Common, Secure Storage, and AWS Common

* fix(aft): Update changelog logic

For the version commit message changelog, only include publishable packages. Also updates base ref logic to ensure packages can be moved in and out of components.

* chore(version): Bump version

- fix(auth)!: Fetch Auth Session offline behavior ([#2585](#2585))

- fix(api): do not include null values in ModelMutations.create ([#2504](#2504))
- fix(api): model helpers use targetNames in schemas with CPK enabled ([#2559](#2559))
- fix(auth): Clear credentials before redirect on Web ([#2603](#2603))
- fix(auth): Refresh token in non-state machine calls ([#2572](#2572))
- fix(authenticator): ARB syntax ([#2560](#2560))
- fix(aws_common): AWSFile contentType getter should not throw exception
- fix(datastore): prevent unhandled exception crashing App rebuilding sync expression
- fix(storage): incorrect transferred bytes emitted from upload task

- feat(analytics): Legacy data migration of Pinpoint Endpoint ID ([#2489](#2489))
- feat(smithy_aws): add copyWith to S3ClientConfig
- feat(storage): allow configuring transfer acceleration

Updated-Components: Amplify Flutter, Amplify Dart, Amplify UI, DB Common, Secure Storage, AWS Common, Smithy, Worker Bee

---------

Co-authored-by: Dillon Nys <nydillon@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants