mirror of
https://github.com/penpot/penpot.git
synced 2026-11-06 01:50:39 +00:00
✨ Preserve subscription audit properties (#12850)
This commit is contained in:
parent
c6a9e62a4a
commit
e42bae34cf
@ -254,7 +254,7 @@ Result: crash reports only exist if the frontend collector runs and `push-audit-
|
||||
|
||||
Telemetry reuses the same table with **anonymized shadow rows** (`source LIKE 'telemetry:%'`): day-truncated times, `0.0.0.0` IPs, props reduced to uuid/boolean/number values plus a few allowed fields (`lang`, `auth-backend`, derived `email-domain` — never raw emails), and a minimal context allowlist. Full + shadow rows can exist together per event when both flags are on; that duplication is on purpose.
|
||||
|
||||
Filters (`filter-telemetry-props` / `filter-telemetry-context`): by default only uuid/boolean/number with a simple key; exceptions: login/register/update-profile and frontend `identify` keep `lang`/`auth-backend`/`email-domain`; `organization-sso-auth-failed` keeps `failure-reason` if it is a known one; `instance-start` passes whole; frontend `navigate` keeps `[:route :file-id :team-id :page-id]`. Free text drops from telemetry but **stays in the full row and in Nexus**.
|
||||
Filters (`filter-telemetry-props` / `filter-telemetry-context`): by default only uuid/boolean/number with a simple key; exceptions: login/register/update-profile and frontend `identify` keep `lang`/`auth-backend`/`email-domain`; `organization-sso-auth-failed` keeps `failure-reason` if it is a known one; `instance-start` passes whole; frontend `navigate` keeps `[:route :file-id :team-id :page-id]`. Subscription actions have event-specific contracts: backend and frontend `create-subscription` / `create-trial-subscription` preserve the default uuid/boolean/number props plus `type`, `deployment`, `quantity` and `subscription-mode`; backend `update-subscription` preserves only `type`, `deployment`, `previous-status`, `status`, `previous-cancel-at-period-end`, `cancel-at-period-end`, `previous-cancel-at`, `cancel-at` and `changed-fields`; backend `subscription-summary` preserves only `type`, `deployment`, `instance-id`, `snapshot-date`, the per-status `*-count` values, `total-count`, `sync-succeeded`, `sync-failed` and `snapshot-complete`. These subscription exceptions apply only to action events. Other free text drops from telemetry but **stays in the full row and in Nexus**.
|
||||
|
||||
Shipping (`backend/src/app/tasks/telemetry.clj`): the cron sends shadows to `:telemetry-uri` as JSON in 10k batches and **deletes** them on success; it purges leftovers older than 7 days. It also sends a legacy report (counters, teams, email domains...). On official hosts nothing is collected or sent (`telemetry-excluded?` covers `penpot.app`/`penpot.dev`).
|
||||
|
||||
|
||||
@ -48,6 +48,46 @@
|
||||
"incomplete-user-info"
|
||||
"unexpected-error"})
|
||||
|
||||
(def ^:private subscription-creation-events
|
||||
#{"create-subscription" "create-trial-subscription"})
|
||||
|
||||
(def ^:private subscription-creation-telemetry-props
|
||||
#{:type
|
||||
:deployment
|
||||
:quantity
|
||||
:subscription-mode})
|
||||
|
||||
(def ^:private subscription-update-telemetry-props
|
||||
#{:type
|
||||
:deployment
|
||||
:previous-status
|
||||
:status
|
||||
:previous-cancel-at-period-end
|
||||
:cancel-at-period-end
|
||||
:previous-cancel-at
|
||||
:cancel-at
|
||||
:changed-fields})
|
||||
|
||||
(def ^:private subscription-summary-telemetry-props
|
||||
#{:type
|
||||
:deployment
|
||||
:instance-id
|
||||
:snapshot-date
|
||||
:trialing-count
|
||||
:active-count
|
||||
:active-cancel-scheduled-count
|
||||
:past-due-count
|
||||
:canceled-count
|
||||
:unpaid-count
|
||||
:incomplete-count
|
||||
:incomplete-expired-count
|
||||
:paused-count
|
||||
:other-count
|
||||
:total-count
|
||||
:sync-succeeded
|
||||
:sync-failed
|
||||
:snapshot-complete})
|
||||
|
||||
(def ^:private safe-backend-context-keys
|
||||
#{:version
|
||||
:initiator
|
||||
@ -348,6 +388,24 @@
|
||||
(defn filter-telemetry-props
|
||||
[{:keys [source name props type] :as params}]
|
||||
(cond
|
||||
(and (= type "action")
|
||||
(contains? subscription-creation-events name)
|
||||
(or (= source "backend")
|
||||
(= source "frontend")))
|
||||
(let [props' (into {} xf:filter-telemetry-props props)
|
||||
props' (merge props' (select-keys props subscription-creation-telemetry-props))]
|
||||
(assoc params :props props'))
|
||||
|
||||
(and (= source "backend")
|
||||
(= type "action")
|
||||
(= name "update-subscription"))
|
||||
(assoc params :props (select-keys props subscription-update-telemetry-props))
|
||||
|
||||
(and (= source "backend")
|
||||
(= type "action")
|
||||
(= name "subscription-summary"))
|
||||
(assoc params :props (select-keys props subscription-summary-telemetry-props))
|
||||
|
||||
(and (= source "backend")
|
||||
(= name "organization-sso-auth-failed"))
|
||||
(let [props' (into {} xf:filter-telemetry-props props)
|
||||
|
||||
@ -828,6 +828,79 @@
|
||||
(t/is (not (contains? (:props result) :active)))
|
||||
(t/is (not (contains? (:props result) :label)))))
|
||||
|
||||
(t/deftest test-filter-telemetry-props-subscription-creation-keeps-safe-fields
|
||||
(let [ftp (ns-resolve 'app.loggers.audit 'filter-telemetry-props)
|
||||
organization-id (uuid/next)
|
||||
props {:type "nitrate:enterprise"
|
||||
:deployment "saas"
|
||||
:quantity 5
|
||||
:subscription-mode "paid"
|
||||
:organization-id organization-id
|
||||
:email "owner@example.com"
|
||||
:unsafe-label "should-be-stripped"}
|
||||
expected {:type "nitrate:enterprise"
|
||||
:deployment "saas"
|
||||
:quantity 5
|
||||
:subscription-mode "paid"
|
||||
:organization-id organization-id}]
|
||||
(doseq [source ["backend" "frontend"]
|
||||
name ["create-subscription" "create-trial-subscription"]]
|
||||
(t/testing (str source " " name)
|
||||
(let [result (ftp {:source source
|
||||
:name name
|
||||
:type "action"
|
||||
:props props})]
|
||||
(t/is (= expected (:props result))))))))
|
||||
|
||||
(t/deftest test-filter-telemetry-props-subscription-update-keeps-safe-fields
|
||||
(let [ftp (ns-resolve 'app.loggers.audit 'filter-telemetry-props)
|
||||
props {:type "nitrate:enterprise"
|
||||
:deployment "selfhost"
|
||||
:previous-status "trialing"
|
||||
:status "active"
|
||||
:previous-cancel-at-period-end false
|
||||
:cancel-at-period-end true
|
||||
:previous-cancel-at nil
|
||||
:cancel-at "2026-12-01T00:00:00.000Z"
|
||||
:changed-fields ["status" "cancel-at-period-end" "cancel-at"]
|
||||
:email "owner@example.com"
|
||||
:unsafe-label "should-be-stripped"}
|
||||
result (ftp {:source "backend"
|
||||
:name "update-subscription"
|
||||
:type "action"
|
||||
:props props})]
|
||||
(t/is (= (dissoc props :email :unsafe-label)
|
||||
(:props result)))))
|
||||
|
||||
(t/deftest test-filter-telemetry-props-subscription-summary-keeps-safe-fields
|
||||
(let [ftp (ns-resolve 'app.loggers.audit 'filter-telemetry-props)
|
||||
props {:type "nitrate:enterprise"
|
||||
:deployment "saas"
|
||||
:instance-id "00000000-0000-4000-8000-000000000099"
|
||||
:snapshot-date "2026-11-05"
|
||||
:trialing-count 1
|
||||
:active-count 2
|
||||
:active-cancel-scheduled-count 3
|
||||
:past-due-count 4
|
||||
:canceled-count 5
|
||||
:unpaid-count 6
|
||||
:incomplete-count 7
|
||||
:incomplete-expired-count 8
|
||||
:paused-count 9
|
||||
:other-count 10
|
||||
:total-count 55
|
||||
:sync-succeeded 54
|
||||
:sync-failed 1
|
||||
:snapshot-complete false
|
||||
:email "owner@example.com"
|
||||
:unsafe-label "should-be-stripped"}
|
||||
result (ftp {:source "backend"
|
||||
:name "subscription-summary"
|
||||
:type "action"
|
||||
:props props})]
|
||||
(t/is (= (dissoc props :email :unsafe-label)
|
||||
(:props result)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SEND-EVENT-BATCH PAYLOAD STRUCTURE
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user