Skip to content

Commit cb5f078

Browse files
ahmedAlaaInstabuga7medev
authored andcommitted
feat: support feature flags with variants (#1230)
Jira ID: MOB-14684 --------- Co-authored-by: Ahmed Elrefaey <[email protected]>
1 parent dc6dc28 commit cb5f078

File tree

12 files changed

+396
-6
lines changed

12 files changed

+396
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44

55
### Added
66

7+
- Add support for Feature Flags APIs `Instabug.addFeatureFlags`, `Instabug.removeFeatureFlags` and `Instabug.clearAllFeatureFlags` ([#1230](https://github.com/Instabug/Instabug-React-Native/pull/1230)).
78
- 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)).
89

10+
### Deprecated
11+
12+
- 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)).
13+
914
### Fixed
1015

1116
- Fix APM network logging on Android ([#1253](https://github.com/Instabug/Instabug-React-Native/pull/1253)).

android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.facebook.react.bridge.ReactApplicationContext;
1717
import com.facebook.react.bridge.ReactMethod;
1818
import com.facebook.react.bridge.ReadableArray;
19+
import com.facebook.react.bridge.ReadableMap;
1920
import com.facebook.react.bridge.WritableArray;
2021
import com.facebook.react.bridge.WritableMap;
2122
import com.facebook.react.bridge.WritableNativeArray;
@@ -29,6 +30,7 @@
2930
import com.instabug.library.LogLevel;
3031
import com.instabug.library.ReproConfigurations;
3132
import com.instabug.library.core.InstabugCore;
33+
import com.instabug.library.featuresflags.model.IBGFeatureFlag;
3234
import com.instabug.library.internal.module.InstabugLocale;
3335
import com.instabug.library.invocation.InstabugInvocationEvent;
3436
import com.instabug.library.logging.InstabugLog;
@@ -41,6 +43,7 @@
4143
import com.instabug.reactlibrary.utils.MainThreadHandler;
4244

4345
import com.instabug.reactlibrary.utils.RNTouchedViewExtractor;
46+
4447
import org.json.JSONException;
4548
import org.json.JSONObject;
4649
import org.json.JSONTokener;
@@ -50,6 +53,7 @@
5053
import java.util.ArrayList;
5154
import java.util.Arrays;
5255
import java.util.HashMap;
56+
import java.util.Iterator;
5357
import java.util.List;
5458
import java.util.Locale;
5559
import java.util.Map;
@@ -1023,6 +1027,9 @@ public void run() {
10231027
});
10241028
}
10251029

1030+
/**
1031+
* @deprecated see {@link #addFeatureFlags(ReadableArray)}
1032+
*/
10261033
@ReactMethod
10271034
public void addExperiments(final ReadableArray experiments) {
10281035
MainThreadHandler.runOnMainThread(new Runnable() {
@@ -1039,6 +1046,9 @@ public void run() {
10391046
});
10401047
}
10411048

1049+
/**
1050+
* @deprecated see {@link #removeFeatureFlags(ReadableArray)}
1051+
*/
10421052
@ReactMethod
10431053
public void removeExperiments(final ReadableArray experiments) {
10441054
MainThreadHandler.runOnMainThread(new Runnable() {
@@ -1055,6 +1065,9 @@ public void run() {
10551065
});
10561066
}
10571067

1068+
/**
1069+
* @deprecated see {@link #removeAllFeatureFlags()}
1070+
*/
10581071
@ReactMethod
10591072
public void clearAllExperiments() {
10601073
MainThreadHandler.runOnMainThread(new Runnable() {
@@ -1069,6 +1082,59 @@ public void run() {
10691082
});
10701083
}
10711084

1085+
@ReactMethod
1086+
public void addFeatureFlags(final ReadableMap featureFlagsMap) {
1087+
MainThreadHandler.runOnMainThread(new Runnable() {
1088+
@Override
1089+
public void run() {
1090+
try {
1091+
Iterator<Map.Entry<String, Object>> iterator = featureFlagsMap.getEntryIterator();
1092+
ArrayList<IBGFeatureFlag> featureFlags = new ArrayList<>();
1093+
while (iterator.hasNext()) {
1094+
Map.Entry<String, Object> item = iterator.next();
1095+
String variant = (String) item.getValue();
1096+
String name = item.getKey();
1097+
featureFlags.add(new IBGFeatureFlag(name, variant.isEmpty() ? null : variant));
1098+
}
1099+
if (!featureFlags.isEmpty()) {
1100+
Instabug.addFeatureFlags(featureFlags);
1101+
}
1102+
} catch (Exception e) {
1103+
e.printStackTrace();
1104+
}
1105+
}
1106+
});
1107+
}
1108+
1109+
@ReactMethod
1110+
public void removeFeatureFlags(final ReadableArray featureFlags) {
1111+
MainThreadHandler.runOnMainThread(new Runnable() {
1112+
@Override
1113+
public void run() {
1114+
try {
1115+
ArrayList<String> stringArray = ArrayUtil.parseReadableArrayOfStrings(featureFlags);
1116+
Instabug.removeFeatureFlag(stringArray);
1117+
} catch (Exception e) {
1118+
e.printStackTrace();
1119+
}
1120+
}
1121+
});
1122+
}
1123+
1124+
@ReactMethod
1125+
public void removeAllFeatureFlags() {
1126+
MainThreadHandler.runOnMainThread(new Runnable() {
1127+
@Override
1128+
public void run() {
1129+
try {
1130+
Instabug.removeAllFeatureFlags();
1131+
} catch (Exception e) {
1132+
e.printStackTrace();
1133+
}
1134+
}
1135+
});
1136+
}
1137+
10721138
@ReactMethod
10731139
public void willRedirectToStore() {
10741140
MainThreadHandler.runOnMainThread(new Runnable() {

android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.instabug.library.IssueType;
1919
import com.instabug.library.ReproConfigurations;
2020
import com.instabug.library.ReproMode;
21+
import com.instabug.library.featuresflags.model.IBGFeatureFlag;
2122
import com.instabug.library.internal.module.InstabugLocale;
2223
import com.instabug.library.ui.onboarding.WelcomeMessage;
2324
import com.instabug.reactlibrary.utils.MainThreadHandler;
@@ -38,6 +39,7 @@
3839
import java.lang.reflect.Method;
3940
import java.util.ArrayList;
4041
import java.util.HashMap;
42+
import java.util.Iterator;
4143
import java.util.List;
4244
import java.util.Locale;
4345
import java.util.Map;
@@ -567,8 +569,6 @@ public void testIdentifyUserWithId() {
567569

568570
@Test
569571
public void given$clearAllExperiments_whenQuery_thenShouldCallNativeApi() {
570-
// given
571-
572572
// when
573573
rnModule.clearAllExperiments();
574574

@@ -577,6 +577,56 @@ public void testIdentifyUserWithId() {
577577
Instabug.clearAllExperiments();
578578
}
579579

580+
@Test
581+
public void testAddFeatureFlags() {
582+
// given
583+
JavaOnlyMap map = new JavaOnlyMap();
584+
map.putString("key1", "value1");
585+
map.putString("key2", "value2");
586+
587+
// when
588+
rnModule.addFeatureFlags(map);
589+
590+
// then
591+
Iterator<Map.Entry<String, Object>> iterator = map.getEntryIterator();
592+
ArrayList<IBGFeatureFlag> featureFlags = new ArrayList<>();
593+
while (iterator.hasNext()) {
594+
Map.Entry<String, Object> item = iterator.next();
595+
featureFlags.add(new IBGFeatureFlag(item.getKey(), (String) item.getValue()));
596+
}
597+
598+
mockInstabug.verify(() -> Instabug.addFeatureFlags(featureFlags));
599+
600+
}
601+
602+
@Test
603+
public void testRemoveFeatureFlags() {
604+
// given
605+
JavaOnlyArray array = new JavaOnlyArray();
606+
array.pushString("exp1");
607+
array.pushString("exp2");
608+
609+
// when
610+
rnModule.removeFeatureFlags(array);
611+
612+
// then
613+
List<String> expectedList = new ArrayList<String>();
614+
for (Object o : array.toArrayList()) {
615+
expectedList.add((String) o);
616+
}
617+
mockInstabug.verify(() -> Instabug.removeFeatureFlag(expectedList));
618+
619+
}
620+
621+
@Test
622+
public void testRemoveAllFeatureFlags() {
623+
// when
624+
rnModule.removeAllFeatureFlags();
625+
626+
// then
627+
mockInstabug.verify(() -> Instabug.removeAllFeatureFlags());
628+
}
629+
580630
@Test
581631
public void testWillRedirectToStore() {
582632
// when

examples/default/ios/InstabugTests/InstabugSampleTests.m

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ - (void)testInit {
7373
NSArray *invocationEvents = [NSArray arrayWithObjects:[NSNumber numberWithInteger:floatingButtonInvocationEvent], nil];
7474
BOOL useNativeNetworkInterception = YES;
7575
IBGSDKDebugLogsLevel sdkDebugLogsLevel = IBGSDKDebugLogsLevelDebug;
76-
76+
7777
OCMStub([mock setCodePushVersion:codePushVersion]);
7878

7979
[self.instabugBridge init:appToken invocationEvents:invocationEvents debugLogsLevel:sdkDebugLogsLevel useNativeNetworkInterception:useNativeNetworkInterception codePushVersion:codePushVersion];
@@ -85,9 +85,9 @@ - (void)testInit {
8585
- (void)testSetCodePushVersion {
8686
id mock = OCMClassMock([Instabug class]);
8787
NSString *codePushVersion = @"123";
88-
88+
8989
[self.instabugBridge setCodePushVersion:codePushVersion];
90-
90+
9191
OCMVerify([mock setCodePushVersion:codePushVersion]);
9292
}
9393

@@ -498,4 +498,43 @@ - (void)testClearAllExperiments {
498498
OCMVerify([mock clearAllExperiments]);
499499
}
500500

501+
- (void)testAddFeatureFlags {
502+
id mock = OCMClassMock([Instabug class]);
503+
NSDictionary *featureFlagsMap = @{ @"key13" : @"value1", @"key2" : @"value2"};
504+
505+
OCMStub([mock addFeatureFlags :[OCMArg any]]);
506+
[self.instabugBridge addFeatureFlags:featureFlagsMap];
507+
OCMVerify([mock addFeatureFlags: [OCMArg checkWithBlock:^(id value) {
508+
NSArray<IBGFeatureFlag *> *featureFlags = value;
509+
NSString* firstFeatureFlagName = [featureFlags objectAtIndex:0 ].name;
510+
NSString* firstFeatureFlagKey = [[featureFlagsMap allKeys] objectAtIndex:0] ;
511+
if([ firstFeatureFlagKey isEqualToString: firstFeatureFlagName]){
512+
return YES;
513+
}
514+
return NO;
515+
}]]);
516+
}
517+
518+
- (void)testRemoveFeatureFlags {
519+
id mock = OCMClassMock([Instabug class]);
520+
NSArray *featureFlags = @[@"exp1", @"exp2"];
521+
[self.instabugBridge removeFeatureFlags:featureFlags];
522+
OCMVerify([mock removeFeatureFlags: [OCMArg checkWithBlock:^(id value) {
523+
NSArray<IBGFeatureFlag *> *featureFlagsObJ = value;
524+
NSString* firstFeatureFlagName = [featureFlagsObJ objectAtIndex:0 ].name;
525+
NSString* firstFeatureFlagKey = [featureFlags firstObject] ;
526+
if([ firstFeatureFlagKey isEqualToString: firstFeatureFlagName]){
527+
return YES;
528+
}
529+
return NO;
530+
}]]);
531+
}
532+
533+
- (void)testRemoveAllFeatureFlags {
534+
id mock = OCMClassMock([Instabug class]);
535+
OCMStub([mock removeAllFeatureFlags]);
536+
[self.instabugBridge removeAllFeatureFlags];
537+
OCMVerify([mock removeAllFeatureFlags]);
538+
}
539+
501540
@end

examples/default/src/screens/SettingsScreen.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ export const SettingsScreen: React.FC = () => {
1717
const [userID, setUserID] = useState('');
1818
const [userAttributeKey, setUserAttributeKey] = useState('');
1919
const [userAttributeValue, setUserAttributeValue] = useState('');
20+
const [featureFlagName, setFeatureFlagName] = useState('');
21+
const [featureFlagVariant, setfeatureFlagVariant] = useState('');
22+
2023
const toast = useToast();
2124
const [userAttributesFormError, setUserAttributesFormError] = useState<any>({});
25+
const [featureFlagFormError, setFeatureFlagFormError] = useState<any>({});
2226

2327
const validateUserAttributeForm = () => {
2428
const errors: any = {};
@@ -31,6 +35,15 @@ export const SettingsScreen: React.FC = () => {
3135
setUserAttributesFormError(errors);
3236
return Object.keys(errors).length === 0;
3337
};
38+
const validateFeatureFlagForm = () => {
39+
const errors: any = {};
40+
if (featureFlagName.length === 0) {
41+
errors.featureFlagName = 'Value is required';
42+
}
43+
setFeatureFlagFormError(errors);
44+
return Object.keys(errors).length === 0;
45+
};
46+
3447
const styles = StyleSheet.create({
3548
inputWrapper: {
3649
padding: 4,
@@ -60,6 +73,37 @@ export const SettingsScreen: React.FC = () => {
6073
setUserAttributeValue('');
6174
}
6275
};
76+
const saveFeatureFlags = () => {
77+
if (validateFeatureFlagForm()) {
78+
Instabug.addFeatureFlag({
79+
name: featureFlagName,
80+
variant: featureFlagVariant,
81+
});
82+
toast.show({
83+
description: 'Feature Flag added successfully',
84+
});
85+
setFeatureFlagName('');
86+
setfeatureFlagVariant('');
87+
}
88+
};
89+
const removeFeatureFlags = () => {
90+
if (validateFeatureFlagForm()) {
91+
Instabug.removeFeatureFlag(featureFlagName);
92+
toast.show({
93+
description: 'Feature Flag removed successfully',
94+
});
95+
setFeatureFlagName('');
96+
setfeatureFlagVariant('');
97+
}
98+
};
99+
const removeAllFeatureFlags = () => {
100+
Instabug.removeAllFeatureFlags();
101+
toast.show({
102+
description: 'Feature Flags removed successfully',
103+
});
104+
setFeatureFlagName('');
105+
setfeatureFlagVariant('');
106+
};
63107

64108
const logout = () => {
65109
Instabug.logOut();
@@ -215,6 +259,38 @@ export const SettingsScreen: React.FC = () => {
215259
</Button>
216260
</VStack>
217261
</VerticalListTile>
262+
<VerticalListTile title="Support varient">
263+
<VStack>
264+
<View style={styles.formContainer}>
265+
<View style={styles.inputWrapper}>
266+
<InputField
267+
placeholder="FeatureFlag name"
268+
onChangeText={(key) => setFeatureFlagName(key)}
269+
value={featureFlagName}
270+
errorText={featureFlagFormError.featureFlagName}
271+
/>
272+
</View>
273+
<View style={styles.inputWrapper}>
274+
<InputField
275+
placeholder="Feature Flag varient"
276+
onChangeText={(value) => setfeatureFlagVariant(value)}
277+
value={featureFlagVariant}
278+
/>
279+
</View>
280+
</View>
281+
282+
<Button mt="4" onPress={saveFeatureFlags}>
283+
Save Feature flag
284+
</Button>
285+
286+
<Button mt="4" colorScheme="red" onPress={removeFeatureFlags}>
287+
remove Feature Flag
288+
</Button>
289+
<Button mt="4" colorScheme="red" onPress={removeAllFeatureFlags}>
290+
remove all Feature Flag
291+
</Button>
292+
</VStack>
293+
</VerticalListTile>
218294
</Screen>
219295
</ScrollView>
220296
);

ios/RNInstabug/InstabugReactBridge.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,7 @@
133133
- (void)addExperiments:(NSArray *)experiments;
134134
- (void)removeExperiments:(NSArray *)experiments;
135135
- (void)clearAllExperiments;
136-
136+
- (void)addFeatureFlags:(NSDictionary *)featureFlagsMap;
137+
- (void)removeFeatureFlags:(NSArray *)featureFlags;
138+
- (void)removeAllFeatureFlags;
137139
@end

0 commit comments

Comments
 (0)