Skip to content

Commit 8f12172

Browse files
Feat:feature bypass (#777)
* feature bypass * error handling
1 parent df7308b commit 8f12172

File tree

3 files changed

+111
-4
lines changed

3 files changed

+111
-4
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { type BypassConfig } from '../interface/RemoteConfig';
2+
import { userHasBypass } from './RemoteConfigProvider';
3+
jest.mock('firebase/compat/app', () => ({
4+
initializeApp: jest.fn(),
5+
remoteConfig: jest.fn(() => ({
6+
settings: { minimumFetchIntervalMillis: 3600000 },
7+
})),
8+
}));
9+
describe('userHasBypass', () => {
10+
beforeEach(() => {
11+
jest.clearAllMocks();
12+
});
13+
14+
it('should return false if userEmail is null', () => {
15+
const byPassConfig: BypassConfig = { regex: ['.*@example.com'] };
16+
expect(userHasBypass(byPassConfig, null)).toBe(false);
17+
});
18+
19+
it('should return false if userEmail is undefined', () => {
20+
const byPassConfig: BypassConfig = { regex: ['.*@example.com'] };
21+
expect(userHasBypass(byPassConfig, undefined)).toBe(false);
22+
});
23+
24+
it('should return true if userEmail matches one of the regex patterns', () => {
25+
const byPassConfig: BypassConfig = { regex: ['.*@example.com'] };
26+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(true);
27+
});
28+
29+
it('should return true if userEmail matches one of the more complex regex patterns', () => {
30+
const byPassConfig: BypassConfig = { regex: ['[^@][email protected]'] };
31+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(true);
32+
});
33+
34+
it('should return false if userEmail does not match any regex patterns', () => {
35+
const byPassConfig: BypassConfig = { regex: ['.*@example.com'] };
36+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(false);
37+
});
38+
39+
it('should return true if userEmail matches multiple regex patterns', () => {
40+
const byPassConfig: BypassConfig = {
41+
regex: ['.*@example.com', '.*@another.com'],
42+
};
43+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(true);
44+
});
45+
46+
it('should return false if byPassConfig has an empty regex array', () => {
47+
const byPassConfig: BypassConfig = { regex: [] };
48+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(false);
49+
});
50+
51+
it('should return true if user is in list of regex', () => {
52+
const byPassConfig: BypassConfig = {
53+
54+
};
55+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(true);
56+
});
57+
58+
it('should return false if user is not in list', () => {
59+
const byPassConfig: BypassConfig = {
60+
61+
};
62+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(false);
63+
});
64+
65+
it('should not break the function if there is an invalid regex', () => {
66+
const byPassConfig: BypassConfig = {
67+
68+
};
69+
expect(userHasBypass(byPassConfig, '[email protected]')).toBe(true);
70+
});
71+
});

web-app/src/app/context/RemoteConfigProvider.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import React, {
55
useEffect,
66
useState,
77
} from 'react';
8-
import { remoteConfig } from '../../firebase';
8+
import { remoteConfig, app } from '../../firebase';
99
import {
10+
type BypassConfig,
1011
defaultRemoteConfigValues,
1112
type RemoteConfigValues,
1213
} from '../interface/RemoteConfig';
@@ -23,6 +24,25 @@ interface RemoteConfigProviderProps {
2324
children: ReactNode;
2425
}
2526

27+
export function userHasBypass(
28+
byPassConfig: BypassConfig,
29+
userEmail: string | null | undefined,
30+
): boolean {
31+
let hasBypass = false;
32+
if (userEmail === null || userEmail === undefined) {
33+
return false;
34+
}
35+
hasBypass = byPassConfig.regex.some((regex) => {
36+
try {
37+
if (userEmail.match(new RegExp(regex, 'i')) !== null) {
38+
return true;
39+
}
40+
} catch (e) {}
41+
return false;
42+
});
43+
return hasBypass;
44+
}
45+
2646
export const RemoteConfigProvider = ({
2747
children,
2848
}: RemoteConfigProviderProps): React.ReactElement => {
@@ -37,12 +57,19 @@ export const RemoteConfigProvider = ({
3757
await remoteConfig.fetchAndActivate();
3858
const fetchedConfigValues = defaultRemoteConfigValues;
3959

60+
const bypassConfig: BypassConfig = JSON.parse(
61+
remoteConfig.getValue('featureFlagBypass').asString(),
62+
);
63+
const hasBypass = userHasBypass(
64+
bypassConfig,
65+
app.auth().currentUser?.email,
66+
);
4067
Object.keys(defaultRemoteConfigValues).forEach((key) => {
4168
const rawValue = remoteConfig.getValue(key);
4269
const rawValueLower = rawValue.asString().toLowerCase();
4370
if (rawValueLower === 'true' || rawValueLower === 'false') {
4471
// Boolean
45-
fetchedConfigValues[key] = rawValue.asBoolean();
72+
fetchedConfigValues[key] = hasBypass || rawValue.asBoolean();
4673
} else if (!isNaN(Number(rawValue)) && rawValueLower.trim() !== '') {
4774
// Number
4875
fetchedConfigValues[key] = rawValue.asNumber();
@@ -51,7 +78,6 @@ export const RemoteConfigProvider = ({
5178
fetchedConfigValues[key] = rawValue.asString();
5279
}
5380
});
54-
5581
setConfig((prevConfig) => ({
5682
...prevConfig,
5783
...fetchedConfigValues,
@@ -64,7 +90,7 @@ export const RemoteConfigProvider = ({
6490
};
6591

6692
void fetchAndActivateConfig();
67-
}, []);
93+
}, [app.auth().currentUser?.email]);
6894

6995
return (
7096
<RemoteConfigContext.Provider value={{ config, loading }}>

web-app/src/app/interface/RemoteConfig.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { remoteConfig } from '../../firebase';
22

33
type FirebaseDefaultConfig = typeof remoteConfig.defaultConfig;
44

5+
export interface BypassConfig {
6+
regex: string[];
7+
}
8+
59
export interface RemoteConfigValues extends FirebaseDefaultConfig {
610
enableAppleSSO: boolean;
711
enableFeedsPage: boolean;
@@ -23,8 +27,13 @@ export interface RemoteConfigValues extends FirebaseDefaultConfig {
2327
gtfsMetricsBucketEndpoint: string;
2428
/** GBFS metrics' bucket endpoint */
2529
gbfsMetricsBucketEndpoint: string;
30+
featureFlagBypass: string;
2631
}
2732

33+
const featureByPassDefault: BypassConfig = {
34+
regex: [],
35+
};
36+
2837
// Add default values for remote config here
2938
export const defaultRemoteConfigValues: RemoteConfigValues = {
3039
enableAppleSSO: false,
@@ -36,6 +45,7 @@ export const defaultRemoteConfigValues: RemoteConfigValues = {
3645
'https://storage.googleapis.com/mobilitydata-gtfs-analytics-dev',
3746
gbfsMetricsBucketEndpoint:
3847
'https://storage.googleapis.com/mobilitydata-gbfs-analytics-dev',
48+
featureFlagBypass: JSON.stringify(featureByPassDefault),
3949
};
4050

4151
remoteConfig.defaultConfig = defaultRemoteConfigValues;

0 commit comments

Comments
 (0)