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.
This commit is contained in:
Icaro Motta 2024-05-09 09:42:38 -03:00 committed by GitHub
parent ab191407ed
commit 8ad58bb364
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 18 deletions

View File

@ -15,7 +15,9 @@
:text-align :left :text-align :left
:title "Features Flags" :title "Features Flags"
:icon-name :i/arrow-left :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 (doall
(for [context-name ff/feature-flags-categories (for [context-name ff/feature-flags-categories
:let [context-flags (filter (fn [[k]] :let [context-flags (filter (fn [[k]]
@ -36,4 +38,3 @@
:container-style {:margin-right 8} :container-style {:margin-right 8}
:on-change #(ff/toggle flag)}] :on-change #(ff/toggle flag)}]
[quo/text (second (string/split (name flag) "."))]]))]))]) [quo/text (second (string/split (name flag) "."))]]))]))])

View File

@ -21,10 +21,10 @@
[status-im.contexts.profile.push-notifications.events :as notifications] [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.state :as shell.state]
[status-im.contexts.shell.jump-to.utils :as shell.utils] [status-im.contexts.shell.jump-to.utils :as shell.utils]
[status-im.feature-flags :as ff]
[status-im.navigation.core :as navigation] [status-im.navigation.core :as navigation]
status-im.contexts.wallet.signals status-im.contexts.wallet.signals
status-im.events status-im.events
status-im.navigation.core
[status-im.setup.dev :as dev] [status-im.setup.dev :as dev]
[status-im.setup.global-error :as global-error] [status-im.setup.global-error :as global-error]
[status-im.setup.interceptors :as interceptors] [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 :selected-stack-id #(shell.utils/change-selected-stack-id % nil nil))
(async-storage/get-item :screen-height #(reset! shell.state/screen-height %)) (async-storage/get-item :screen-height #(reset! shell.state/screen-height %))
(when config/quo-preview-enabled?
(ff/load-flags))
(dev/setup) (dev/setup)
(log/info "hermesEnabled ->" (is-hermes)) (log/info "hermesEnabled ->" (is-hermes))

View File

@ -1,6 +1,7 @@
(ns status-im.feature-flags (ns status-im.feature-flags
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[react-native.async-storage :as async-storage]
[react-native.config :as config] [react-native.config :as config]
[reagent.core :as reagent])) [reagent.core :as reagent]))
@ -8,8 +9,7 @@
[k] [k]
(= "1" (config/get-config k))) (= "1" (config/get-config k)))
(defonce ^:private feature-flags-config (def ^:private initial-flags
(reagent/atom
{::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED) {::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED)
::wallet.activities (enabled-in-env? :FLAG_WALLET_ACTIVITY_ENABLED) ::wallet.activities (enabled-in-env? :FLAG_WALLET_ACTIVITY_ENABLED)
::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE) ::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE)
@ -21,7 +21,10 @@
::wallet.import-private-key (enabled-in-env? :FLAG_IMPORT_PRIVATE_KEY_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.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED)
::wallet.swap (enabled-in-env? :FLAG_SWAP_ENABLED) ::wallet.swap (enabled-in-env? :FLAG_SWAP_ENABLED)
::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED)})) ::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED)})
(defonce ^:private feature-flags-config
(reagent/atom initial-flags))
(defn feature-flags [] @feature-flags-config) (defn feature-flags [] @feature-flags-config)
@ -37,7 +40,28 @@
(defn toggle (defn toggle
[flag] [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 (defn alert
[flag action] [flag action]

View File

@ -1,7 +1,7 @@
const WebSocket = require('ws'); const WebSocket = require('ws');
const { NativeModules } = require('react-native'); 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-gesture-handler/jestSetup');
require('react-native-reanimated/src/reanimated2/jestUtils').setUpTests(); require('react-native-reanimated/src/reanimated2/jestUtils').setUpTests();