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

HackWeek - Feature Flags Support #984

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
212f896
HackWeek - feature flags
marandaneto Aug 22, 2022
4f336aa
add import
marandaneto Aug 22, 2022
d75831f
parsing of the spec
marandaneto Aug 22, 2022
14d7c9c
add type enum and rename evaluation to evaluation rule
marandaneto Aug 22, 2022
de64e1d
add feature flags dump and transform it to a map
marandaneto Aug 22, 2022
fb53a05
expose fetch feature flags api
marandaneto Aug 22, 2022
5a9f9be
add test for parsing feature flag response
marandaneto Aug 22, 2022
2cef8f6
fix dsn parsing and change request to post
marandaneto Aug 22, 2022
2381b41
add payload field
marandaneto Aug 22, 2022
c7dab0d
temp commit
marandaneto Aug 22, 2022
db85e99
added random number generator
marandaneto Aug 23, 2022
8ccccc0
add default value
marandaneto Aug 23, 2022
726b47d
add caching of feature flags
marandaneto Aug 23, 2022
4329bd1
fix
marandaneto Aug 23, 2022
264889e
fix
marandaneto Aug 23, 2022
901d883
implement fetch for flutter transport
marandaneto Aug 23, 2022
b662c62
fix get feature flag info
marandaneto Aug 24, 2022
4de969b
fixes
marandaneto Aug 24, 2022
eeab0aa
add method with generics
marandaneto Aug 24, 2022
c0b5c80
remove non used code
marandaneto Aug 24, 2022
9226546
revert mocked transport
marandaneto Aug 24, 2022
4f8507c
ref
marandaneto Aug 24, 2022
f794350
fix type check
marandaneto Aug 24, 2022
5f7505a
read traces sample rate and error traces rate automatically
marandaneto Aug 24, 2022
f93e430
add group
marandaneto Aug 25, 2022
1f323e8
fix tests
marandaneto Aug 25, 2022
14a6602
fetch feature flags on start
marandaneto Aug 25, 2022
7c6b89c
fix tests
marandaneto Aug 26, 2022
45bb842
fix broken tests
marandaneto Aug 26, 2022
c4a77ae
vendor sha1
marandaneto Aug 26, 2022
a9a9361
supress long method
marandaneto Aug 26, 2022
163ddcf
vendor Object.hashAll
marandaneto Aug 26, 2022
0fac27e
remove non used comment
marandaneto Aug 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 82 additions & 79 deletions dart/example/bin/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:collection';

import 'package:sentry/sentry.dart';

Expand All @@ -11,99 +12,101 @@ import 'event_example.dart';
/// Sends a test exception report to Sentry.io using this Dart client.
Future<void> main() async {
// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io
// const dsn =
// 'https://[email protected]/5428562';
const dsn =
'https://[email protected].io/5428562';
'https://[email protected].io/2';

await Sentry.init(
(options) => options
..dsn = dsn
..debug = true
..sendDefaultPii = true
..addEventProcessor(TagEventProcessor()),
(options) {
options.dsn = dsn;
options.debug = true;
options.release = '[email protected]+1';
options.environment = 'prod';
options.experimental['featureFlagsEnabled'] = true;
},
appRunner: runApp,
);
}

Future<void> runApp() async {
print('\nReporting a complete event example: ');

Sentry.addBreadcrumb(
Breadcrumb(
message: 'Authenticated user',
category: 'auth',
type: 'debug',
data: {
'admin': true,
'permissions': [1, 2, 3]
},
),
);

await Sentry.configureScope((scope) async {
await scope.setUser(SentryUser(
id: '800',
username: 'first-user',
email: '[email protected]',
// ipAddress: '127.0.0.1', sendDefaultPii feature is enabled
extras: <String, String>{'first-sign-in': '2020-01-01'},
));
scope
// ..fingerprint = ['example-dart'], fingerprint forces events to group together
..transaction = '/example/app'
..level = SentryLevel.warning;
await scope.setTag('build', '579');
await scope.setExtra('company-name', 'Dart Inc');
await scope.setUser(
SentryUser(
id: '800',
),
);
// await scope.setTag('isSentryDev', 'true');
});

// Sends a full Sentry event payload to show the different parts of the UI.
final sentryId = await Sentry.captureEvent(event);

print('Capture event result : SentryId : $sentryId');

print('\nCapture message: ');

// Sends a full Sentry event payload to show the different parts of the UI.
final messageSentryId = await Sentry.captureMessage(
'Message 1',
level: SentryLevel.warning,
template: 'Message %s',
params: ['1'],
// accessToProfilingRollout
final accessToProfilingRollout = await Sentry.isFeatureFlagEnabled(
'accessToProfiling',
defaultValue: false,
context: (myContext) => {
myContext.tags['userSegment'] = 'slow',
},
);
print(
'accessToProfilingRollout $accessToProfilingRollout'); // false for user 800

// accessToProfilingMatch
final accessToProfilingMatch = await Sentry.isFeatureFlagEnabled(
'accessToProfiling',
defaultValue: false,
context: (myContext) => {
myContext.tags['isSentryDev'] = 'true',
},
);
print('accessToProfilingMatch $accessToProfilingMatch'); // returns true

// profilingEnabledMatch
final profilingEnabledMatch = await Sentry.isFeatureFlagEnabled(
'profilingEnabled',
defaultValue: false,
context: (myContext) => {
myContext.tags['isSentryDev'] = 'true',
},
);
print('profilingEnabledMatch $profilingEnabledMatch'); // returns true

print('Capture message result : SentryId : $messageSentryId');

try {
await loadConfig();
} catch (error, stackTrace) {
print('\nReporting the following stack trace: ');
print(stackTrace);
final sentryId = await Sentry.captureException(
error,
stackTrace: stackTrace,
);

print('Capture exception result : SentryId : $sentryId');
}
// profilingEnabledRollout
final profilingEnabledRollout = await Sentry.isFeatureFlagEnabled(
'profilingEnabled',
defaultValue: false,
);
print(
'profilingEnabledRollout $profilingEnabledRollout'); // false for user 800

// loginBannerMatch
final loginBannerMatch = await Sentry.getFeatureFlagValueAsync<String>(
'loginBanner',
defaultValue: 'banner0',
context: (myContext) => {
myContext.tags['isSentryDev'] = 'true',
},
);
print('loginBannerMatch $loginBannerMatch'); // returns banner1

// capture unhandled error
await loadConfig();
}
// loginBannerMatch2
final loginBannerMatch2 = await Sentry.getFeatureFlagValueAsync<String>(
'loginBanner',
defaultValue: 'banner0',
);
print('loginBannerMatch2 $loginBannerMatch2'); // returns banner2

Future<void> loadConfig() async {
await parseConfig();
}
// tracesSampleRate
final tracesSampleRate = await Sentry.getFeatureFlagValueAsync<double>(
'tracesSampleRate',
defaultValue: 0.0,
);
print('tracesSampleRate $tracesSampleRate'); // returns 0.25

Future<void> parseConfig() async {
await decode();
}
// final flag = await Sentry.getFeatureFlagInfo('loginBanner',
// context: (myContext) => {
// myContext.tags['myCustomTag'] = 'true',
// });

Future<void> decode() async {
throw StateError('This is a test error');
// print(flag?.payload?['internal_setting'] ?? 'whaat');
// print(flag?.payload ?? {});
}

class TagEventProcessor extends EventProcessor {
@override
FutureOr<SentryEvent?> apply(SentryEvent event, {hint}) {
return event..tags?.addAll({'page-locale': 'en-us'});
}
}
Future<void> runApp() async {}
8 changes: 8 additions & 0 deletions dart/lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ export 'src/http_client/sentry_http_client.dart';
export 'src/http_client/sentry_http_client_error.dart';
export 'src/sentry_attachment/sentry_attachment.dart';
export 'src/sentry_user_feedback.dart';
export 'src/transport/rate_limiter.dart';
export 'src/transport/http_transport.dart';
// tracing
export 'src/tracing.dart';
export 'src/sentry_measurement.dart';
// feature flags
export 'src/feature_flags/feature_flag.dart';
export 'src/feature_flags/evaluation_rule.dart';
export 'src/feature_flags/evaluation_type.dart';
export 'src/feature_flags/feature_flag_context.dart';
export 'src/feature_flags/feature_flag_info.dart';
54 changes: 54 additions & 0 deletions dart/lib/src/cryptography/digest.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:typed_data';
import '../utils/hash_code.dart';

/// A message digest as computed by a `Hash` or `HMAC` function.
class Digest {
/// The message digest as an array of bytes.
final List<int> bytes;

Digest(this.bytes);

/// Returns whether this is equal to another digest.
///
/// This should be used instead of manual comparisons to avoid leaking
/// information via timing.
@override
bool operator ==(Object other) {
if (other is Digest) {
final a = bytes;
final b = other.bytes;
final n = a.length;
if (n != b.length) {
return false;
}
var mismatch = 0;
for (var i = 0; i < n; i++) {
mismatch |= a[i] ^ b[i];
}
return mismatch == 0;
}
return false;
}

@override
int get hashCode => hashAll(bytes);

/// The message digest as a string of hexadecimal digits.
@override
String toString() => _hexEncode(bytes);
}

String _hexEncode(List<int> bytes) {
const hexDigits = '0123456789abcdef';
var charCodes = Uint8List(bytes.length * 2);
for (var i = 0, j = 0; i < bytes.length; i++) {
var byte = bytes[i];
charCodes[j++] = hexDigits.codeUnitAt((byte >> 4) & 0xF);
charCodes[j++] = hexDigits.codeUnitAt(byte & 0xF);
}
return String.fromCharCodes(charCodes);
}
29 changes: 29 additions & 0 deletions dart/lib/src/cryptography/digest_sink.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'digest.dart';

/// A sink used to get a digest value out of `Hash.startChunkedConversion`.
class DigestSink extends Sink<Digest> {
/// The value added to the sink.
///
/// A value must have been added using [add] before reading the `value`.
Digest get value => _value!;

Digest? _value;

/// Adds [value] to the sink.
///
/// Unlike most sinks, this may only be called once.
@override
void add(Digest value) {
if (_value != null) throw StateError('add may only be called once.');
_value = value;
}

@override
void close() {
if (_value == null) throw StateError('add must be called once.');
}
}
35 changes: 35 additions & 0 deletions dart/lib/src/cryptography/hash.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';

import 'digest.dart';
import 'digest_sink.dart';

/// An interface for cryptographic hash functions.
///
/// Every hash is a converter that takes a list of ints and returns a single
/// digest. When used in chunked mode, it will only ever add one digest to the
/// inner [Sink].
abstract class Hash extends Converter<List<int>, Digest> {
/// The internal block size of the hash in bytes.
///
/// This is exposed for use by the `Hmac` class,
/// which needs to know the block size for the [Hash] it uses.
int get blockSize;

const Hash();

@override
Digest convert(List<int> input) {
var innerSink = DigestSink();
var outerSink = startChunkedConversion(innerSink);
outerSink.add(input);
outerSink.close();
return innerSink.value;
}

@override
ByteConversionSink startChunkedConversion(Sink<Digest> sink);
}
Loading