[#6644] Chat command recipients should be able to install an extension

Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2018-11-28 17:08:57 +01:00 committed by Julien Eluard
parent 37ef82b04d
commit 582c2960ec
No known key found for this signature in database
GPG Key ID: 6FD7DB5437FCBEF6
16 changed files with 124 additions and 61 deletions

View File

@ -11,7 +11,7 @@
com.taoensso/timbre {:mvn/version "4.10.0"} com.taoensso/timbre {:mvn/version "4.10.0"}
hickory {:mvn/version "0.7.1"} hickory {:mvn/version "0.7.1"}
com.cognitect/transit-cljs {:mvn/version "0.8.248"} com.cognitect/transit-cljs {:mvn/version "0.8.248"}
status-im/pluto {:mvn/version "iteration-4-4"} status-im/pluto {:mvn/version "iteration-4-5"}
mvxcvi/alphabase {:mvn/version "1.0.0"} mvxcvi/alphabase {:mvn/version "1.0.0"}
rasom/cljs-react-navigation {:mvn/version "0.1.4"}} rasom/cljs-react-navigation {:mvn/version "0.1.4"}}

View File

@ -11,7 +11,7 @@
[com.taoensso/timbre "4.10.0"] [com.taoensso/timbre "4.10.0"]
[hickory "0.7.1"] [hickory "0.7.1"]
[com.cognitect/transit-cljs "0.8.248"] [com.cognitect/transit-cljs "0.8.248"]
[status-im/pluto "iteration-4-4"] [status-im/pluto "iteration-4-5"]
[mvxcvi/alphabase "1.0.0"] [mvxcvi/alphabase "1.0.0"]
[rasom/cljs-react-navigation "0.1.4"]] [rasom/cljs-react-navigation "0.1.4"]]
:plugins [[lein-cljsbuild "1.1.7"] :plugins [[lein-cljsbuild "1.1.7"]

View File

@ -127,7 +127,8 @@
:suggestions? :view}]} :suggestions? :view}]}
:hook :hook
(reify hooks/Hook (reify hooks/Hook
(hook-in [_ id {:keys [description scope parameters preview short-preview on-send on-receive on-send-sync]} cofx] (hook-in [_ id {extension-id :id} {:keys [description scope parameters preview short-preview
on-send on-receive on-send-sync]} cofx]
(let [new-command (if on-send-sync (let [new-command (if on-send-sync
(reify protocol/Command (reify protocol/Command
(id [_] (name id)) (id [_] (name id))
@ -140,7 +141,9 @@
(short-preview [_ props] (short-preview props)) (short-preview [_ props] (short-preview props))
(preview [_ props] (preview props)) (preview [_ props] (preview props))
protocol/Yielding protocol/Yielding
(yield-control [_ props _] {:dispatch (on-send-sync props)})) (yield-control [_ props _] {:dispatch (on-send-sync props)})
protocol/Extension
(extension-id [_] extension-id))
(reify protocol/Command (reify protocol/Command
(id [_] (name id)) (id [_] (name id))
(scope [_] scope) (scope [_] scope)
@ -150,9 +153,11 @@
(on-send [_ command-message _] (when on-send {:dispatch (on-send command-message)})) (on-send [_ command-message _] (when on-send {:dispatch (on-send command-message)}))
(on-receive [_ command-message _] (when on-receive {:dispatch (on-receive command-message)})) (on-receive [_ command-message _] (when on-receive {:dispatch (on-receive command-message)}))
(short-preview [_ props] (short-preview props)) (short-preview [_ props] (short-preview props))
(preview [_ props] (preview props))))] (preview [_ props] (preview props))
protocol/Extension
(extension-id [_] extension-id)))]
(load-commands cofx [new-command]))) (load-commands cofx [new-command])))
(unhook [_ id {:keys [scope]} {:keys [db] :as cofx}] (unhook [_ id _ {:keys [scope]} {:keys [db] :as cofx}]
(remove-command (get-in db [:id->command [(name id) scope] :type]) cofx)))}) (remove-command (get-in db [:id->command [(name id) scope] :type]) cofx)))})
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -68,3 +68,7 @@
"Function which takes original parameters + cofx map and returns new map of parameters") "Function which takes original parameters + cofx map and returns new map of parameters")
(enhance-receive-parameters [this parameters cofx] (enhance-receive-parameters [this parameters cofx]
"Function which takes original parameters + cofx map and returns new map of parameters")) "Function which takes original parameters + cofx map and returns new map of parameters"))
(defprotocol Extension
"Protocol for defining extension"
(extension-id [this]))

View File

@ -12,12 +12,15 @@
[chat-id type parameter-map cofx] [chat-id type parameter-map cofx]
(let [command-path (commands/command-id type) (let [command-path (commands/command-id type)
new-parameter-map (and (satisfies? protocol/EnhancedParameters type) new-parameter-map (and (satisfies? protocol/EnhancedParameters type)
(protocol/enhance-send-parameters type parameter-map cofx))] (protocol/enhance-send-parameters type parameter-map cofx))
params (merge (or new-parameter-map parameter-map)
(when (satisfies? protocol/Extension type)
{:extension-id (protocol/extension-id type)}))]
{:chat-id chat-id {:chat-id chat-id
:content-type constants/content-type-command :content-type constants/content-type-command
:content {:chat-id chat-id :content {:chat-id chat-id
:command-path command-path :command-path command-path
:params (or new-parameter-map parameter-map)}})) :params params}}))
(fx/defn validate-and-send (fx/defn validate-and-send
"Validates and sends command in current chat" "Validates and sends command in current chat"

View File

@ -512,13 +512,19 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions.ui/show-button-pressed :extensions.ui/show-button-pressed
(fn [cofx [_ url]] (fn [cofx [_ url]]
(extensions.registry/load cofx url))) (extensions.registry/load cofx url false)))
(handlers/register-handler-fx
:extensions.ui/install-extension-button-pressed
(fn [{:keys [db] :as cofx} [_ url]]
(fx/merge cofx
{:db (assoc-in db [:extensions/manage :url :value] url)}
(extensions.registry/load url true))))
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions.ui/install-button-pressed :extensions.ui/install-button-pressed
[(re-frame/inject-cofx :random-id-generator)] (fn [cofx [_ data modal?]]
(fn [cofx [_ data]] (extensions.registry/install cofx data modal?)))
(extensions.registry/install cofx data)))
;; log-level module ;; log-level module

View File

@ -6,35 +6,36 @@
[status-im.accounts.update.core :as accounts.update] [status-im.accounts.update.core :as accounts.update]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[clojure.set :as set])) [clojure.set :as set]
[status-im.ui.screens.navigation :as navigation]))
(fx/defn update-hooks (fx/defn update-hooks
[{:keys [db] :as cofx} hook-fn extension-key] [{:keys [db] :as cofx} hook-fn extension-id]
(let [account (get db :account/account) (let [account (get db :account/account)
hooks (get-in account [:extensions extension-key :hooks])] hooks (get-in account [:extensions extension-id :hooks])]
(apply fx/merge cofx (apply fx/merge cofx
(mapcat (fn [[_ extension-hooks]] (mapcat (fn [[_ extension-hooks]]
(map (fn [[hook-id {:keys [hook-ref parsed]}]] (map (fn [[hook-id {:keys [hook-ref parsed]}]]
(partial hook-fn (:hook hook-ref) hook-id parsed)) (partial hook-fn (:hook hook-ref) hook-id {:id extension-id} parsed))
extension-hooks)) extension-hooks))
hooks)))) hooks))))
(fx/defn add-to-registry (fx/defn add-to-registry
[{:keys [db] :as cofx} extension-key extension-data active?] [{:keys [db] :as cofx} extension-id extension-data active?]
(let [{:keys [hooks]} extension-data (let [{:keys [hooks]} extension-data
data {:hooks hooks data {:hooks hooks
:active? active?}] :active? active?}]
(fx/merge cofx (fx/merge cofx
{:db (update-in db [:account/account :extensions extension-key] merge data)} {:db (update-in db [:account/account :extensions extension-id] merge data)}
(update-hooks hooks/hook-in extension-key)))) (update-hooks hooks/hook-in extension-id))))
(fx/defn remove-from-registry (fx/defn remove-from-registry
[cofx extension-key] [cofx extension-id]
(let [extensions (get-in cofx [:db :account/account :extensions])] (let [extensions (get-in cofx [:db :account/account :extensions])]
(fx/merge cofx (fx/merge cofx
(when (get-in extensions [extension-key :active?]) (when (get-in extensions [extension-id :active?])
(update-hooks hooks/unhook extension-key)) (update-hooks hooks/unhook extension-id))
{:db (update-in cofx [:db :account/account :extensions] dissoc extension-key)}))) {:db (update-in cofx [:db :account/account :extensions] dissoc extension-id)})))
(fx/defn change-state (fx/defn change-state
[cofx extension-key active?] [cofx extension-key active?]
@ -48,21 +49,23 @@
(update-hooks hook-fn extension-key)))) (update-hooks hook-fn extension-key))))
(fx/defn install (fx/defn install
[{:keys [db] :as cofx} {:keys [hooks] :as extension-data}] [{:keys [db] :as cofx} {:keys [hooks] :as extension-data} modal?]
(let [{:extensions/keys [manage] (let [{:extensions/keys [manage]
:account/keys [account]} db :account/keys [account]} db
{:keys [url]} manage url (get-in manage [:url :value])
extension {:id (:value url) extension {:id url
:name (get-in extension-data ['meta :name]) :name (get-in extension-data ['meta :name])
:url (:value url) :url url
:active? true} :active? true}
new-extensions (assoc (:extensions account) (:url extension) extension)] new-extensions (assoc (:extensions account) url extension)]
(fx/merge cofx (fx/merge cofx
{:utils/show-popup {:title (i18n/label :t/success) {:utils/show-popup {:title (i18n/label :t/success)
:content (i18n/label :t/extension-installed) :content (i18n/label :t/extension-installed)
:on-dismiss #(re-frame/dispatch [:navigate-to-clean :my-profile])}} :on-dismiss #(re-frame/dispatch (if modal?
[:navigate-back]
[:navigate-to-clean :my-profile]))}}
(when hooks (accounts.update/account-update {:extensions new-extensions} {})) (when hooks (accounts.update/account-update {:extensions new-extensions} {}))
(when hooks (add-to-registry (:value url) extension-data true))))) (when hooks (add-to-registry url extension-data true)))))
(fx/defn uninstall (fx/defn uninstall
[{:keys [db] :as cofx} extension-key] [{:keys [db] :as cofx} extension-key]
@ -75,13 +78,13 @@
(accounts.update/account-update {:extensions new-extensions} {})))) (accounts.update/account-update {:extensions new-extensions} {}))))
(fx/defn load (fx/defn load
[cofx url] [cofx url modal?]
(if (get-in cofx [:db :account/account :extensions url]) (if (get-in cofx [:db :account/account :extensions url])
{:utils/show-popup {:title (i18n/label :t/error) {:utils/show-popup {:title (i18n/label :t/error)
:content (i18n/label :t/extension-is-already-added)}} :content (i18n/label :t/extension-is-already-added)}}
{:extensions/load {:extensions [{:url (string/trim url) {:extensions/load {:extensions [{:url (string/trim url)
:active? true}] :active? true}]
:follow-up :extensions/stage}})) :follow-up (if modal? :extensions/stage-modal :extensions/stage)}}))
(fx/defn initialize (fx/defn initialize
[{{:account/keys [account]} :db}] [{{:account/keys [account]} :db}]
@ -108,3 +111,15 @@
(keys) (keys)
(map #(existing-hooks-for % cofx extension-data)) (map #(existing-hooks-for % cofx extension-data))
(apply set/union))) (apply set/union)))
(fx/defn stage-extension [{:keys [db] :as cofx} extension-data modal?]
(let [hooks (existing-hooks cofx extension-data)]
(if (empty? hooks)
(fx/merge cofx
{:db (assoc db :staged-extension extension-data)}
(navigation/navigate-to-cofx (if modal? :show-extension-modal :show-extension) nil))
{:utils/show-popup {:title (i18n/label :t/error)
:content (i18n/label :t/extension-hooks-cannot-be-added
{:hooks (->> hooks
(map name)
(clojure.string/join ", "))})}})))

View File

@ -53,6 +53,7 @@
(nav-text (merge props styles/item-text-white-background) text))) (nav-text (merge props styles/item-text-white-background) text)))
(def default-nav-back [nav-button actions/default-back]) (def default-nav-back [nav-button actions/default-back])
(def default-nav-close [nav-button actions/default-close])
(defn nav-back-count (defn nav-back-count
([] ([]
@ -160,4 +161,5 @@
(defn simple-toolbar (defn simple-toolbar
"A simple toolbar composed of a nav-back item and a single line title." "A simple toolbar composed of a nav-back item and a single line title."
([] (simple-toolbar nil)) ([] (simple-toolbar nil))
([title] (toolbar nil default-nav-back [content-title title]))) ([title] (simple-toolbar title false))
([title modal?] (toolbar nil (if modal? default-nav-close default-nav-back) [content-title title])))

View File

@ -1,9 +1,7 @@
(ns status-im.ui.screens.chat.message.message (ns status-im.ui.screens.chat.message.message
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.action-sheet :as action-sheet] [status-im.ui.components.action-sheet :as action-sheet]
@ -12,22 +10,31 @@
[status-im.ui.screens.chat.styles.message.message :as style] [status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.chat.photos :as photos]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.utils.core :as utils]
[status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.chat.utils :as chat.utils]
[status-im.utils.identicon :as identicon] [status-im.utils.identicon :as identicon]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[clojure.string :as string])) [status-im.ui.components.icons.vector-icons :as icons]))
(defn install-extension-message [extension-id outgoing]
[react/touchable-highlight {:on-press #(re-frame/dispatch
[:extensions.ui/install-extension-button-pressed extension-id])}
[react/view style/extension-container
[icons/icon :icons/info {:color (if outgoing colors/white colors/gray)}]
[react/text {:style (style/extension-text outgoing)}
(i18n/label :to-see-this-message)]
[react/text {:style (style/extension-install outgoing)}
(i18n/label :install-the-extension)]]])
(defview message-content-command (defview message-content-command
[command-message] [command-message]
(letsubs [id->command [:chats/id->command]] (letsubs [id->command [:chats/id->command]]
(if-let [command (commands-receiving/lookup-command-by-ref command-message id->command)] (if-let [command (commands-receiving/lookup-command-by-ref command-message id->command)]
(commands/generate-preview command command-message) (commands/generate-preview command command-message)
[react/text (str "Unhandled command: " (-> command-message :content :command-path first))]))) (if-let [extension-id (get-in command-message [:content :params :extension-id])]
[install-extension-message extension-id (:outgoing command-message)]
[react/text (str "Unhandled command: " (-> command-message :content :command-path first))]))))
(defview message-timestamp [t justify-timestamp? outgoing command? content] (defview message-timestamp [t justify-timestamp? outgoing command? content]
(when-not command? (when-not command?

View File

@ -198,3 +198,17 @@
:color (if outgoing :color (if outgoing
colors/wild-blue-yonder colors/wild-blue-yonder
colors/gray)}) colors/gray)})
(def extension-container
{:align-items :center
:margin 10})
(defn extension-text [outgoing]
{:font-size 12
:margin-top 10
:color (if outgoing colors/white-transparent colors/gray)})
(defn extension-install [outgoing]
{:font-size 12
:color (if outgoing colors/white colors/blue)})

View File

@ -15,17 +15,13 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions/stage :extensions/stage
(fn [{:keys [db] :as cofx} [_ _ extension-data]] (fn [cofx [_ _ extension-data]]
(let [hooks (extensions.registry/existing-hooks cofx extension-data)] (extensions.registry/stage-extension cofx extension-data false)))
(if (empty? hooks)
(fx/merge cofx (handlers/register-handler-fx
{:db (assoc db :staged-extension extension-data)} :extensions/stage-modal
(navigation/navigate-to-cofx :show-extension nil)) (fn [cofx [_ _ extension-data]]
{:utils/show-popup {:title (i18n/label :t/error) (extensions.registry/stage-extension cofx extension-data true)))
:content (i18n/label :t/extension-hooks-cannot-be-added
{:hooks (->> hooks
(map name)
(clojure.string/join ", "))})}}))))
(handlers/register-handler-fx (handlers/register-handler-fx
:extensions/add-to-registry :extensions/add-to-registry

View File

@ -30,13 +30,13 @@
values)) values))
hooks)) hooks))
(views/defview show-extension [] (views/defview show-extension-base [modal?]
(views/letsubs [{:keys [data errors]} [:get-staged-extension]] (views/letsubs [{:keys [data errors]} [:get-staged-extension]]
(if data (if data
[react/view styles/screen [react/view styles/screen
[status-bar/status-bar] [status-bar/status-bar]
[react/keyboard-avoiding-view components.styles/flex [react/keyboard-avoiding-view components.styles/flex
[toolbar/simple-toolbar (i18n/label :t/extension)] [toolbar/simple-toolbar (i18n/label :t/extension) modal?]
[react/scroll-view {:keyboard-should-persist-taps :handled} [react/scroll-view {:keyboard-should-persist-taps :handled}
[react/view styles/wrapper [react/view styles/wrapper
[react/view {:style {:border-radius 8 :margin 10 :padding 8 :background-color colors/red}} [react/view {:style {:border-radius 8 :margin 10 :padding 8 :background-color colors/red}}
@ -71,7 +71,7 @@
{:forward? true {:forward? true
:label (i18n/label :t/install) :label (i18n/label :t/install)
:disabled? (not (empty? errors)) :disabled? (not (empty? errors))
:on-press #(re-frame/dispatch [:extensions.ui/install-button-pressed data])}]]]] :on-press #(re-frame/dispatch [:extensions.ui/install-button-pressed data modal?])}]]]]
[react/view styles/screen [react/view styles/screen
[status-bar/status-bar] [status-bar/status-bar]
[react/view {:flex 1} [react/view {:flex 1}
@ -80,6 +80,12 @@
[react/text (i18n/label :t/invalid-extension)] [react/text (i18n/label :t/invalid-extension)]
[react/text (str errors)]]]]))) [react/text (str errors)]]]])))
(views/defview show-extension []
[show-extension-base false])
(views/defview show-extension-modal []
[show-extension-base true])
(def qr-code (def qr-code
[react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed [react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed
{:toolbar-title (i18n/label :t/scan-qr)} {:toolbar-title (i18n/label :t/scan-qr)}

View File

@ -47,7 +47,7 @@
[status-im.ui.screens.fleet-settings.views :refer [fleet-settings]] [status-im.ui.screens.fleet-settings.views :refer [fleet-settings]]
[status-im.ui.screens.offline-messaging-settings.views :refer [offline-messaging-settings]] [status-im.ui.screens.offline-messaging-settings.views :refer [offline-messaging-settings]]
[status-im.ui.screens.offline-messaging-settings.edit-mailserver.views :refer [edit-mailserver]] [status-im.ui.screens.offline-messaging-settings.edit-mailserver.views :refer [edit-mailserver]]
[status-im.ui.screens.extensions.add.views :refer [edit-extension show-extension]] [status-im.ui.screens.extensions.add.views :refer [edit-extension show-extension show-extension-modal]]
[status-im.ui.screens.bootnodes-settings.views :refer [bootnodes-settings]] [status-im.ui.screens.bootnodes-settings.views :refer [bootnodes-settings]]
[status-im.ui.screens.pairing.views :refer [installations]] [status-im.ui.screens.pairing.views :refer [installations]]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]] [status-im.ui.screens.bootnodes-settings.edit-bootnode.views :refer [edit-bootnode]]
@ -177,6 +177,9 @@
:chat-modal :chat-modal
(wrap-modal :chat-modal chat-modal) (wrap-modal :chat-modal chat-modal)
:show-extension-modal
(wrap-modal :show-extension-modal show-extension-modal)
:wallet-send-modal-stack :wallet-send-modal-stack
{:screens {:screens
{:wallet-send-transaction-modal {:wallet-send-transaction-modal

View File

@ -21,9 +21,9 @@
:on-click? :event} :on-click? :event}
:hook :hook
(reify hooks/Hook (reify hooks/Hook
(hook-in [_ id {:keys [label view on-click]} {:keys [db]}] (hook-in [_ id env {:keys [label view on-click]} {:keys [db]}]
{:db (assoc-in db [:wallet :settings id] {:label label :view view :on-click on-click})}) {:db (assoc-in db [:wallet :settings id] {:label label :view view :on-click on-click})})
(unhook [_ id _ {:keys [db]}] (unhook [_ id env _ {:keys [db]}]
{:db (update-in db [:wallet :settings] dissoc id)}))}) {:db (update-in db [:wallet :settings] dissoc id)}))})
(defn- render-token [{:keys [symbol name icon]} visible-tokens] (defn- render-token [{:keys [symbol name icon]} visible-tokens]

View File

@ -72,7 +72,7 @@
(fx/defn handle-extension [cofx url] (fx/defn handle-extension [cofx url]
(log/info "universal-links: handling url profile" url) (log/info "universal-links: handling url profile" url)
(extensions.registry/load cofx url)) (extensions.registry/load cofx url false))
(defn handle-not-found [full-url] (defn handle-not-found [full-url]
(log/info "universal-links: no handler for " full-url)) (log/info "universal-links: no handler for " full-url))

View File

@ -780,9 +780,11 @@
"share-dapp-text": "Check out this DApp I'm using on Status: {{link}}", "share-dapp-text": "Check out this DApp I'm using on Status: {{link}}",
"network-invalid-url": "Network URL is invalid", "network-invalid-url": "Network URL is invalid",
"network-invalid-status-code": "Invalid status code: {{code}}", "network-invalid-status-code": "Invalid status code: {{code}}",
"extension-is-already-added": "The extension is already added", "extension-is-already-added": "The extension is already installed",
"extension-uninstalled": "The extension was uninstalled", "extension-uninstalled": "The extension was uninstalled",
"extension-hooks-cannot-be-added": "The following hooks from this extension cannot be added: {{hooks}}", "extension-hooks-cannot-be-added": "The following hooks from this extension cannot be added: {{hooks}}",
"to-see-this-message": "To see this message,",
"install-the-extension": "install the extension",
"migrations-failed-title": "Migration failed", "migrations-failed-title": "Migration failed",
"migrations-failed-content": "{{message}}\n\nPlease let us know about this problem at #status public chat. If you press \"Cancel\" button, nothing will happen. If you press \"{{erase-accounts-data-button-text}}\" button, account's db will be removed and you will be able to unlock account. All account's data will be lost.", "migrations-failed-content": "{{message}}\n\nPlease let us know about this problem at #status public chat. If you press \"Cancel\" button, nothing will happen. If you press \"{{erase-accounts-data-button-text}}\" button, account's db will be removed and you will be able to unlock account. All account's data will be lost.",
"migrations-erase-accounts-data-button": "Erase account's db" "migrations-erase-accounts-data-button": "Erase account's db"