How frappe/pulse works
1. What this repo is
- pulse is a Frappe app that collects product telemetry and turns it into persisted Pulse Event records for analysis.
- It sits at the boundary between event producers (browser and framework telemetry clients) and Frappe’s database-backed data model.
2. Core ingest pipeline
- The primary flow is: client-side telemetry posts directly to Pulse → Pulse buffers events in Redis → a consumer drains Redis and writes Pulse Event rows in MariaDB/Frappe.
- This separates high-volume ingest from durable storage and keeps the browser-facing request path lightweight.
3. Client-side telemetry boundary
- pulse/public/js/pulse_client.js is the browser SDK served from the app’s asset pipeline.
- The README indicates the browser loads it from /assets/pulse/js/pulse_client.js and posts events directly to the Pulse host, not through an intermediate framework relay.
4. Server-side API surface
- pulse/api/ is the likely HTTP entry point for ingest and related operations, with the README explicitly referencing bulk_ingest.
- This API is the public boundary for event submission; it is where API key validation, CORS behavior, and request shaping matter.
5. Redis-backed buffering layer
- The redis_stream doctype family (Redis Stream, Redis Stream Consumer, Redis Stream Entry) models the queueing/buffering layer in Frappe terms.
- Architecturally, this is the “shock absorber” between transient incoming traffic and durable event storage.
6. Persistence and domain model
- Pulse Event is the main fact table for analytics.
- Supporting doctypes such as Pulse Alias, Pulse Person, Pulse Log, and Pulse Settings capture identity, metadata, operational logs, and configuration, respectively.
7. Settings and enablement
- Pulse Settings controls runtime behavior, including API key matching and feature toggles referenced in the README.
- The app also relies on site config values like pulse_host, pulse_api_key, and allow_cors, so deployment configuration is part of the architecture, not just app logic.
8. Framework integration
- pulse/hooks.py, pulse/constants.py, and pulse/patches/ indicate standard Frappe integration points for app lifecycle, configuration, and schema/data migrations.
- The README’s mention of pulse_force_enabled shows there is an explicit test-only override path for local/e2e setups.
9. E2E and test harness
- pulse/e2e.py provides operational helpers for test setup, draining queues, counting ingested rows, and cleanup.
- End-to-end coverage spans browser client behavior and server ingest, which is important because the system’s correctness depends on the full pipeline, not just one service.
10. Automation and external validation
- Cypress tests under cypress/ validate browser-to-Pulse telemetry flow.
- pulse-k6.test.js suggests load/performance validation exists for ingest behavior, and GitHub Actions (.github/workflows/e2e.yml) runs these checks in CI.
11. Non-runtime/supporting pieces
- pulse/utils/simulator.py and the tests/ directory support local simulation and unit-level verification.
- These are useful for reproducing event flows without needing a full interactive product site.
12. How to draw the architecture
- A good diagram would show: external app/browser → Pulse JS client or Frappe telemetry client → Pulse ingest API → Redis stream buffer → Redis consumer → Frappe/MariaDB Pulse Event storage.
- Side boxes should include config/settings, identity/log doctypes, migration patches, and the CI/e2e harness that exercises the same flow end to end.
Main entry points: api Ingest API · pulse_event.py Pulse Event · redis_stream.py Redis Stream · e2e.py E2E Helpers
Components 18
Telemetry clients
Browser-side telemetry entry points and supporting helpers.
- Browser SDK · Assets-served client that posts telemetry directly to Pulse.
pulse/public/js/pulse_client.js
Pulse app
Server-side ingest, buffering, and persistence inside the Frappe app.
- Ingest API · Public server boundary for bulk ingest and related operations.
pulse/api - Redis Stream · Frappe-backed buffering layer between ingest traffic and durable storage.
pulse/pulse/doctype/redis_stream/redis_stream.py - Stream Consumer · Drains buffered events and converts them into persisted records.
pulse/pulse/doctype/redis_stream_consumer/redis_stream_consumer.py
Domain model
Primary doctypes for events, identity, logs, and settings.
- Pulse Event · Primary analytics record stored in MariaDB/Frappe.
pulse/pulse/doctype/pulse_event/pulse_event.py - Pulse Settings · Runtime configuration and feature toggles for the app.
pulse/pulse/doctype/pulse_settings/pulse_settings.py - Pulse Person · Identity mapping for tracked users/persons.
pulse/pulse/doctype/pulse_person/pulse_person.py - Pulse Alias · Alias mapping used to connect identities across events.
pulse/pulse/doctype/pulse_alias/pulse_alias.py - Pulse Log · Operational log records for the telemetry system.
pulse/pulse/doctype/pulse_log/pulse_log.py
Lifecycle and ops
Framework hooks, migrations, and auth/config helpers.
- Auth Helpers · Helpers for anonymous access and request-side validation.
pulse/anon.py - Hooks · App lifecycle and framework integration points.
pulse/hooks.py - Constants · Shared application constants and configuration values.
pulse/constants.py - Migrations · Data and schema migration scripts for deployed sites.
pulse/patches
Validation
End-to-end, load, and CI coverage for the ingest pipeline.
- E2E Helpers · Utilities for setup, queue draining, counting, and cleanup.
pulse/e2e.py - Browser Tests · Cypress coverage for browser-to-Pulse telemetry flow.
cypress/integration/pulse_telemetry.js - Load Test · K6-based ingest and throughput validation.
pulse-k6.test.js - CI Workflow · Automation that runs end-to-end validation in GitHub Actions.
.github/workflows/e2e.yml - Simulator · Local event-flow simulator for reproducing telemetry behavior.
pulse/utils/simulator.py
Connections 21
- Browser SDK → Ingest API: posts events · Browser telemetry is sent directly to the Pulse ingest API.
- Ingest API → Auth Helpers: validates · API requests depend on request-side auth and allow/deny behavior.
- Ingest API → Redis Stream: buffers · Accepted events are written into Redis-backed stream storage.
- Redis Stream → Stream Consumer: drains to · Consumers read queued stream entries for durable processing.
- Stream Consumer → Pulse Event: persists · Consumed events are materialized as Pulse Event rows.
- Pulse Event → Pulse Person: joins identity · Event facts attach to person records for analysis.
- Pulse Event → Pulse Alias: resolves aliases · Identity aliases help unify related telemetry sources.
- Pulse Event → Pulse Log: emits ops · Operational activity is captured in Pulse Log records.
- Pulse Settings → Ingest API: configures · Settings influence ingest behavior and feature toggles.
- Constants → Ingest API: shared rules · Constants centralize app-wide values used by runtime code.
- Hooks → Ingest API: registers · Frappe hooks wire the app into the framework lifecycle.
- Migrations → Pulse Event: migrates · Migration scripts update stored telemetry data and schema.
- Migrations → Redis Stream: migrates · Patches also cover queue-related model transitions.
- E2E Helpers → Redis Stream: drains · Test helpers clear queued stream data during setup and teardown.
- E2E Helpers → Pulse Event: counts · Helpers verify the number of ingested durable rows.
- Browser Tests → Browser SDK: exercises · Browser tests validate the client-side telemetry path.
- Browser Tests → Ingest API: hits · Tests cover the server ingest boundary end to end.
- Load Test → Ingest API: stresses · Performance tests measure ingest behavior under load.
- CI Workflow → Browser Tests: runs · CI executes browser-to-server e2e validation.
- CI Workflow → Load Test: runs · CI also runs load/performance checks.
- Simulator → E2E Helpers: drives · The simulator supports reproducible ingest scenarios in tests.