Skip to content

Commit

Permalink
feat: user controlled update toggles and send metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Dec 19, 2024
2 parents 8f8dd2f + befd790 commit e30c4a1
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.7.0

* Feat: manual control over updateToggles and sendMetrics


## 1.6.0

* Feat: save network calls when context fields don't change
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ final unleash = UnleashClient(
* If `bootstrapOverride` is `true` (by default), any local cached data will be overridden with the bootstrap specified.
* If `bootstrapOverride` is `false` any local cached data will not be overridden unless the local cache is empty.

## Manage your own refresh mechanism

You can opt out of the Unleash feature flag auto-refresh mechanism and metrics update by settings the `refreshInterval` and/or `metricsInterval` options to `0`.
In this case, it becomes your responsibility to call `updateToggles` and/or `sendMetrics` methods.

## Release guide
* Run tests: `flutter test`
Expand Down
6 changes: 3 additions & 3 deletions lib/metrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class Metrics {
: bucket = Bucket(clock);

Future<void> start() async {
if (disableMetrics) {
if (disableMetrics || metricsInterval == 0) {
return;
}

Expand Down Expand Up @@ -119,14 +119,14 @@ class Metrics {
}
}

void sendMetrics() async {
Future<void> sendMetrics() async {
bucket.closeBucket();
if (bucket.isEmpty()) {
return;
}

final localBucket = bucket;
// For now, accept that a failing request will lose the metrics.
// For now, accept that a failing request will loose the metrics.
bucket = Bucket(clock);

try {
Expand Down
14 changes: 13 additions & 1 deletion lib/unleash_proxy_client_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,18 @@ class UnleashClient extends EventEmitter {
}
}

Future<void> sendMetrics() async {
await metrics.sendMetrics();
}

Future<void> updateToggles() async {
if (clientState != ClientState.ready) {
await _waitForEvent('ready');
await _fetchToggles();
}
await _fetchToggles();
}

void _updateContextField(String field, String value) {
if (field == 'userId') {
context.userId = value;
Expand Down Expand Up @@ -358,7 +370,7 @@ class UnleashClient extends EventEmitter {
emit(readyEvent);
}

if (!disableRefresh) {
if (!disableRefresh && refreshInterval > 0) {
timer = Timer.periodic(Duration(seconds: refreshInterval), (timer) {
_fetchToggles();
});
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: A Flutter/Dart client that can be used together with the unleash-pr
homepage: https://github.com/Unleash/unleash_proxy_client_flutter
repository: https://github.com/Unleash/unleash_proxy_client_flutter
issue_tracker: https://github.com/Unleash/unleash_proxy_client_flutter
version: 1.6.0
version: 1.7.0

environment:
sdk: ">=2.18.0 <4.0.0"
Expand Down
94 changes: 94 additions & 0 deletions test/unleash_proxy_client_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,100 @@ void main() {
});
});

test('can manually update toggles', () async {
final getMock = GetMock();
final unleash = UnleashClient(
url: url,
clientKey: 'proxy-123',
appName: 'flutter-test',
disableRefresh: true,
storageProvider: InMemoryStorageProvider(),
fetcher: getMock);

var updateEventCount = 0;
unleash.on('update', (_) {
updateEventCount += 1;
});

await unleash.start();
expect(getMock.calledTimes, 1);
expect(updateEventCount, 1);

await unleash.updateToggles();
expect(getMock.calledTimes, 2);
expect(updateEventCount, 2);

await unleash.updateToggles();
expect(getMock.calledTimes, 3);
expect(updateEventCount, 3);
});

test('should not update toggles when not started', () async {
final getMock = GetMock();
final unleash = UnleashClient(
url: url,
clientKey: 'proxy-123',
appName: 'flutter-test',
refreshInterval: 0,
storageProvider: InMemoryStorageProvider(),
fetcher: getMock);

unleash.updateToggles();
expect(getMock.calledTimes, 0);
});

test('update toggles should wait on asynchronous start', () async {
final getMock = GetMock();
final unleash = UnleashClient(
url: url,
clientKey: 'proxy-123',
appName: 'flutter-test',
refreshInterval: 0,
storageProvider: InMemoryStorageProvider(),
fetcher: getMock);

final completer = Completer<void>();
// Ready should be registered before we start the client.
unleash.on('ready', (_) {
completer.complete();
});

unleash.start();
unleash.updateToggles();

await completer.future;

expect(getMock.calledTimes, 1);

await unleash.updateToggles();

expect(getMock.calledTimes, 3);
});

test('can manually send metrics', () async {
var postMock = PostMock(payload: '''{}''', status: 200, headers: {});
final getMock = GetMock(body: mockData, status: 200, headers: {});
final unleash = UnleashClient(
url: url,
clientKey: 'proxy-123',
appName: 'flutter-test',
refreshInterval: 0,
metricsInterval: 0,
storageProvider: InMemoryStorageProvider(),
fetcher: getMock,
poster: postMock);

await unleash.start();

// no buckets to send
await unleash.sendMetrics();
expect(postMock.calledTimes, 0);

unleash.isEnabled('flutter-on');
await unleash.sendMetrics();
expect(postMock.calledTimes, 1);
});

test('stopping client should cancel the timer', () {
fakeAsync((async) {
final getMock = GetMock();
Expand Down

0 comments on commit e30c4a1

Please sign in to comment.