refactor: inline single-site telemetry helpers and trim what is collected

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GLnp4v49BozkDd3KeWn5Q3
This commit is contained in:
hugocasa
2026-09-04 18:58:10 +02:00
co-authored by Claude Opus 5
parent c4d8d5a30d
commit fb453d88b8
9 changed files with 45 additions and 83 deletions
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n COUNT(*)::BIGINT AS \"total!\",\n COUNT(*) FILTER (WHERE code_down IS NOT NULL)::BIGINT AS \"with_down!\",\n COUNT(DISTINCT (workspace_id, datatable))::BIGINT AS \"datatables!\"\n FROM datatable_migrations",
"query": "SELECT\n COUNT(*)::BIGINT AS \"total!\",\n COUNT(DISTINCT (workspace_id, datatable))::BIGINT AS \"datatables!\"\n FROM datatable_migrations",
"describe": {
"columns": [
{
@@ -10,11 +10,6 @@
},
{
"ordinal": 1,
"name": "with_down!",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "datatables!",
"type_info": "Int8"
}
@@ -23,10 +18,9 @@
"Left": []
},
"nullable": [
null,
null,
null
]
},
"hash": "bf7528712e452e72611e35fb0803cb0bb29879ec010e1f9f36f19a2b3b5a721c"
"hash": "2105d37be923a445933c899bb31523709f837b7e1969b7364f70cc2a60cdd520"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT usage_kind::text AS \"kind!\", COUNT(*)::BIGINT AS \"count!\"\n FROM asset WHERE kind = 'datatable' AND usage_kind <> 'job'\n GROUP BY 1\n UNION ALL\n SELECT 'job_recent'::text, COUNT(DISTINCT path)::BIGINT\n FROM asset\n WHERE kind = 'datatable' AND usage_kind = 'job'\n AND created_at > now() - interval '30 days'",
"query": "SELECT usage_kind::text AS \"kind!\", COUNT(*)::BIGINT AS \"count!\"\n FROM asset WHERE kind = 'datatable' AND usage_kind <> 'job'\n GROUP BY 1\n UNION ALL\n SELECT 'job_recent'::text, COUNT(DISTINCT (workspace_id, path))::BIGINT\n FROM asset\n WHERE kind = 'datatable' AND usage_kind = 'job'\n AND created_at > now() - interval '30 days'",
"describe": {
"columns": [
{
@@ -22,5 +22,5 @@
null
]
},
"hash": "716e9945096c3bc59c793decac4902061f949af3b52313484cc139ffdfeca42f"
"hash": "d3a6a27ece3b5d5071dd8074b8db6f369eceb079b31ca118cae23c3d15314590"
}
+1 -1
View File
@@ -1 +1 @@
895634d16038e0018c4eebef8c2da53b20576ccd
548b5e0421a04a2d9a76cce6efc6c91b1d8560ee
@@ -416,6 +416,16 @@ async fn run_datatable_migrations(
let applied_versions = read_applied_versions_on_client(&client, &datatable_name).await?;
// How the user scoped the run, for the counter emitted on the first migration
// that lands below.
let scope = if query.only.is_some() {
"only"
} else if query.up_to.is_some() {
"up_to"
} else {
"all"
};
let mut applied = Vec::new();
for m in migrations {
if let Some(only) = query.only {
@@ -453,23 +463,14 @@ async fn run_datatable_migrations(
))
})?;
applied.push(AppliedMigration { version: m.timestamp, name: m.name });
}
// One event per run that moved the data table forward, keyed by how the user
// scoped it. A run with nothing pending is not counted: it is the common
// outcome of opening the list and would drown out the runs that did something.
if !applied.is_empty() {
windmill_common::feature_usage::log_feature_usage(
"datatable",
"migration_run",
if query.only.is_some() {
"only"
} else if query.up_to.is_some() {
"up_to"
} else {
"all"
},
);
// One event per run that moved the data table forward, emitted on the
// first migration that lands rather than after the loop: a later one
// failing returns early, and that run still advanced the data table. A
// run with nothing pending stays uncounted it is the common outcome of
// opening the list and would drown out the runs that did something.
if applied.len() == 1 {
windmill_common::feature_usage::log_feature_usage("datatable", "migration_run", scope);
}
}
Ok(Json(RunDatatableMigrationsResult { applied }))
@@ -1073,19 +1073,16 @@
are started for, whether AI chat skills are turned on or off and how often one is
loaded, whether SSO logins evaluate an IdP groups claim (SAML or OIDC) and change a
membership, the plan tier and quota shown when the execution meter is opened, whether
sandbox isolation is turned on or off for low-code and raw apps, whether a step's
workspace script is opened and saved from inside the flow editor, and how data tables
are set up and used (which database kind is picked, how far the setup wizard gets,
which choice the schema-change prompt is answered with, and whether migrations are
opted into, written, run or rolled back), last 30 days)</li
sandbox isolation is turned on or off for apps, whether a step's script is edited from
the flow editor, and how data tables are set up and used (database kind, setup wizard
progress, schema-change prompt choice, and migrations opted into, written, run or
rolled back), last 30 days)</li
>
<li
>feature adoption (counts of which flow, script, trigger, worker and data table
features your deployed items use, including how many apps run sandboxed, how many data
tables are configured per database kind, how many of those have migrations turned on
or off, how many migrations they carry and how many of those can be rolled back, how
many scripts and flows read or write a data table, and how many data tables a job
touched in the last 30 days)</li
tables exist per database kind, how many have migrations turned on, how many
migrations they carry, and how many scripts, flows and recent jobs use a data table)</li
>
<li
>resource counts (workspaces, scripts per language, flows, workflows as code, low-code
@@ -1136,19 +1133,16 @@
are started for, whether AI chat skills are turned on or off and how often one is
loaded, whether SSO logins evaluate an IdP groups claim (SAML or OIDC) and change a
membership, the plan tier and quota shown when the execution meter is opened, whether
sandbox isolation is turned on or off for low-code and raw apps, whether a step's
workspace script is opened and saved from inside the flow editor, and how data tables
are set up and used (which database kind is picked, how far the setup wizard gets,
which choice the schema-change prompt is answered with, and whether migrations are
opted into, written, run or rolled back), last 30 days)</li
sandbox isolation is turned on or off for apps, whether a step's script is edited from
the flow editor, and how data tables are set up and used (database kind, setup wizard
progress, schema-change prompt choice, and migrations opted into, written, run or
rolled back), last 30 days)</li
>
<li
>feature adoption (counts of which flow, script, trigger, worker and data table
features your deployed items use, including how many apps run sandboxed, how many data
tables are configured per database kind, how many of those have migrations turned on
or off, how many migrations they carry and how many of those can be rolled back, how
many scripts and flows read or write a data table, and how many data tables a job
touched in the last 30 days)</li
tables exist per database kind, how many have migrations turned on, how many
migrations they carry, and how many scripts, flows and recent jobs use a data table)</li
>
<li
>resource counts (workspaces, scripts per language, flows, workflows as code, low-code
@@ -22,7 +22,7 @@
} from '$lib/components/OnBehalfOfSelector.svelte'
import { canUserBypassRuleKind, protectionRulesState } from '$lib/workspaceProtectionRules.svelte'
import { FRONTEND_SDK_SCOPES } from '$lib/components/raw_apps/sdkScopes'
import { logAppSandboxToggle } from './appSandboxTelemetry'
import { logFeatureUsage } from '$lib/utils/featureUsage'
const WM_DEPLOYERS_GROUP = 'wm_deployers'
@@ -301,7 +301,12 @@
checked={policy.sandbox == true}
on:change={(e) => {
policy.sandbox = e.detail || undefined
logAppSandboxToggle(rawApp ? 'raw' : 'low_code', e.detail)
// Counted where the toggle is flipped rather than where the policy is
// persisted: a not-yet-deployed app only mutates it locally, and skipping
// those would read as unused in the case where it is picked up front.
logFeatureUsage('app_sandbox', 'toggled', {
key: `${rawApp ? 'raw' : 'low_code'}:${e.detail ? 'on' : 'off'}`
})
// Frontend API access exists only for a sandboxed app, so turning
// isolation off drops the declared scopes with it rather than leaving
// them set but inert.
@@ -1,17 +0,0 @@
import { logFeatureUsage } from '$lib/utils/featureUsage'
// Anonymous counters for the take rate of the alpha sandbox-isolation toggle. Same rules as
// every other `logFeatureUsage` caller: aggregated counts only, and the four keys below are
// the whole vocabulary — no app path, policy or scope ever reaches here.
/** Which editor the toggle was flipped in, since the two app kinds adopt it independently. */
export type AppSandboxKind = 'low_code' | 'raw'
/**
* Counted where the user flips the toggle, not where the policy is persisted: a not-yet-deployed
* app only mutates its policy locally, and dropping those would count the toggle as unused in
* exactly the case where it is picked up front.
*/
export function logAppSandboxToggle(kind: AppSandboxKind, enabled: boolean): void {
logFeatureUsage('app_sandbox', 'toggled', { key: `${kind}:${enabled ? 'on' : 'off'}` })
}
@@ -25,7 +25,7 @@
import { sendUserToast, type Item } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
import { getToolNameError } from '$lib/components/flows/agentToolUtils'
import { logStepScriptEdit } from '$lib/components/flows/stepScriptEditTelemetry'
import { logFeatureUsage } from '$lib/utils/featureUsage'
import autosize from '$lib/autosize'
interface Props {
@@ -110,11 +110,11 @@
// this a no-op — and a no-op must not be counted as an editor open.
const drawer = $scriptEditorDrawer
if (!drawer) return
logStepScriptEdit('opened')
logFeatureUsage('flow_step', 'script_edit', { key: 'opened' })
// The drawer only runs this callback once a new version is deployed, so it is
// what separates opening the editor from actually editing the script here.
drawer.openDrawer(hash, () => {
logStepScriptEdit('saved')
logFeatureUsage('flow_step', 'script_edit', { key: 'saved' })
dispatch('reload')
sendUserToast('Script has been updated')
})
@@ -1,15 +0,0 @@
import { logFeatureUsage } from '$lib/utils/featureUsage'
// Anonymous counters for editing a workspace script's code without leaving the flow editor.
// Same rules as every other `logFeatureUsage` caller: aggregated counts only, and the two keys
// below are the whole vocabulary — no script path, hash, language or code ever reaches here.
export type StepScriptEditEvent =
/** The drawer was opened on the step's script. */
| 'opened'
/** A new version of that script was deployed from the drawer. */
| 'saved'
export function logStepScriptEdit(event: StepScriptEditEvent): void {
logFeatureUsage('flow_step', 'script_edit', { key: event })
}