;; This Source Code Form is subject to the terms of the Mozilla Public ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) KALEIDOS INC (ns app.main.features "A thin, frontend centric abstraction layer and collection of helpers for `app.common.features` namespace." (:require [app.common.features :as cfeat] [app.common.logging :as log] [app.config :as cf] [app.main.store :as st] [beicon.v2.core :as rx] [clojure.set :as set] [cuerdas.core :as str] [okulary.core :as l] [potok.v2.core :as ptk] [rumext.v2 :as mf])) (log/set-level! :trace) (def global-enabled-features (cfeat/get-enabled-features cf/flags)) (defn get-enabled-features [state] (-> (get state :features-runtime #{}) (set/intersection cfeat/no-migration-features) (set/union global-enabled-features))) (defn get-team-enabled-features [state] (-> global-enabled-features (set/union (:features-runtime state #{})) (set/intersection cfeat/no-migration-features) (set/union (:features-team state #{})))) (def features-ref (l/derived get-team-enabled-features st/state =)) (defn active-feature? "Given a state and feature, check if feature is enabled" [state feature] (assert (contains? cfeat/supported-features feature) "not supported feature") (or (contains? (get state :features-runtime) feature) (if (contains? cfeat/no-migration-features feature) (or (contains? global-enabled-features feature) (contains? (get state :features-team) feature)) (contains? (get state :features-team state) feature)))) (defn use-feature "A react hook that checks if feature is currently enabled" [feature] (assert (contains? cfeat/supported-features feature) "Not supported feature") (let [enabled-features (mf/deref features-ref)] (contains? enabled-features feature))) (defn toggle-feature "An event constructor for runtime feature toggle. Warning: if a feature is active globally or by team, it can't be disabled." [feature] (ptk/reify ::toggle-feature ptk/UpdateEvent (update [_ state] (assert (contains? cfeat/supported-features feature) "not supported feature") (update state :features-runtime (fn [features] (if (contains? features feature) (do (log/trc :hint "feature disabled" :feature feature) (disj features feature)) (do (log/trc :hint "feature enabled" :feature feature) (conj features feature)))))))) (defn enable-feature [feature] (ptk/reify ::enable-feature ptk/UpdateEvent (update [_ state] (assert (contains? cfeat/supported-features feature) "not supported feature") (if (active-feature? state feature) state (do (log/trc :hint "feature enabled" :feature feature) (update state :features-runtime (fnil conj #{}) feature)))))) (defn initialize ([] (initialize #{})) ([team-features] (assert (set? team-features) "expected a set of features") (assert (every? string? team-features) "expected a set of strings") (ptk/reify ::initialize ptk/UpdateEvent (update [_ state] (let [runtime-features (get state :features/runtime #{}) team-features (into #{} cfeat/xf-supported-features team-features)] (-> state (assoc :features-runtime runtime-features) (assoc :features-team team-features)))) ptk/WatchEvent (watch [_ _ _] (when *assert* (->> (rx/from cfeat/no-migration-features) ;; text editor v2 isn't enabled by default even in devenv (rx/filter #(not (or (contains? cfeat/backend-only-features %) (= "text-editor/v2" %)))) (rx/observe-on :async) (rx/map enable-feature)))) ptk/EffectEvent (effect [_ state _] (log/trc :hint "initialized features" :team (str/join "," (:features-team state)) :runtime (str/join "," (:features-runtime state)))))))