Skip to content

Commit

Permalink
Preserve insertion order in feature flags
Browse files Browse the repository at this point in the history
  • Loading branch information
kstenerud committed Jul 8, 2022
1 parent 52b0b6f commit 1c54a0c
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 28 deletions.
20 changes: 20 additions & 0 deletions Bugsnag/BugsnagFeatureFlag.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,24 @@ - (instancetype)initWithName:(NSString *)name variant:(nullable NSString *)varia
return self;
}

- (BOOL)isEqual:(id)object {
if (object == nil) {
return NO;
}

if (self == object) {
return YES;
}

if (![object isKindOfClass:[BugsnagFeatureFlag class]]) {
return NO;
}

BugsnagFeatureFlag *obj = (BugsnagFeatureFlag *)object;

// Ignore the variant when checking for equality. We only care if the name matches
// when checking for duplicates.
return [obj.name isEqualToString:self.name];
}

@end
4 changes: 2 additions & 2 deletions Bugsnag/Client/BugsnagClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -994,8 +994,8 @@ - (void)setObserver:(BSGClientObserver)observer {
};

@synchronized (self.featureFlagStore) {
for (NSString *name in self.featureFlagStore) {
observer(BSGClientObserverAddFeatureFlag, [BugsnagFeatureFlag flagWithName:name variant:self.featureFlagStore[name]]);
for (BugsnagFeatureFlag *flag in self.featureFlagStore) {
observer(BSGClientObserverAddFeatureFlag, flag);
}
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion Bugsnag/Helpers/BSGFeatureFlagStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

NS_ASSUME_NONNULL_BEGIN

typedef NSMutableDictionary<NSString *, id> BSGFeatureFlagStore;
typedef NSMutableArray<BugsnagFeatureFlag *> BSGFeatureFlagStore;

void BSGFeatureFlagStoreAddFeatureFlag(BSGFeatureFlagStore *store, NSString *name, NSString *_Nullable variant);

Expand Down
30 changes: 17 additions & 13 deletions Bugsnag/Helpers/BSGFeatureFlagStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,53 @@
#import "BSGKeys.h"
#import "BugsnagFeatureFlag.h"

static void internalAddFeatureFlag(BSGFeatureFlagStore *store, BugsnagFeatureFlag *flag) {
[store removeObject:flag];
[store addObject:flag];
}

void BSGFeatureFlagStoreAddFeatureFlag(BSGFeatureFlagStore *store, NSString *name, NSString *_Nullable variant) {
store[name] = variant ?: [NSNull null];
internalAddFeatureFlag(store, [BugsnagFeatureFlag flagWithName:name variant:variant]);
}

void BSGFeatureFlagStoreAddFeatureFlags(BSGFeatureFlagStore *store, NSArray<BugsnagFeatureFlag *> *featureFlags) {
for (BugsnagFeatureFlag *featureFlag in featureFlags) {
store[featureFlag.name] = featureFlag.variant ?: [NSNull null];
internalAddFeatureFlag(store, featureFlag);
}
}

void BSGFeatureFlagStoreClear(BSGFeatureFlagStore *store, NSString *_Nullable name) {
if (name) {
[store removeObjectForKey:(NSString *_Nonnull)name];
[store removeObject:[BugsnagFeatureFlag flagWithName:(NSString * _Nonnull)name]];
} else {
[store removeAllObjects];
}
}

NSArray<NSDictionary *> * BSGFeatureFlagStoreToJSON(BSGFeatureFlagStore *store) {
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
for (NSString *name in store) {
id variant = store[name];
if ([variant isKindOfClass:[NSString class]]) {
[result addObject:@{BSGKeyFeatureFlag: name, BSGKeyVariant: variant}];
for (BugsnagFeatureFlag *flag in store) {
if (flag.variant) {
[result addObject:@{BSGKeyFeatureFlag: flag.name, BSGKeyVariant: (NSString * _Nonnull)flag.variant}];
} else {
[result addObject:@{BSGKeyFeatureFlag: name}];
[result addObject:@{BSGKeyFeatureFlag: flag.name}];
}
}
[result sortUsingComparator:^NSComparisonResult(NSDictionary *_Nonnull obj1, NSDictionary *_Nonnull obj2) {
return [(NSString *)obj1[BSGKeyFeatureFlag] compare:(NSString *)obj2[BSGKeyFeatureFlag]];
}];
return result;
}

BSGFeatureFlagStore * BSGFeatureFlagStoreFromJSON(id json) {
BSGFeatureFlagStore *store = [NSMutableDictionary dictionary];
BSGFeatureFlagStore *store = [NSMutableArray array];
if ([json isKindOfClass:[NSArray class]]) {
for (id item in json) {
if ([item isKindOfClass:[NSDictionary class]]) {
NSString *featureFlag = item[BSGKeyFeatureFlag];
if ([featureFlag isKindOfClass:[NSString class]]) {
id variant = item[BSGKeyVariant];
store[featureFlag] = [variant isKindOfClass:[NSString class]] ? variant : [NSNull null];
if (![variant isKindOfClass:[NSString class]]) {
variant = nil;
}
[store addObject:[BugsnagFeatureFlag flagWithName:featureFlag variant:variant]];
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Changelog

### Enhancements

* Feature flags are now kept in order of insertion or modification rather than in alphabetical order.
[#1429](https://github.com/bugsnag/bugsnag-android/pull/1429)

* Send usage telemetry to Bugsnag for product improvement purposes. Can be disabled using `configuration.telemetry`.
[#1422](https://github.com/bugsnag/bugsnag-cocoa/pull/1422)

Expand Down
42 changes: 30 additions & 12 deletions Tests/BugsnagTests/BSGFeatureFlagStoreTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,45 @@ @implementation BSGFeatureFlagStoreTests
- (void)test {
BSGFeatureFlagStore *store = [[BSGFeatureFlagStore alloc] init];
XCTAssertEqualObjects(BSGFeatureFlagStoreToJSON(store), @[]);
BSGFeatureFlagStoreAddFeatureFlag(store, @"featureA", @"enabled");

BSGFeatureFlagStoreAddFeatureFlag(store, @"featureC", @"checked");
XCTAssertEqualObjects(BSGFeatureFlagStoreToJSON(store),
(@[@{@"featureFlag": @"featureA", @"variant": @"enabled"}]));
(@[@{@"featureFlag": @"featureC", @"variant": @"checked"}]));

BSGFeatureFlagStoreAddFeatureFlags(store, @[[BugsnagFeatureFlag flagWithName:@"featureA"]]);
BSGFeatureFlagStoreAddFeatureFlag(store, @"featureA", @"enabled");
XCTAssertEqualObjects(BSGFeatureFlagStoreToJSON(store),
@[@{@"featureFlag": @"featureA"}]);

(@[
@{@"featureFlag": @"featureC", @"variant": @"checked"},
@{@"featureFlag": @"featureA", @"variant": @"enabled"}
]));

BSGFeatureFlagStoreAddFeatureFlag(store, @"featureB", nil);
XCTAssertEqualObjects(BSGFeatureFlagStoreToJSON(store),
(@[@{@"featureFlag": @"featureA"},
@{@"featureFlag": @"featureB"}]));

(@[
@{@"featureFlag": @"featureC", @"variant": @"checked"},
@{@"featureFlag": @"featureA", @"variant": @"enabled"},
@{@"featureFlag": @"featureB"}
]));


BSGFeatureFlagStoreAddFeatureFlags(store, @[[BugsnagFeatureFlag flagWithName:@"featureA"]]);
XCTAssertEqualObjects(BSGFeatureFlagStoreToJSON(store),
(@[
@{@"featureFlag": @"featureC", @"variant": @"checked"},
@{@"featureFlag": @"featureB"},
@{@"featureFlag": @"featureA"}
]));

XCTAssertEqualObjects(BSGFeatureFlagStoreFromJSON(BSGFeatureFlagStoreToJSON(store)),
store);

BSGFeatureFlagStoreClear(store, @"featureA");
BSGFeatureFlagStoreClear(store, @"featureB");
XCTAssertEqualObjects(BSGFeatureFlagStoreToJSON(store),
@[@{@"featureFlag": @"featureB"}]);

(@[
@{@"featureFlag": @"featureC", @"variant": @"checked"},
@{@"featureFlag": @"featureA"}
]));

BSGFeatureFlagStoreClear(store, nil);
XCTAssertEqualObjects(BSGFeatureFlagStoreToJSON(store), @[]);
}
Expand Down

0 comments on commit 1c54a0c

Please sign in to comment.