Skip to content

Commit

Permalink
feat: add ability to override visitor id to preserve id across organi…
Browse files Browse the repository at this point in the history
…c installs (#36)

* feat: add ability to override visitor id to preserve id across organic installs

* store log in state to disk and ensure we update in log in and log out use cases. add tests for all

* run dart format

* revert pubspec.yaml files

* bump test_track version

* introduce melos and use in ci

* fix path issues

* fix shebang

* use more portable shebang

* fix pana job

* add back code gen

* add pubspec_overrides.yaml to gitignore

* apply gitignore

* add tests for fakes

* rename fetchLoginState

* change doc language

* run dart format

* do not throw Exception if user is already logged in

* add reset function and only flip login state in logout; adjust tests

* remove melos

* use correct versions of each package in pubspecs

* Revert "remove melos"

This reverts commit 257683b.

* call correct function in test track test

* run format

* fix example main.dart
  • Loading branch information
btrautmann authored Mar 20, 2023
1 parent cc7a262 commit a940d41
Show file tree
Hide file tree
Showing 31 changed files with 528 additions and 152 deletions.
70 changes: 55 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,59 @@ on:
branches: [ main ]

jobs:
validate_test_track:
uses: ./.github/workflows/validate.yml
with:
package-name: test_track
code-gen: true
min-code-coverage: 90
min-pana-score: 110

validate_test_track_test_support:
uses: ./.github/workflows/validate.yml
with:
package-name: test_track_test_support
code-gen: false
min-code-coverage: 90
min-pana-score: 110
build:
runs-on: ubuntu-latest

steps:
- uses: actions/[email protected]
- uses: dart-lang/[email protected]

- name: Install dependencies
run: dart pub global activate melos 2.3.1 && melos bs

- name: Run build_runner
run: melos exec --depends-on="build_runner" --fail-fast -- "dart run build_runner build --delete-conflicting-outputs"

- name: Ensure clean git status
run: melos exec --fail-fast -- "../../tool/ensure_git_clean.sh"

- name: Verify formatting
run: melos exec --fail-fast -- "dart format --output=none --set-exit-if-changed ."

- name: Analyze test_track
uses: invertase/github-action-dart-analyzer@v1
with:
working-directory: packages/test_track

- name: Analyze test_track_test_support
uses: invertase/github-action-dart-analyzer@v1
with:
working-directory: packages/test_track_test_support

- name: Run tests and generate coverage report
run: melos exec --fail-fast -- "../../tool/generate_code_coverage.sh"

- name: Upload test_track code coverage
uses: codecov/codecov-action@v2
with:
files: coverage/lcov.info
working-directory: packages/test_track

- name: Upload test_track_test_support code coverage
uses: codecov/codecov-action@v2
with:
files: coverage/lcov.info
working-directory: packages/test_track_test_support

pana:
runs-on: ubuntu-latest

steps:
- uses: actions/[email protected]
- uses: dart-lang/[email protected]
- name: Install dependencies
run: |
dart pub global activate melos 2.3.1 && melos bs
dart pub global activate pana
- name: Verify pub score
run: melos exec -- "../../tool/verify_pub_score.sh 110"
72 changes: 0 additions & 72 deletions .github/workflows/validate.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/pubspec_overrides.yaml
1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions .idea/runConfigurations/melos_bootstrap.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions .idea/runConfigurations/melos_clean.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: test_track
command:
bootstrap:
usePubspecOverrides: true
runPubGetInParallel: false

packages:
- packages/**
12 changes: 12 additions & 0 deletions melos_test_track.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>
11 changes: 11 additions & 0 deletions packages/test_track/example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Future<void> main() async {
class DummyDataStorageProvider implements DataStorageProvider {
Visitor? _visitor;
SplitRegistry? _splitRegistry;
bool _isLoggedIn = false;

@override
Future<SplitRegistry?> fetchSplitRegistry() async {
Expand All @@ -59,6 +60,16 @@ class DummyDataStorageProvider implements DataStorageProvider {
Future<void> storeVisitor(Visitor visitor) async {
_visitor = visitor;
}

@override
Future<void> storeLoginState(bool isLoggedIn) async {
_isLoggedIn = isLoggedIn;
}

@override
Future<bool> fetchLoginState() async {
return _isLoggedIn;
}
}

class DummyAnalyticsProvider implements AnalyticsProvider {
Expand Down
2 changes: 2 additions & 0 deletions packages/test_track/lib/src/domain/domain.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export 'get_visitor_config.dart';
export 'override_assignments.dart';
export 'override_visitor_id.dart';
export 'report_assignment_event.dart';
export 'test_track_login.dart';
export 'test_track_logout.dart';
export 'reset.dart';
export 'vary/vary.dart';
50 changes: 50 additions & 0 deletions packages/test_track/lib/src/domain/override_visitor_id.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:test_track/src/domain/get_visitor_config.dart';
import 'package:test_track/test_track.dart';

/// {@template override_visitor_id}
///
/// Allows for overriding the current visitor id with the provided one. This is
/// useful for cases where your user is assigned a visitor id from another
/// platform prior to invoking login on the SDK, and you'd like any
/// configuration assigned to that user to "follow" them to the
/// platform running this client.
///
/// If this method is called when a user has already logged in to TestTrack,
/// the request to override the current visitor id will be ignored.
///
/// If the visitor id could be overwritten, the [AppVisitorConfig] associated
/// with the visitor id will be returned. Otherwise, this function will return
/// null.
///
/// {@endtemplate}
class OverrideVisitorId {
final GetVisitorConfig _getVisitorConfig;
final DataStorageProvider _dataStorageProvider;
final TestTrackLogger _logger;

/// {@macro override_visitor_id}
OverrideVisitorId({
required GetVisitorConfig getVisitorConfig,
required DataStorageProvider dataStorageProvider,
required TestTrackLogger logger,
}) : _getVisitorConfig = getVisitorConfig,
_dataStorageProvider = dataStorageProvider,
_logger = logger;

/// {@macro override_visitor_id}
Future<AppVisitorConfig?> call({
required String visitorId,
required AppVersionBuild appVersionBuild,
}) async {
final isLoggedIn = await _dataStorageProvider.fetchLoginState();
if (isLoggedIn) {
_logger.info(
'Attempt to override visitor id ignored because a user is currently logged in.');
return null;
}
return _getVisitorConfig(
visitorId: visitorId,
appVersionBuild: appVersionBuild,
);
}
}
37 changes: 37 additions & 0 deletions packages/test_track/lib/src/domain/reset.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:test_track/test_track.dart';

/// {@template test_track_reset}
///
/// Resets all state by replacing stored visitor with
/// a fresh [Visitor]. Additionally, indicates that the user
/// has been logged out to the [DataStorageProvider].
///
/// Lastly, invokes [AnalyticsProvider.identify] with the id
/// of the newly generated [Visitor] and returns the [Visitor].
///
/// If the desire is to preserve the id of the current [Visitor],
/// use `logout` instead.
///
/// {@endtemplate}
class Reset {
final DataStorageProvider _dataStorageProvider;
final AnalyticsProvider _analyticsProvider;

/// {@macro test_track_reset}
Reset({
required DataStorageProvider dataStorageProvider,
required AnalyticsProvider analyticsProvider,
}) : _dataStorageProvider = dataStorageProvider,
_analyticsProvider = analyticsProvider;

/// {@macro test_track_reset}
Future<Visitor> call() async {
final newVisitor = Visitor.build();

await _dataStorageProvider.storeVisitor(newVisitor);
await _dataStorageProvider.storeLoginState(false);
await _analyticsProvider.identify(visitorId: newVisitor.id);

return newVisitor;
}
}
4 changes: 3 additions & 1 deletion packages/test_track/lib/src/domain/test_track_login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import 'package:test_track/test_track.dart';
/// [TestTrackLoginFailureException].
///
/// If the linking is successful, the [Visitor] and [SplitRegistry]
/// contained within the associated [AppVisitorConfig] are persisted.
/// contained within the associated [AppVisitorConfig] are persisted, and
/// the [DataStorageProvider] is notified of the new login state.
///
/// {@endtemplate}
class Login {
Expand Down Expand Up @@ -64,6 +65,7 @@ class Login {
await _dataStorageProvider.storeVisitor(appVisitorConfig.visitor);
await _dataStorageProvider
.storeSplitRegistry(appVisitorConfig.splitRegistry);
await _dataStorageProvider.storeLoginState(true);

await _analyticsProvider.identify(visitorId: appVisitorConfig.visitor.id);

Expand Down
22 changes: 6 additions & 16 deletions packages/test_track/lib/src/domain/test_track_logout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,22 @@ import 'package:test_track/test_track.dart';

/// {@template test_track_logout}
///
/// Logs the current user out of the test track client by
/// writing over their data with a fresh [Visitor] and
/// returns the fresh [Visitor].
/// Indicates that the user has been logged out to the [DataStorageProvider].
///
/// Also, invokes [AnalyticsProvider.identify] with the id
/// of the newly generated [Visitor]
/// Does *not* alter the stored [Visitor] in any way. This is in contrast to
/// `reset` which clears all [Visitor] state.
///
/// {@endtemplate}
class Logout {
final DataStorageProvider _dataStorageProvider;
final AnalyticsProvider _analyticsProvider;

/// {@macro test_track_logout}
Logout({
required DataStorageProvider dataStorageProvider,
required AnalyticsProvider analyticsProvider,
}) : _dataStorageProvider = dataStorageProvider,
_analyticsProvider = analyticsProvider;
}) : _dataStorageProvider = dataStorageProvider;

/// {@macro test_track_logout}
Future<Visitor> call() async {
final newVisitor = Visitor.build();

await _dataStorageProvider.storeVisitor(newVisitor);
await _analyticsProvider.identify(visitorId: newVisitor.id);

return newVisitor;
Future<void> call() async {
await _dataStorageProvider.storeLoginState(false);
}
}
Loading

0 comments on commit a940d41

Please sign in to comment.