Skip to content

feat: support feature flags with variants #1230

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@

### Added

- Add support for Feature Flags APIs `Instabug.addFeatureFlags`, `Instabug.removeFeatureFlags` and `Instabug.clearAllFeatureFlags` ([#1230](https://github.com/Instabug/Instabug-React-Native/pull/1230)).
- Export `uploadSourcemaps` and `uploadSoFiles` utilities in the `instabug-reactnative/upload` sub-package for usage in custom Node.js upload scripts ([#1252](https://github.com/Instabug/Instabug-React-Native/pull/1252)).

### Deprecated

- Deprecate Experiments APIs `Instabug.addExperiments`, `Instabug.removeExperiments` and `Instabug.clearAllExperiments` in favor of the new Feature Flags APIs ([#1230](https://github.com/Instabug/Instabug-React-Native/pull/1230)).

### Fixed

- Fix APM network logging on Android ([#1253](https://github.com/Instabug/Instabug-React-Native/pull/1253)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
Expand All @@ -29,6 +30,7 @@
import com.instabug.library.LogLevel;
import com.instabug.library.ReproConfigurations;
import com.instabug.library.core.InstabugCore;
import com.instabug.library.featuresflags.model.IBGFeatureFlag;
import com.instabug.library.internal.module.InstabugLocale;
import com.instabug.library.invocation.InstabugInvocationEvent;
import com.instabug.library.logging.InstabugLog;
Expand All @@ -41,6 +43,7 @@
import com.instabug.reactlibrary.utils.MainThreadHandler;

import com.instabug.reactlibrary.utils.RNTouchedViewExtractor;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
Expand All @@ -50,6 +53,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -1023,6 +1027,9 @@ public void run() {
});
}

/**
* @deprecated see {@link #addFeatureFlags(ReadableArray)}
*/
@ReactMethod
public void addExperiments(final ReadableArray experiments) {
MainThreadHandler.runOnMainThread(new Runnable() {
Expand All @@ -1039,6 +1046,9 @@ public void run() {
});
}

/**
* @deprecated see {@link #removeFeatureFlags(ReadableArray)}
*/
@ReactMethod
public void removeExperiments(final ReadableArray experiments) {
MainThreadHandler.runOnMainThread(new Runnable() {
Expand All @@ -1055,6 +1065,9 @@ public void run() {
});
}

/**
* @deprecated see {@link #removeAllFeatureFlags()}
*/
@ReactMethod
public void clearAllExperiments() {
MainThreadHandler.runOnMainThread(new Runnable() {
Expand All @@ -1069,6 +1082,59 @@ public void run() {
});
}

@ReactMethod
public void addFeatureFlags(final ReadableMap featureFlagsMap) {
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
try {
Iterator<Map.Entry<String, Object>> iterator = featureFlagsMap.getEntryIterator();
ArrayList<IBGFeatureFlag> featureFlags = new ArrayList<>();
while (iterator.hasNext()) {
Map.Entry<String, Object> item = iterator.next();
String variant = (String) item.getValue();
String name = item.getKey();
featureFlags.add(new IBGFeatureFlag(name, variant.isEmpty() ? null : variant));
}
if (!featureFlags.isEmpty()) {
Instabug.addFeatureFlags(featureFlags);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

@ReactMethod
public void removeFeatureFlags(final ReadableArray featureFlags) {
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
try {
ArrayList<String> stringArray = ArrayUtil.parseReadableArrayOfStrings(featureFlags);
Instabug.removeFeatureFlag(stringArray);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

@ReactMethod
public void removeAllFeatureFlags() {
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
try {
Instabug.removeAllFeatureFlags();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

@ReactMethod
public void willRedirectToStore() {
MainThreadHandler.runOnMainThread(new Runnable() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.instabug.library.IssueType;
import com.instabug.library.ReproConfigurations;
import com.instabug.library.ReproMode;
import com.instabug.library.featuresflags.model.IBGFeatureFlag;
import com.instabug.library.internal.module.InstabugLocale;
import com.instabug.library.ui.onboarding.WelcomeMessage;
import com.instabug.reactlibrary.utils.MainThreadHandler;
Expand All @@ -38,6 +39,7 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -567,8 +569,6 @@ public void testIdentifyUserWithId() {

@Test
public void given$clearAllExperiments_whenQuery_thenShouldCallNativeApi() {
// given

// when
rnModule.clearAllExperiments();

Expand All @@ -577,6 +577,56 @@ public void testIdentifyUserWithId() {
Instabug.clearAllExperiments();
}

@Test
public void testAddFeatureFlags() {
// given
JavaOnlyMap map = new JavaOnlyMap();
map.putString("key1", "value1");
map.putString("key2", "value2");

// when
rnModule.addFeatureFlags(map);

// then
Iterator<Map.Entry<String, Object>> iterator = map.getEntryIterator();
ArrayList<IBGFeatureFlag> featureFlags = new ArrayList<>();
while (iterator.hasNext()) {
Map.Entry<String, Object> item = iterator.next();
featureFlags.add(new IBGFeatureFlag(item.getKey(), (String) item.getValue()));
}

mockInstabug.verify(() -> Instabug.addFeatureFlags(featureFlags));

}

@Test
public void testRemoveFeatureFlags() {
// given
JavaOnlyArray array = new JavaOnlyArray();
array.pushString("exp1");
array.pushString("exp2");

// when
rnModule.removeFeatureFlags(array);

// then
List<String> expectedList = new ArrayList<String>();
for (Object o : array.toArrayList()) {
expectedList.add((String) o);
}
mockInstabug.verify(() -> Instabug.removeFeatureFlag(expectedList));

}

@Test
public void testRemoveAllFeatureFlags() {
// when
rnModule.removeAllFeatureFlags();

// then
mockInstabug.verify(() -> Instabug.removeAllFeatureFlags());
}

@Test
public void testWillRedirectToStore() {
// when
Expand Down
45 changes: 42 additions & 3 deletions examples/default/ios/InstabugTests/InstabugSampleTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ - (void)testInit {
NSArray *invocationEvents = [NSArray arrayWithObjects:[NSNumber numberWithInteger:floatingButtonInvocationEvent], nil];
BOOL useNativeNetworkInterception = YES;
IBGSDKDebugLogsLevel sdkDebugLogsLevel = IBGSDKDebugLogsLevelDebug;

OCMStub([mock setCodePushVersion:codePushVersion]);

[self.instabugBridge init:appToken invocationEvents:invocationEvents debugLogsLevel:sdkDebugLogsLevel useNativeNetworkInterception:useNativeNetworkInterception codePushVersion:codePushVersion];
Expand All @@ -85,9 +85,9 @@ - (void)testInit {
- (void)testSetCodePushVersion {
id mock = OCMClassMock([Instabug class]);
NSString *codePushVersion = @"123";

[self.instabugBridge setCodePushVersion:codePushVersion];

OCMVerify([mock setCodePushVersion:codePushVersion]);
}

Expand Down Expand Up @@ -498,4 +498,43 @@ - (void)testClearAllExperiments {
OCMVerify([mock clearAllExperiments]);
}

- (void)testAddFeatureFlags {
id mock = OCMClassMock([Instabug class]);
NSDictionary *featureFlagsMap = @{ @"key13" : @"value1", @"key2" : @"value2"};

OCMStub([mock addFeatureFlags :[OCMArg any]]);
[self.instabugBridge addFeatureFlags:featureFlagsMap];
OCMVerify([mock addFeatureFlags: [OCMArg checkWithBlock:^(id value) {
NSArray<IBGFeatureFlag *> *featureFlags = value;
NSString* firstFeatureFlagName = [featureFlags objectAtIndex:0 ].name;
NSString* firstFeatureFlagKey = [[featureFlagsMap allKeys] objectAtIndex:0] ;
if([ firstFeatureFlagKey isEqualToString: firstFeatureFlagName]){
return YES;
}
return NO;
}]]);
}

- (void)testRemoveFeatureFlags {
id mock = OCMClassMock([Instabug class]);
NSArray *featureFlags = @[@"exp1", @"exp2"];
[self.instabugBridge removeFeatureFlags:featureFlags];
OCMVerify([mock removeFeatureFlags: [OCMArg checkWithBlock:^(id value) {
NSArray<IBGFeatureFlag *> *featureFlagsObJ = value;
NSString* firstFeatureFlagName = [featureFlagsObJ objectAtIndex:0 ].name;
NSString* firstFeatureFlagKey = [featureFlags firstObject] ;
if([ firstFeatureFlagKey isEqualToString: firstFeatureFlagName]){
return YES;
}
return NO;
}]]);
}

- (void)testRemoveAllFeatureFlags {
id mock = OCMClassMock([Instabug class]);
OCMStub([mock removeAllFeatureFlags]);
[self.instabugBridge removeAllFeatureFlags];
OCMVerify([mock removeAllFeatureFlags]);
}

@end
76 changes: 76 additions & 0 deletions examples/default/src/screens/SettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ export const SettingsScreen: React.FC = () => {
const [userID, setUserID] = useState('');
const [userAttributeKey, setUserAttributeKey] = useState('');
const [userAttributeValue, setUserAttributeValue] = useState('');
const [featureFlagName, setFeatureFlagName] = useState('');
const [featureFlagVariant, setfeatureFlagVariant] = useState('');

const toast = useToast();
const [userAttributesFormError, setUserAttributesFormError] = useState<any>({});
const [featureFlagFormError, setFeatureFlagFormError] = useState<any>({});

const validateUserAttributeForm = () => {
const errors: any = {};
Expand All @@ -31,6 +35,15 @@ export const SettingsScreen: React.FC = () => {
setUserAttributesFormError(errors);
return Object.keys(errors).length === 0;
};
const validateFeatureFlagForm = () => {
const errors: any = {};
if (featureFlagName.length === 0) {
errors.featureFlagName = 'Value is required';
}
setFeatureFlagFormError(errors);
return Object.keys(errors).length === 0;
};

const styles = StyleSheet.create({
inputWrapper: {
padding: 4,
Expand Down Expand Up @@ -60,6 +73,37 @@ export const SettingsScreen: React.FC = () => {
setUserAttributeValue('');
}
};
const saveFeatureFlags = () => {
if (validateFeatureFlagForm()) {
Instabug.addFeatureFlag({
name: featureFlagName,
variant: featureFlagVariant,
});
toast.show({
description: 'Feature Flag added successfully',
});
setFeatureFlagName('');
setfeatureFlagVariant('');
}
};
const removeFeatureFlags = () => {
if (validateFeatureFlagForm()) {
Instabug.removeFeatureFlag(featureFlagName);
toast.show({
description: 'Feature Flag removed successfully',
});
setFeatureFlagName('');
setfeatureFlagVariant('');
}
};
const removeAllFeatureFlags = () => {
Instabug.removeAllFeatureFlags();
toast.show({
description: 'Feature Flags removed successfully',
});
setFeatureFlagName('');
setfeatureFlagVariant('');
};

const logout = () => {
Instabug.logOut();
Expand Down Expand Up @@ -215,6 +259,38 @@ export const SettingsScreen: React.FC = () => {
</Button>
</VStack>
</VerticalListTile>
<VerticalListTile title="Support varient">
<VStack>
<View style={styles.formContainer}>
<View style={styles.inputWrapper}>
<InputField
placeholder="FeatureFlag name"
onChangeText={(key) => setFeatureFlagName(key)}
value={featureFlagName}
errorText={featureFlagFormError.featureFlagName}
/>
</View>
<View style={styles.inputWrapper}>
<InputField
placeholder="Feature Flag varient"
onChangeText={(value) => setfeatureFlagVariant(value)}
value={featureFlagVariant}
/>
</View>
</View>

<Button mt="4" onPress={saveFeatureFlags}>
Save Feature flag
</Button>

<Button mt="4" colorScheme="red" onPress={removeFeatureFlags}>
remove Feature Flag
</Button>
<Button mt="4" colorScheme="red" onPress={removeAllFeatureFlags}>
remove all Feature Flag
</Button>
</VStack>
</VerticalListTile>
</Screen>
</ScrollView>
);
Expand Down
4 changes: 3 additions & 1 deletion ios/RNInstabug/InstabugReactBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,7 @@
- (void)addExperiments:(NSArray *)experiments;
- (void)removeExperiments:(NSArray *)experiments;
- (void)clearAllExperiments;

- (void)addFeatureFlags:(NSDictionary *)featureFlagsMap;
- (void)removeFeatureFlags:(NSArray *)featureFlags;
- (void)removeAllFeatureFlags;
@end
Loading