Analytics
Gets you the data. What to do with it is your call — read the question and answer it, rather than producing a fixed report.
Prerequisites
scripts/export_analytics.py calls load_dotenv(), so a .env at the repo root
supplies: BASE_URL, AUTH_SECRET, AUTH_NAME, AUTH_EMAIL, APP_KEY, and
optionally APTABASE_REGION (defaults to SH).
Export
Dates are UTC and day-granular: --start-date inclusive, --end-date exclusive.
For a window in hours, export the days that overlap it and trim afterwards.
uv run scripts/export_analytics.py \
--start-date YYYY-MM-DD --end-date YYYY-MM-DD \
--output scripts/analytics_raw.csv
Also takes --format csv|parquet and --build-mode release|debug (default
release — debug rows are developer machines).
Columns
| Column | |
|---|---|
timestamp |
UTC |
event_name |
see below |
user_id |
anonymous |
app_version |
|
os_name / os_version |
e.g. Windows, macOS, Linux Mint |
string_props |
JSON; failures carry error_message with the full string |
numeric_props |
JSON |
The event set
Read it from the code, never from memory — names change:
desktop/src-tauri/src/analytics.rsdesktop/src/lib/analytics.ts
The shapes are <action>_started → <action>_succeeded / <action>_failed, and
<component>_<failure_type> for infrastructure faults. Work out which of the
current names are starts, successes and failures before computing any rate.
Trimming to an exact window
uv run --with pandas python - <<'PY'
from datetime import timedelta
import pandas as pd
df = pd.read_csv("scripts/analytics_raw.csv")
df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True)
df = df[df["timestamp"] >= df["timestamp"].max() - timedelta(hours=48)]
df.to_csv("scripts/analytics_window.csv", index=False)
print(len(df), df["timestamp"].min(), df["timestamp"].max())
PY
From here, inline pandas passes over the CSV answer most questions. A few things worth knowing when you interpret them: a handful of users can repeat the same input mistake and dominate an event-level error rate, so per-user counts often say more than raw ones; and a failure rate is only meaningful against its own start event, when that lifecycle is present in the data.