✨ Add deployment info to events (#11867)

* ✨ Add deployment info to events

* ✨ Add get-environment-data RPC method

Add a single public RPC method returning the deployment type and
the enabled environment flags. It replaces get-deployment on the
management API and get-enabled-flags on the main API.

get-enabled-flags stays as a deprecated alias returning only the
flags, so existing callers keep working until it is removed.

The frontend event initialization now reads the flags from the new
method. The exposed flags stay limited to audit-log and telemetry
to avoid leaking internal backend flags.

AI-assisted-by: deepseek-v4.1-flash

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Marina López 2026-09-25 11:22:43 +02:00 committed by GitHub
parent 4b978767ea
commit 0255bed6c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 152 additions and 12 deletions

View File

@ -98,6 +98,9 @@
:main/get-subscription-usage}
[[:get-access-tokens :bucket "150/75/30s"]]
#{:main/get-environment-data}
[[:get-environment-data :bucket "250/125/30s"]]
#{:main/get-enabled-flags}
[[:get-enabled-flags :bucket "250/125/30s"]]

View File

@ -49,6 +49,9 @@
:host "localhost"
:tenant "default"
;; The SaaS host also sets penpotIsSaas in the browser. Keep this server
;; value in sync so Admin Console can query the same deployment type.
:is-saas false
:redis-uri "redis://redis/0"
@ -109,6 +112,7 @@
[:secret-key {:optional true} :string]
[:tenant {:optional false} :string]
[:is-saas ::sm/boolean]
[:public-uri {:optional false} ::sm/uri]
[:host {:optional false} :string]

View File

@ -178,13 +178,45 @@
(rph/wrap nil)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET-ENABLED-FLAGS
;; GET-ENVIRONMENT-DATA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(sv/defmethod ::get-enabled-flags
(def ^:private enabled-flags
"Subset of the backend flags that are safe to expose publicly and that
drive frontend event collection."
#{:audit-log :telemetry})
(defn- environment-data
[]
{:deployment (if (cf/get :is-saas) "saas" "selfhost")
:flags (set/intersection cf/flags enabled-flags)})
(def ^:private schema:get-environment-data-result
[:map
[:deployment [:enum "saas" "selfhost"]]
[:flags [::sm/set [:enum :audit-log :telemetry]]]])
(sv/defmethod ::get-environment-data
"Returns the deployment type and the enabled environment flags."
{::audit/skip true
::rpc/auth false
::doc/skip true
::doc/added "2.19"
::sm/params [:map]
::sm/result schema:get-environment-data-result}
[_cfg _params]
(environment-data))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET-ENABLED-FLAGS (DEPRECATED)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(sv/defmethod ::get-enabled-flags
"Deprecated. Use `get-environment-data` instead."
{::audit/skip true
::rpc/auth false
::doc/skip true
::doc/deprecated true
::doc/added "1.20"}
[_cfg _params]
(set/intersection cf/flags #{:audit-log :telemetry}))
(:flags (environment-data)))

View File

@ -304,6 +304,41 @@
(t/is (nil? (:error out)))
(t/is (= 0 (count (th/db-exec! ["select * from audit_log"])))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET-ENVIRONMENT-DATA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(t/deftest get-environment-data-returns-selfhost-by-default
(with-redefs [cf/flags #{:audit-log}]
(let [out (th/command! {::th/type :get-environment-data})]
(t/is (th/success? out))
(t/is (= "selfhost" (-> out :result :deployment))))))
(t/deftest get-environment-data-returns-saas-when-configured
(with-redefs [cf/flags #{:telemetry}]
(binding [cf/config (assoc cf/config :is-saas true)]
(let [out (th/command! {::th/type :get-environment-data})]
(t/is (th/success? out))
(t/is (= "saas" (-> out :result :deployment)))))))
(t/deftest get-environment-data-only-exposes-event-flags
(with-redefs [cf/flags #{:audit-log :telemetry :graph :nrepl-server}]
(let [out (th/command! {::th/type :get-environment-data})]
(t/is (th/success? out))
(t/is (= #{:audit-log :telemetry} (-> out :result :flags))))))
(t/deftest get-environment-data-returns-empty-flags-when-none-enabled
(with-redefs [cf/flags #{:graph}]
(let [out (th/command! {::th/type :get-environment-data})]
(t/is (th/success? out))
(t/is (= #{} (-> out :result :flags))))))
(t/deftest get-enabled-flags-still-returns-flat-set
(with-redefs [cf/flags #{:audit-log :telemetry :graph}]
(let [out (th/command! {::th/type :get-enabled-flags})]
(t/is (th/success? out))
(t/is (= #{:audit-log :telemetry} (:result out))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PURE HELPER UNIT TESTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -359,6 +359,23 @@
(rx/map deref)
(rx/map snapshot-performance-info)))))
(defn fetch-environment-data
"Fetches the backend environment data (deployment type and enabled
flags). On RPC failure, falls back to telemetry so event collection is
not silently dropped; the backend rejects events if truly disabled."
[]
(->> (rp/cmd! :get-environment-data)
(rx/catch (fn [cause]
(l/debug :hint "unable to fetch backend environment data, proceeding with event collection"
:cause cause)
(rx/of {:flags #{:telemetry}})))))
(defn events-enabled?
"Returns true when the environment flags enable frontend event collection."
[flags]
(or (contains? flags :audit-log)
(contains? flags :telemetry)))
(defn initialize
[]
(ptk/reify ::initialize
@ -378,16 +395,14 @@
(l/debug :hint "event instrumentation initialized")
;; Fetch backend flags and only start event collection if
;; :audit-log or :telemetry is enabled. On RPC failure, proceed
;; with event collection anyway (backend will reject if truly disabled).
(->> (rp/cmd! :get-enabled-flags)
(rx/catch (fn [cause]
(l/debug :hint "unable to fetch backend flags, proceeding with event collection" :cause cause)
(rx/of #{:telemetry})))
;; Fetch the backend environment data and only start event
;; collection if :audit-log or :telemetry is enabled. On RPC
;; failure, proceed with event collection anyway (backend will
;; reject if truly disabled).
(->> (fetch-environment-data)
(rx/map :flags)
(rx/mapcat (fn [flags]
(if (or (contains? flags :audit-log)
(contains? flags :telemetry))
(if (events-enabled? flags)
(do
(l/debug :hint "event collection enabled" :flags (str/join " " (map name flags)))
(rx/of true))

View File

@ -279,6 +279,7 @@
(fn []
(st/emit! (ev/event {::ev/name "open-subscription-management"
::ev/origin "dashboard"
:deployment (if cf/saas? "saas" "selfhost")
:section "team-settings"}))
(let [href (-> (rt/get-current-href)
(rt/encode-url))

View File

@ -201,6 +201,7 @@
(reset! form nil)
(st/emit! (ev/event {::ev/name event-name
:type "unlimited"
:deployment (if cf/saas? "saas" "selfhost")
:quantity min-members
:subscription-mode subscription-mode})
(rt/nav-raw :href href))))))
@ -216,6 +217,7 @@
href (dm/str "payments/subscriptions/create?type=enterprise&returnUrl=" return-url)]
(st/emit! (ev/event {::ev/name event-name
:type "enterprise"
:deployment (if cf/saas? "saas" "selfhost")
:subscription-mode subscription-mode})
(rt/nav-raw :href href)))))
@ -224,6 +226,7 @@
(fn []
(st/emit! (ev/event {::ev/name "open-subscription-management"
::ev/origin "settings"
:deployment (if cf/saas? "saas" "selfhost")
:section "subscription-management-modal"}))
(let [current-href (rt/get-current-href)
returnUrl (js/encodeURIComponent current-href)
@ -489,6 +492,7 @@
(fn []
(st/emit! (ev/event {::ev/name "open-subscription-management"
::ev/origin "settings"
:deployment (if cf/saas? "saas" "selfhost")
:section "subscription"}))
(let [current-href (rt/get-current-href)
returnUrl (js/encodeURIComponent current-href)
@ -500,6 +504,7 @@
(fn []
(st/emit! (ev/event {::ev/name "open-subscription-management"
::ev/origin "settings"
:deployment (if cf/saas? "saas" "selfhost")
:section "nitrate:enterprise"}))
(dnt/go-to-nitrate-billing)))

View File

@ -0,0 +1,43 @@
;; 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 SUBSIDIARY SL
(ns frontend-tests.data.event-test
(:require
[app.main.data.event :as ev]
[app.main.repo :as rp]
[beicon.v2.core :as rx]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.async :as async]
[frontend-tests.helpers.mock :as mock]))
(t/deftest events-enabled?-truth-table
(t/testing "enabled when audit-log is present"
(t/is (true? (ev/events-enabled? #{:audit-log}))))
(t/testing "enabled when telemetry is present"
(t/is (true? (ev/events-enabled? #{:telemetry}))))
(t/testing "enabled when both are present"
(t/is (true? (ev/events-enabled? #{:audit-log :telemetry}))))
(t/testing "disabled when no event flag is present"
(t/is (false? (ev/events-enabled? #{})))
(t/is (false? (ev/events-enabled? #{:graph})))))
(t/deftest ^:async fetch-environment-data-uses-get-environment-data
(let [data (await
(mock/with-mocks*
{rp/cmd! (mock/stub
(fn [cmd]
(t/is (= :get-environment-data cmd))
(rx/of {:deployment "saas"
:flags #{:audit-log}})))}
(await (async/->promise (ev/fetch-environment-data)))))]
(t/is (= {:deployment "saas" :flags #{:audit-log}} data))))
(t/deftest ^:async fetch-environment-data-falls-back-to-telemetry-on-error
(let [data (await
(mock/with-mocks*
{rp/cmd! (mock/stub (fn [_cmd] (rx/throw (ex-info "boom" {}))))}
(await (async/->promise (ev/fetch-environment-data)))))]
(t/is (= {:flags #{:telemetry}} data))))

View File

@ -10,6 +10,7 @@
[frontend-tests.copy-as-svg-test]
[frontend-tests.data.comments-filters-test]
[frontend-tests.data.dashboard-test]
[frontend-tests.data.event-test]
[frontend-tests.data.exports-assets-test]
[frontend-tests.data.nitrate-test]
[frontend-tests.data.persistence-retry-test]
@ -137,6 +138,7 @@
'frontend-tests.copy-as-svg-test
'frontend-tests.data.comments-filters-test
'frontend-tests.data.dashboard-test
'frontend-tests.data.event-test
'frontend-tests.data.nitrate-test
'frontend-tests.data.persistence-retry-test
'frontend-tests.data.persistence-test