Skip to content

Releases: pug-sh/pug

v0.0.21

Choose a tag to compare

@poluruprvn poluruprvn released this 30 Aug 07:36
d39640b

Bots are excluded on profiles and activity too. v0.0.20 kept tagged traffic out of insights; the same include_bots toggle — default off — now reaches the activity feed, event explorer, heatmap and profile reads. A crawler stops listing as a person, and an identified profile's summary stops counting its tagged events (and says so with a new bot flag).

Upgrading: one ClickHouse migration (013) — run migrate-clickhouse before rolling the workers. It is deliberately not backfilled, so roughly a day of traffic between the v0.0.19 and v0.0.21 deploys keeps listing its crawlers. No Postgres or NATS migrations, no config changes.

v0.0.20

Choose a tag to compare

@poluruprvn poluruprvn released this 29 Aug 17:39
db1c13c

Bots are now excluded by default

Eight tags since the last release note (v0.0.13 → v0.0.20), one note.

Bots are tagged at ingest and excluded by default. Headless browsers, synthetic monitors and scrapers on datacenter IPs are recognised by user agent — and by ASN once you add a Cloudflare Transform Rule — and tagged with $bot / $bot_reason. Nothing is dropped, so a breakdown by $bot_reason still works; every insight and metric just leaves them out unless the query sets include_bots. Your numbers will move by however much has been tagged since v0.0.19.

Attribution stops crediting Google for its sign-in page. An OAuth bounce through accounts.google.com is a visitor who was already on your site, so it books Direct now, not Organic Search. Only Google's real search subdomains count as search, and webmail referrers book Email.

Requests are capped at 8 MiB. Real event batches are 1–4 MiB, so this should touch nobody; anything larger is refused with ResourceExhausted.

Everything else is Go 1.26.6 and CI.

Upgrading: one ClickHouse migration (012) — run migrate-clickhouse before rolling the workers, or inserts fail on the new columns. No Postgres or NATS migrations, no config changes. Channel values written before these deploys keep the old rules.

v0.0.12

Choose a tag to compare

@poluruprvn poluruprvn released this 21 Aug 03:46
e074a68

Three releases' worth of notes — v0.0.10 and v0.0.11 never got their own.

Maps — v0.0.10

A new insight type that measures any metric per country, so you can see where traffic comes from on a map instead of in a list. Every country with traffic is returned; there's no top-10 cutoff to pick. This shipped as the API only — the map in the dashboard comes separately. Countries Pug can't confirm are dropped, so the rows won't sum to your project total.

Stream replicas are a deployment setting — v0.0.11

Self-hosters only: how many copies Pug keeps of each message stream now comes from NATS_STREAM_REPLICAS instead of being baked into the image. It defaults to 1, which is what single-node installs already ran, so nothing changes unless you set it. On a NATS cluster, set it to your cluster size and re-run the migrate job — the job refuses obviously wrong values rather than quietly leaving your streams unreplicated.

Identity resolution in insights — v0.0.12

One person usually shows up in Pug under more than one ID — first as an anonymous visitor, then under the ID your app gives them once they sign in. Funnels and retention used to count those as two separate people; now Pug stitches them back together at query time.

Your numbers will move when you upgrade. Conversion rates, time-to-convert and retention curves go up for any project that calls identify() — the old numbers were splitting one person in two. Trends and unique-visitor counts are deliberately left alone, and visitors who declined cookies are never stitched to anyone. Upgrading is just the images: no migrations, no config changes, no API changes.

v0.0.9

Choose a tag to compare

@poluruprvn poluruprvn released this 14 Aug 11:42
cdb6a68

Event usage metering

You can now see how many events an org has sent this month. Pug counts them on a schedule and stores the totals, so the dashboard reads a single stored number instead of running an analytical query every time someone opens the page. Alongside the headline total you get a per-project daily breakdown, and the whole team can see it — viewers included, since the person who notices a spike is rarely an admin. (#69)

This is counting only. There are no plans, quotas or limits, nothing is billed, and no event is ever rejected, throttled or delayed because of a count — ingestion never looks at these numbers.

Setting it up — please read

The counting does not start on its own. Neither pug server nor any worker schedules it. Upgrade and stop there and usage reads as unknown forever, not as a number.

Run the new cron-usage image on a schedule — hourly is what it is designed for:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: pug-cron-usage
spec:
  schedule: "0 * * * *"
  # The pass takes a Postgres advisory lock, so an overrun cannot double-count.
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 600
  jobTemplate:
    spec:
      backoffLimit: 1
      # Backstop for a pod wedged past the pass's own 30m timeout.
      activeDeadlineSeconds: 3000
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: pug-cron-usage
              image: ghcr.io/pug-sh/pug-cron-usage:0.0.9
              # Reads ClickHouse, writes Postgres. No NATS, no app secrets.
              envFrom:
                - configMapRef: { name: pug-config }
                - secretRef: { name: pug-db }

It needs the same database configuration as the migrate jobs and nothing else. Each run recounts the last couple of days to pick up late-arriving events, re-totals every org's current month, and exits; once a day it widens to recount the whole month. Overlapping runs are safe, and a failed run exits non-zero so your scheduler shows it as failed.

PUG_USAGE_RESCAN_DAYS (default 2) controls how many trailing days each run recounts. That is the only setting.

Reading the number

The API deliberately distinguishes "never counted" from "counted, and it really is zero". If the job has never run, the timestamp comes back absent and the count is omitted entirely — render that as unknown, never as 0. There is a third state too: just after a month rolls over, you get a timestamp with no count yet, which means "counting".

Usage is only as fresh as the schedule you chose, so show the last-updated timestamp beside the number rather than implying it is live. Everything is counted in UTC, which can differ at the edges from a project's own reporting timezone in insights — they answer different questions.

Counts move down as well as up: erasing a data subject's events lowers the total on the next run rather than leaving it frozen. And a run that cannot trust what it read leaves the timestamp to go stale instead of writing a number it never verified, so a stale timestamp is the signal worth alerting on.

Upgrading

  • One new migration, applied by the usual migrate-postgres step. It adds new tables only — nothing existing changes shape.
  • No breaking changes. No API or wire changes to anything that already existed, and no dashboard changes are required to upgrade.
  • Metering is optional. Skip the scheduled job entirely and everything else works exactly as before.

docs/architecture/usage.md is the full reference, including what is counted and the known imprecisions.

v0.0.8

Choose a tag to compare

@poluruprvn poluruprvn released this 11 Aug 12:11
f63f2ef

Configurable OIDC sign-in

"Sign in with an outside account" used to mean Google, and only Google. Now you can point Pug at your own identity provider — Okta, Keycloak, Microsoft Entra, Auth0, your company's self-hosted login — and your team signs in with the accounts they already have. List the providers in a JSON file and each one becomes a button on the sign-in page. (#65)

Sign-in also moved to the server: the browser hands Pug a one-time code and Pug trades it for the user's identity, so your provider's client secret never reaches the browser. Returning users match on their verified email; new ones get an account with its default org and project, exactly as magic-link sign-in already did.

Setting it up

Point PUG_CONFIG_FILE at a JSON file mounted in the server container. config.example.json is a working example to copy, and docs/authentication.md covers the rest, including the redirect URL to register with your provider. If anything in the file is wrong, Pug refuses to start and names the entry at fault.

Each provider needs a short id, and it is permanent — Pug stores it against every account that signs in through that provider, so renaming it later disconnects them. Change displayName if you just want different wording on the button.

Upgrading — please read

  • Upgrade the dashboard at the same time as the server. The sign-in request changed shape, so an older dashboard's Google button will fail against this server. Password and magic-link sign-in are unaffected.
  • PUG_OAUTH_GOOGLE_CLIENT_ID no longer does anything. Upgrade without setting up the new config file and Pug starts normally with no outside sign-in buttons at all — Google disappears until you configure it. The startup logs warn about this.
  • Name the Google entry "id": "google". That is the id your existing Google users are stored under, so they carry over with nothing to do. Any other id still signs them in, by matching their verified email, but leaves a duplicate record behind.

No database migration, and nothing to run before or after deploying.

Also in this release

  • More honest monitoring. A ClickHouse query that failed partway through reading its results, or an insert that failed while sending, was recorded as successful in our traces. Both now show up as the failures they are. (#67)
  • Updates to 20 Go dependencies, including the ClickHouse driver. (#67)

Compatibility

Nothing to change in your SDKs or tracking code. This release is all on the sign-in side — event collection and existing data are untouched.

Contributors

Full Changelog: v0.0.7...v0.0.8

v0.0.6

Choose a tag to compare

@poluruprvn poluruprvn released this 07 Aug 03:42
eddb44e

Tile legend options

Dashboard tiles can now place their legend at the top, bottom, or right, and pie tiles can hide the labels drawn inside their slices.

VisualizationOptions gains legend_position and hide_pie_labels. Both are stored on the tile and rendered client-side, so there is no migration and no change to how a tile is queried. The wire change is additive — no client upgrade is required. (#66)

Also: the README screenshots are refreshed for the current UI, in light and dark. (#64)

Full Changelog: v0.0.5...v0.0.6

v0.0.5

Choose a tag to compare

@poluruprvn poluruprvn released this 03 Aug 08:31
90c9821

Cookieless identity

Visitors who decline cookies are now countable — without anyone being persistently identified.

When a visitor rejects consent, the client stores nothing on the device and sends no identity at all. The server instead derives an ephemeral id from transport facts the request already carries:

cookieless-<base64url(HMAC-SHA256(salt_of_the_day, project ‖ ip ‖ user_agent))>

IP and user agent are hash inputs only — never stored, never returned by any API. The salt rotates daily and is held only in memory with a TTL; once it expires, that day's hashes are unlinkable to any IP or user agent by anyone, including us. Sessions are stitched server-side on a 30-minute inactivity window.

What this changes in your analytics

Cookieless ids rotate at midnight, so counting them as people would inflate your numbers. InsightQuerySpec gains an include_cookieless toggle, default off:

Cookieless traffic
Unique users, per-user average excluded by default
Funnel, retention, user flow, top-K by user excluded by default
Total event counts always counted
All session metrics always counted
Sum / avg / min / max aggregations always counted

Flip the toggle on when you want the full picture and accept that a returning visitor counts once per day. Both toggle states are served from the pre-aggregated rollup, so neither is the slow path.

Migration

ClickHouse migration 011 adds a rollup key column. No backfill — it applies to an existing table in place and runs as an ArgoCD PreSync hook ahead of the rollout.

Also in this release

  • Invite controls — resends are capped at 10 per rolling 24h window (counting the original), and admins can now revoke a pending invitation. Revoking hard-deletes the record and invalidates any link already sitting in the invitee's inbox, which makes it the way to fix a typo'd address or a wrong role. (#59)
  • Email idempotency — dispatch is keyed per send, so a retried delivery can no longer double-send, and provider-side idempotency conflicts are acknowledged rather than dead-lettered. (#59)
  • Pie chart tiles — a new dashboard tile view mode. (#60)
  • Ingestion observability — batches now report dropped events with a per-reason breakdown, so a partial failure is visible and actionable instead of silent.
  • Dependency updates across Go modules and CI actions. (#53, #54, #56, #57, #58, #62)

Compatibility

No client upgrade is required. Existing SDKs keep working unchanged — the wire changes are additive, and every SDK uses the Connect binary codec, which ignores fields it doesn't know. Cookieless mode itself needs a forthcoming SDK release to opt into; until then this release is a no-op for existing traffic.

One thing to know if you mint your own identifiers: distinct_id and external_id may no longer begin with cookieless-, which is now a reserved server-owned prefix. A batch containing such an id is rejected.

New contributors

Full Changelog: v0.0.4...v0.0.5

v0.0.4

Choose a tag to compare

@poluruprvn poluruprvn released this 18 Jul 21:55
379717f

Web analytics

v0.0.3

Choose a tag to compare

@poluruprvn poluruprvn released this 16 Jul 01:18
12c3d5c

What's Changed

  • docs: remove stale docs/compliance references by @poluruprvn in #42
  • chore(deps): bump the go-minor-patch group with 11 updates by @dependabot[bot] in #43
  • chore(deps): bump buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go from 1.36.11-20260415201107-50325440f8f2.1 to 1.36.11-20260709200747-435963d16310.1 by @dependabot[bot] in #44
  • chore: remove TODO files by @poluruprvn in #46
  • feat(mcp): add read-only MCP analytics server at /mcp by @poluruprvn in #45
  • feat(auth): raise access token TTL to 24h by @poluruprvn in #48
  • feat(projects): move API keys into a first-class api_keys table by @poluruprvn in #49

Full Changelog: v0.0.2...v0.0.3

v0.0.2

Choose a tag to compare

@poluruprvn poluruprvn released this 10 Jul 10:27
a440c8b

What's Changed

Full Changelog: v0.0.1...v0.0.2