From 8ad58bb364bda79f1322281153f3bc571cc62843 Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Thu, 9 May 2024 09:42:38 -0300 Subject: [PATCH] Persist in-app feature flags (dev-only feature) (#19619) This commit improves in-app feature flags to persist what is currently only stored in a Reagent atom by using RN Async Storage https://reactnative.dev/docs/asyncstorage. This should make them more convenient to use, which is a good thing overall for developers. Additionally, there's now a top-right button in screen Settings > Feature Flags that will reset the flags to the initial values obtained from environment variables. These in-app feature flags are exclusively available in debug builds in Settings > Feature Flags, and only visible when flag ENABLE_QUO_PREVIEW is enabled. There's no impact whatsoever in prod builds. A reminder that they are not meant to be used by users (yet). It's worth noting that RN has deprecated Async Storage and now recommends other community solutions, but for a dev-only feature, I think it's fine. --- .../contexts/preview/feature_flags/view.cljs | 5 +- src/status_im/core.cljs | 5 +- src/status_im/feature_flags.cljs | 52 ++++++++++++++----- test/jest/jestSetup.js | 2 +- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/status_im/contexts/preview/feature_flags/view.cljs b/src/status_im/contexts/preview/feature_flags/view.cljs index 5de696d500..a660e0396b 100644 --- a/src/status_im/contexts/preview/feature_flags/view.cljs +++ b/src/status_im/contexts/preview/feature_flags/view.cljs @@ -15,7 +15,9 @@ :text-align :left :title "Features Flags" :icon-name :i/arrow-left - :on-press #(rf/dispatch [:navigate-back])}] + :on-press #(rf/dispatch [:navigate-back]) + :right-side [{:icon-name :i/rotate + :on-press #(ff/reset-flags)}]}] (doall (for [context-name ff/feature-flags-categories :let [context-flags (filter (fn [[k]] @@ -36,4 +38,3 @@ :container-style {:margin-right 8} :on-change #(ff/toggle flag)}] [quo/text (second (string/split (name flag) "."))]]))]))]) - diff --git a/src/status_im/core.cljs b/src/status_im/core.cljs index cd1e8fb970..94bdbbb362 100644 --- a/src/status_im/core.cljs +++ b/src/status_im/core.cljs @@ -21,10 +21,10 @@ [status-im.contexts.profile.push-notifications.events :as notifications] [status-im.contexts.shell.jump-to.state :as shell.state] [status-im.contexts.shell.jump-to.utils :as shell.utils] + [status-im.feature-flags :as ff] [status-im.navigation.core :as navigation] status-im.contexts.wallet.signals status-im.events - status-im.navigation.core [status-im.setup.dev :as dev] [status-im.setup.global-error :as global-error] [status-im.setup.interceptors :as interceptors] @@ -63,6 +63,9 @@ (async-storage/get-item :selected-stack-id #(shell.utils/change-selected-stack-id % nil nil)) (async-storage/get-item :screen-height #(reset! shell.state/screen-height %)) + (when config/quo-preview-enabled? + (ff/load-flags)) + (dev/setup) (log/info "hermesEnabled ->" (is-hermes)) diff --git a/src/status_im/feature_flags.cljs b/src/status_im/feature_flags.cljs index 0820104257..4756771f6e 100644 --- a/src/status_im/feature_flags.cljs +++ b/src/status_im/feature_flags.cljs @@ -1,6 +1,7 @@ (ns status-im.feature-flags (:require [clojure.string :as string] + [react-native.async-storage :as async-storage] [react-native.config :as config] [reagent.core :as reagent])) @@ -8,20 +9,22 @@ [k] (= "1" (config/get-config k))) +(def ^:private initial-flags + {::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED) + ::wallet.activities (enabled-in-env? :FLAG_WALLET_ACTIVITY_ENABLED) + ::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE) + ::wallet.assets-modal-manage-tokens (enabled-in-env? :FLAG_ASSETS_MODAL_MANAGE_TOKENS) + ::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED) + ::wallet.contacts (enabled-in-env? :FLAG_CONTACTS_ENABLED) + ::wallet.edit-derivation-path (enabled-in-env? :FLAG_EDIT_DERIVATION_PATH) + ::wallet.graph (enabled-in-env? :FLAG_GRAPH_ENABLED) + ::wallet.import-private-key (enabled-in-env? :FLAG_IMPORT_PRIVATE_KEY_ENABLED) + ::wallet.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED) + ::wallet.swap (enabled-in-env? :FLAG_SWAP_ENABLED) + ::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED)}) + (defonce ^:private feature-flags-config - (reagent/atom - {::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED) - ::wallet.activities (enabled-in-env? :FLAG_WALLET_ACTIVITY_ENABLED) - ::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE) - ::wallet.assets-modal-manage-tokens (enabled-in-env? :FLAG_ASSETS_MODAL_MANAGE_TOKENS) - ::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED) - ::wallet.contacts (enabled-in-env? :FLAG_CONTACTS_ENABLED) - ::wallet.edit-derivation-path (enabled-in-env? :FLAG_EDIT_DERIVATION_PATH) - ::wallet.graph (enabled-in-env? :FLAG_GRAPH_ENABLED) - ::wallet.import-private-key (enabled-in-env? :FLAG_IMPORT_PRIVATE_KEY_ENABLED) - ::wallet.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED) - ::wallet.swap (enabled-in-env? :FLAG_SWAP_ENABLED) - ::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED)})) + (reagent/atom initial-flags)) (defn feature-flags [] @feature-flags-config) @@ -37,7 +40,28 @@ (defn toggle [flag] - (swap! feature-flags-config update flag not)) + (let [new-flags (update @feature-flags-config flag not)] + (async-storage/set-item! + :feature-flags + new-flags + (fn [] + (reset! feature-flags-config new-flags))))) + +(defn load-flags + [] + (async-storage/get-item + :feature-flags + (fn [flags] + (when flags + (reset! feature-flags-config flags))))) + +(defn reset-flags + [] + (async-storage/set-item! + :feature-flags + initial-flags + (fn [] + (reset! feature-flags-config initial-flags)))) (defn alert [flag action] diff --git a/test/jest/jestSetup.js b/test/jest/jestSetup.js index fb77ccc0a7..3b1aafbc25 100644 --- a/test/jest/jestSetup.js +++ b/test/jest/jestSetup.js @@ -1,7 +1,7 @@ const WebSocket = require('ws'); const { NativeModules } = require('react-native'); -require('@react-native-async-storage/async-storage/jest/async-storage-mock'); +mockAsyncStorage = require('@react-native-async-storage/async-storage/jest/async-storage-mock'); require('react-native-gesture-handler/jestSetup'); require('react-native-reanimated/src/reanimated2/jestUtils').setUpTests();