Skip to content
Open
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
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,9 @@ const ONYXKEYS = {
/** Indicates whether the debug mode is currently enabled */
IS_DEBUG_MODE_ENABLED: 'isDebugModeEnabled',

/** Local overrides for beta feature flags, set from the Test Tool Menu on dev/staging. Takes precedence over the server-provided betas */
BETAS_OVERRIDE: 'betasOverride',

/** Indicates whether the git branch name should be shown in the browser tab title */
SHOULD_SHOW_BRANCH_NAME_IN_TITLE: 'shouldShowBranchNameInTitle',

Expand Down Expand Up @@ -1731,6 +1734,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.SHOULD_MASK_ONYX_STATE]: boolean;
[ONYXKEYS.SHOULD_USE_STAGING_SERVER]: boolean;
[ONYXKEYS.IS_DEBUG_MODE_ENABLED]: boolean;
[ONYXKEYS.BETAS_OVERRIDE]: OnyxTypes.BetasOverride;
[ONYXKEYS.SHOULD_SHOW_BRANCH_NAME_IN_TITLE]: boolean;
[ONYXKEYS.IS_SENTRY_DEBUG_ENABLED]: boolean;
[ONYXKEYS.IS_SENTRY_SEND_ENABLED]: boolean;
Expand Down
108 changes: 108 additions & 0 deletions src/components/BetaOverridesModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';

import {clearBetaOverrides, setBetaOverride} from '@userActions/User';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

import React from 'react';
import {View} from 'react-native';

import Badge from './Badge';
import Button from './ButtonComposed';
import HeaderWithBackButton from './HeaderWithBackButton';
import Modal from './Modal';
import SafeAreaConsumer from './SafeAreaConsumer';
import ScrollView from './ScrollView';
import Switch from './Switch';
import Text from './Text';

type BetaOverridesModalProps = {
/** Whether the modal is visible */
isVisible: boolean;

/** Closes the modal */
onClose: () => void;
};

// The 'all' beta is excluded because it is a blanket switch rather than an individual feature, and overriding it would behave unpredictably against the per-feature switches.
const betasList = Object.values(CONST.BETAS).filter((beta) => beta !== CONST.BETAS.ALL);

function BetaOverridesModal({isVisible, onClose}: BetaOverridesModalProps) {
const styles = useThemeStyles();
const {translate, localeCompare} = useLocalize();
const sortedBetas = [...betasList].sort(localeCompare);
const {windowHeight} = useWindowDimensions();
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to be consistent with BaseModal component
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();
const {isBetaEnabled} = usePermissions();
const [betasOverride] = useOnyx(ONYXKEYS.BETAS_OVERRIDE);

const maxHeight = isSmallScreenWidth ? undefined : windowHeight - 40;

return (
<SafeAreaConsumer>
{({safeAreaPaddingBottomStyle}) => (
<Modal
isVisible={isVisible}
type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.CENTERED_SWIPEABLE_TO_RIGHT : CONST.MODAL.MODAL_TYPE.CENTERED_SMALL}
onClose={onClose}
innerContainerStyle={isSmallScreenWidth ? {...safeAreaPaddingBottomStyle, maxHeight} : {...styles.workspaceSection, ...safeAreaPaddingBottomStyle, maxHeight}}
>
<HeaderWithBackButton
title={translate('initialSettingsPage.troubleshoot.betaOverrides')}
shouldShowCloseButton={!isSmallScreenWidth}
onCloseButtonPress={onClose}
shouldShowBackButton={isSmallScreenWidth}
onBackButtonPress={onClose}
shouldDisplayHelpButton={false}
/>
<ScrollView contentContainerStyle={[styles.ph5, styles.pb5]}>
<Text style={[styles.textLabelSupporting, styles.mb4]}>{translate('initialSettingsPage.troubleshoot.betaOverridesDescription')}</Text>
{sortedBetas.map((beta) => (
<View
key={beta}
style={styles.testRowContainer}
>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2, styles.flexGrow1, styles.flexShrink1]}>
<Text>{beta}</Text>
{betasOverride?.[beta] !== undefined && (
<Badge
text={translate('initialSettingsPage.troubleshoot.overridden')}
isCondensed
textStyles={styles.condensedBadgeTextLarge}
success
/>
)}
</View>
<View style={[styles.flexGrow0, styles.flexShrink0, styles.alignItemsEnd]}>
<Switch
accessibilityLabel={beta}
isOn={isBetaEnabled(beta)}
onToggle={() => setBetaOverride(beta, !isBetaEnabled(beta))}
/>
</View>
</View>
))}
<View style={styles.mt4}>
<Button
size={CONST.BUTTON_SIZE.LARGE}
onPress={clearBetaOverrides}
>
<Button.Text>{translate('initialSettingsPage.troubleshoot.resetAllOverrides')}</Button.Text>
</Button>
</View>
</ScrollView>
</Modal>
)}
</SafeAreaConsumer>
);
}

export default BetaOverridesModal;
3 changes: 3 additions & 0 deletions src/components/OnyxListItemProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const [PersonalDetailsProvider, , usePersonalDetails] = createOnyxContext(ONYXKE
const [BlockedFromConciergeProvider, , useBlockedFromConcierge] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE);
const [BetasProvider, BetasContext] = createOnyxContext(ONYXKEYS.BETAS);
const [BetaConfigurationProvider, BetaConfigurationContext] = createOnyxContext(ONYXKEYS.BETA_CONFIGURATION);
const [BetasOverrideProvider, BetasOverrideContext] = createOnyxContext(ONYXKEYS.BETAS_OVERRIDE);
const [SessionProvider, , useSession] = createOnyxContext(ONYXKEYS.SESSION);
const [PolicyCategoriesProvider, , usePolicyCategories] = createOnyxContext(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
const [PolicyTagsProvider, , usePolicyTags] = createOnyxContext(ONYXKEYS.COLLECTION.POLICY_TAGS);
Expand All @@ -36,6 +37,7 @@ function OnyxListItemProvider(props: OnyxListItemProviderProps) {
BlockedFromConciergeProvider,
BetasProvider,
BetaConfigurationProvider,
BetasOverrideProvider,
SessionProvider,
PolicyCategoriesProvider,
PolicyTagsProvider,
Expand All @@ -57,6 +59,7 @@ export {
usePersonalDetails,
BetasContext,
BetaConfigurationContext,
BetasOverrideContext,
useBlockedFromConcierge,
useSession,
usePolicyCategories,
Expand Down
24 changes: 23 additions & 1 deletion src/components/TestToolMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import useEnvironment from '@hooks/useEnvironment';
import useIsAgentAccount from '@hooks/useIsAgentAccount';
import useIsAuthenticated from '@hooks/useIsAuthenticated';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -15,9 +16,10 @@ import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

import React from 'react';
import React, {useState} from 'react';
import {Platform} from 'react-native';

import BetaOverridesModal from './BetaOverridesModal';
import BiometricsTestToolRow from './BiometricsTestToolRow';
import Button from './ButtonComposed';
import SoftKillTestToolRow from './SoftKillTestToolRow';
Expand All @@ -35,6 +37,8 @@ function TestToolMenu() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {clearLHNCache} = useSidebarOrderedReportsActions();
const {isProduction} = useEnvironment();
const [isBetaOverridesModalVisible, setIsBetaOverridesModalVisible] = useState(false);

// Check if the user is authenticated to show options that require authentication
const isAuthenticated = useIsAuthenticated();
Expand Down Expand Up @@ -115,6 +119,24 @@ function TestToolMenu() {
</Button>
</TestToolRow>

{/* Allows locally overriding beta feature flags for testing. Not rendered in production because betas are meant to be controlled by the backend there. */}
{!isProduction && (
<>
<TestToolRow title={translate('initialSettingsPage.troubleshoot.betaOverrides')}>
<Button
size={CONST.BUTTON_SIZE.SMALL}
onPress={() => setIsBetaOverridesModalVisible(true)}
>
<Button.Text>{translate('common.view')}</Button.Text>
</Button>
</TestToolRow>
<BetaOverridesModal
isVisible={isBetaOverridesModalVisible}
onClose={() => setIsBetaOverridesModalVisible(false)}
/>
</>
)}

{/* Allows testing and revoking biometric multifactor authentication */}
{isAgentAccount === false && <BiometricsTestToolRow />}
</>
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/usePermissions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {BetaConfigurationContext, BetasContext} from '@components/OnyxListItemProvider';
import {BetaConfigurationContext, BetasContext, BetasOverrideContext} from '@components/OnyxListItemProvider';

import Permissions from '@libs/Permissions';

Expand All @@ -13,9 +13,10 @@ let permissionKey: PermissionKey;
export default function usePermissions(): UsePermissions {
const betas = useContext(BetasContext);
const betaConfiguration = useContext(BetaConfigurationContext);
const betasOverride = useContext(BetasOverrideContext);
return useMemo(() => {
const permissions: UsePermissions = {
isBetaEnabled: (beta: Beta) => Permissions.isBetaEnabled(beta, betas, betaConfiguration),
isBetaEnabled: (beta: Beta) => Permissions.isBetaEnabled(beta, betas, betaConfiguration, betasOverride),
};

for (permissionKey in Permissions) {
Expand All @@ -26,5 +27,5 @@ export default function usePermissions(): UsePermissions {
}

return permissions;
}, [betas, betaConfiguration]);
}, [betas, betaConfiguration, betasOverride]);
}
5 changes: 5 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2277,6 +2277,11 @@ const translations: TranslationDeepObject<typeof en> = {
sentryHighlightedSpanOps: 'Hervorgehobene Spannen-Namen',
sentryHighlightedSpanOpsPlaceholder: 'ui.interaktion.klick, navigation, ui.laden',
showBranchNameInTitle: 'Branch-Namen im Browser-Titel anzeigen',
betaOverrides: 'Beta-Überschreibungen',
betaOverridesDescription:
'Überschreibungen gelten nur für dieses Gerät und wirken sich nur auf Frontend-Prüfungen aus. Durch das Umschalten einer Beta wird sie auf diesen Wert festgelegt und bleibt es, bis Sie sie zurücksetzen. „Alle Überschreibungen zurücksetzen“ löscht alle und setzt jede Beta auf den Wert zurück, den Ihr Konto im Backend hat. Einige Funktionen werden zusätzlich vom Backend gesteuert, sodass das erzwungene Aktivieren einer Beta, die Ihr Konto nicht hat, auf Anfrageebene trotzdem fehlschlagen kann.',
resetAllOverrides: 'Alle Überschreibungen zurücksetzen',
overridden: 'Überschrieben',
},
security: 'Sicherheit',
signOut: 'Abmelden',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2319,6 +2319,11 @@ const translations: TranslationDeepObject<typeof en> = {
usingImportedState: 'Χρησιμοποιείτε εισαγόμενη κατάσταση. Πατήστε εδώ για να την καθαρίσετε.',
debugMode: 'Λειτουργία αποσφαλμάτωσης',
showBranchNameInTitle: 'Εμφάνιση ονόματος κλάδου στον τίτλο του προγράμματος περιήγησης',
betaOverrides: 'Παρακάμψεις beta',
betaOverridesDescription:
'Οι παρακάμψεις ισχύουν μόνο για αυτή τη συσκευή και επηρεάζουν μόνο τους ελέγχους του frontend. Η εναλλαγή μιας beta την καρφιτσώνει σε αυτή την τιμή και παραμένει έτσι μέχρι να την επαναφέρετε. Η «Επαναφορά όλων των παρακάμψεων» τις διαγράφει όλες και επαναφέρει κάθε beta στην τιμή που έχει ο λογαριασμός σας στο backend. Ορισμένες λειτουργίες ελέγχονται επίσης από το backend, οπότε η αναγκαστική ενεργοποίηση μιας beta που δεν έχει ο λογαριασμός σας μπορεί να αποτύχει σε επίπεδο αιτήματος.',
resetAllOverrides: 'Επαναφορά όλων των παρακάμψεων',
overridden: 'Παρακάμφθηκε',
invalidFile: 'Μη έγκυρο αρχείο',
invalidFileDescription: 'Το αρχείο που προσπαθείτε να εισαγάγετε δεν είναι έγκυρο. Παρακαλούμε δοκιμάστε ξανά.',
invalidateWithDelay: 'Ακύρωση με καθυστέρηση',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2353,6 +2353,11 @@ const translations = {
usingImportedState: 'You are using imported state. Press here to clear it.',
debugMode: 'Debug mode',
showBranchNameInTitle: 'Show branch name in browser title',
betaOverrides: 'Beta overrides',
betaOverridesDescription:
'Overrides only apply to this device and only affect frontend checks. Toggling a beta pins it to that value, and it stays pinned until you reset it. “Reset all overrides” clears them all and returns every beta to the value your account has on the backend. Some features are also gated by the backend, so force-enabling a beta your account doesn’t have may still fail at the request level.',
resetAllOverrides: 'Reset all overrides',
overridden: 'Overridden',
invalidFile: 'Invalid file',
invalidFileDescription: 'The file you are trying to import is not valid. Please try again.',
invalidateWithDelay: 'Invalidate with delay',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2195,6 +2195,11 @@ const translations: TranslationDeepObject<typeof en> = {
sentryHighlightedSpanOps: 'Nombres de spans resaltados',
sentryHighlightedSpanOpsPlaceholder: 'ui.interaction.click, navigation, ui.load',
showBranchNameInTitle: 'Mostrar nombre de rama en el título del navegador',
betaOverrides: 'Anulaciones de betas',
betaOverridesDescription:
'Las anulaciones solo se aplican a este dispositivo y solo afectan a las comprobaciones del frontend. Al activar o desactivar una beta, esta queda fijada en ese valor y permanece así hasta que la restablezcas. «Restablecer todas las anulaciones» las borra todas y devuelve cada beta al valor que tiene tu cuenta en el backend. Algunas funciones también están controladas por el backend, así que forzar la activación de una beta que tu cuenta no tiene puede fallar igualmente a nivel de solicitud.',
resetAllOverrides: 'Restablecer todas las anulaciones',
overridden: 'Anulada',
},
security: 'Seguridad',
restoreStashed: 'Restablecer login guardado',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2283,6 +2283,11 @@ const translations: TranslationDeepObject<typeof en> = {
sentryHighlightedSpanOps: 'Noms de segments surlignés',
sentryHighlightedSpanOpsPlaceholder: 'ui.interaction.clic, navigation, ui.chargement',
showBranchNameInTitle: 'Afficher le nom de la branche dans le titre du navigateur',
betaOverrides: 'Remplacements de bêtas',
betaOverridesDescription:
"Les remplacements ne s'appliquent qu'à cet appareil et n'affectent que les vérifications côté frontend. Activer ou désactiver une bêta la fige sur cette valeur, et elle le reste jusqu'à ce que vous la réinitialisiez. « Réinitialiser tous les remplacements » les efface tous et rétablit pour chaque bêta la valeur associée à votre compte côté backend. Certaines fonctionnalités sont aussi contrôlées par le backend, donc forcer l'activation d'une bêta que votre compte ne possède pas peut échouer au niveau de la requête.",
resetAllOverrides: 'Réinitialiser tous les remplacements',
overridden: 'Remplacée',
},
security: 'Sécurité',
signOut: 'Se déconnecter',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,11 @@ const translations: TranslationDeepObject<typeof en> = {
sentryHighlightedSpanOps: 'Nomi degli intervalli evidenziati',
sentryHighlightedSpanOpsPlaceholder: 'ui.interaction.click, navigazione, ui.load',
showBranchNameInTitle: 'Mostra nome del ramo nel titolo del browser',
betaOverrides: 'Sostituzioni delle beta',
betaOverridesDescription:
"Le sostituzioni si applicano solo a questo dispositivo e influiscono solo sui controlli del frontend. Attivando o disattivando una beta la si fissa su quel valore, che resta tale finché non la reimposti. «Reimposta tutte le sostituzioni» le cancella tutte e riporta ogni beta al valore che il tuo account ha nel backend. Alcune funzionalità sono controllate anche dal backend, quindi forzare l'attivazione di una beta che il tuo account non possiede può comunque fallire a livello di richiesta.",
resetAllOverrides: 'Reimposta tutte le sostituzioni',
overridden: 'Sostituita',
},
security: 'Sicurezza',
signOut: 'Esci',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2251,6 +2251,11 @@ const translations: TranslationDeepObject<typeof en> = {
sentryHighlightedSpanOps: '強調表示されたスパン名',
sentryHighlightedSpanOpsPlaceholder: 'ui.interaction.click、ナビゲーション、ui.load',
showBranchNameInTitle: 'ブラウザのタイトルにブランチ名を表示',
betaOverrides: 'ベータのオーバーライド',
betaOverridesDescription:
'オーバーライドはこの端末にのみ適用され、フロントエンドのチェックにのみ影響します。ベータを切り替えるとその値に固定され、リセットするまで固定されたままです。「すべてのオーバーライドをリセット」はすべてを消去し、各ベータをアカウントがバックエンドで持つ値に戻します。一部の機能はバックエンドでも制御されているため、アカウントが持っていないベータを強制的に有効にしても、リクエストレベルで失敗する場合があります。',
resetAllOverrides: 'すべてのオーバーライドをリセット',
overridden: 'オーバーライド済み',
},
security: 'セキュリティ',
signOut: 'サインアウト',
Expand Down
Loading
Loading