A React + Electron application for validating analytics event implementations against Excel-based tracking plans. Testers run bounded QA sessions that poll a Satori identity in real time, automatically matching observed events and properties against expected definitions, and producing exportable pass/fail reports for regression sign-off.
- Overview
- How It Works
- Features
- Architecture
- File Structure
- Setup & Usage
- Excel Tracking Plan Format
- API Integration
- Development
QA and product teams need a reliable way to verify that analytics events fire correctly during regression testing. Manual validation is fragmented and error-prone. This tool automates the process by:
- Importing an Excel tracking plan that defines expected events, event metadata properties, and identity properties
- Polling the Satori API for a specific identity to capture live events
- Auto-matching received events against the expected checklist in real time
- Tracking identity and event metadata properties for completeness
- Generating a pass/fail report exportable as JSON or CSV
Sign in to Satori (Dev or Prod)
↓
Upload Excel Tracking Plan
↓
Enter Identity ID → Click "Start Session"
↓
Polls Satori API every 6 seconds:
• GET /v1/console/identity/{id}/event
• GET /v1/console/identity/{id}
↓
Auto-matches events against expected checklist
Tracks identity properties and event metadata
↓
Tester reviews results, flags data issues
↓
Click "Stop Session" → View Report → Export (JSON / CSV)
- Per-engineer authentication — OAuth-style login (email + password + MFA) against the engineer's chosen Satori environment. No shared API keys.
- Configurable endpoints — Each engineer types their Satori endpoint URL once and it's saved to the browser. The codebase itself contains no infrastructure URLs.
- Session management — Start, pause, resume, and stop bounded QA sessions
- Live event feed — Wide, dominant feed with pretty-printed JSON payloads, filter chips (All / Expected / Unexpected / Hide property syncs), and expand-all toggle
- Tabbed inspector — Expected events, identity properties, and event properties all share a single tabbed pane on the right
- Consolidated stat bar — Three summary cards at the top show pass/fail/pending and completion percent for each tracking dimension
- Event checklist — Expected events auto-check as they arrive; manually mark missing events as failed with notes
- Identity properties tracking — Monitors identity properties (default, custom, computed) against expectations; tracks value changes over time
- Event metadata properties tracking — Validates event metadata properties across all received events
- Property flagging — Flag incorrect event metadata values with expected-value annotations
- Auth-lost recovery — If a token expires mid-session, an overlay prompts for re-login and resumes the QA session with all in-flight state intact
- Status filtering — Filter checklist items by Excel status (e.g., "Active", "In Progress", "Done")
- Report generation — Summary overlay with overall verdict (PASS / FAIL / INCOMPLETE), progress bars, and detailed tables
- Export — Download reports as JSON or CSV; copy summary to clipboard
- Resilient ingestion — Defensive parsing layer drops malformed payloads, coerces non-string property values to safe strings, and pretty-prints stringified-JSON values (including doubly-stringified Flutter SDK payloads). Per-panel error boundaries contain rendering failures.
- Electron support — Runs as a desktop app via Electron or as a standard web app
| Layer | Technology |
|---|---|
| Framework | React 19 + TypeScript 5.8 |
| Build | Vite 7 |
| State Management | Zustand 5 |
| Routing | React Router 7 |
| Excel Parsing | SheetJS (xlsx) |
| Desktop | Electron 35 + electron-builder |
Three Zustand stores drive the application:
authStore— Auth lifecycle (unauthenticated/authenticated/lost). Token + refresh token live inservices/auth.tsmodule memory, never persisted. The store exposes aloststate distinct fromunauthenticatedso the UI can preserve QA session data while prompting for re-login.qaSessionStore— Session lifecycle (idle→running→completed), event checklist, identity property checklist, event metadata property checklist, received events, unexpected events/properties, deduplication, and session summary computationtrackingPlanStore— Parsed Excel tracking plan data (MasterJson)
- No persisted credentials — Access and refresh tokens live in memory only. A page reload or app relaunch returns the engineer to the login screen. The security stance is: tokens that don't exist on disk can't be stolen from a lost/borrowed machine.
- No infrastructure in the bundle — Satori endpoint URLs are entered by the engineer at login and saved to their browser's localStorage. The compiled bundle contains no URLs, no keys, no tenant identifiers — safe to host publicly. Optional
VITE_SATORI_{DEV,PROD}_BASEenv vars can pre-fill the field for trusted internal builds. - Polling over WebSockets — Uses a 6-second polling interval against the Satori console API. No instrumentation changes needed in the app under test.
- Token-based auth with auto-refresh — Bearer tokens attached on every request. On 401, the http layer attempts a single inline refresh and retries; on refresh failure it flips
authStore.statustolost, which pauses polling and surfaces the re-login overlay without unmounting any QA state. - Event deduplication — A
Set<string>of seen event IDs prevents double-counting across poll cycles. - Time-window filtering — Only events after the session start time are processed.
- Defensive ingestion — Polling rejects events without a string
nameand normalizesmetadatato a plain object before storing. Identity property values are coerced to strings via JSON.stringify so nested API responses can't crash the React tree. - Flexible Excel parsing — Column name matching is case-insensitive, ignores punctuation, and scores header rows to find the best match.
- Per-panel error boundaries — Each major panel is wrapped so a single malformed payload contains the failure to one pane instead of blanking the app.
src/
├── pages/
│ └── qa-validation.tsx # Main page layout (AuthGate + feed-dominant 2-column grid)
├── components/
│ ├── auth/
│ │ ├── AuthGate.tsx # Renders LoginScreen when unauthenticated; children otherwise
│ │ ├── LoginScreen.tsx # Full-page sign-in with env toggle + endpoint URL field
│ │ └── AuthLostOverlay.tsx # Mid-session re-login modal that preserves QA state
│ ├── qa/
│ │ ├── QASessionHeader.tsx # Session controls, identity input, status cluster, sign-out
│ │ ├── StatBar.tsx # Three summary cards (Events / Identity / Event Props)
│ │ ├── InspectorTabs.tsx # Tab switcher for the three checklist panels
│ │ ├── EventFeedPanel.tsx # Live event stream with filter chips + expand-all
│ │ ├── ChecklistPanel.tsx # Expected events tracker (pass/fail/pending)
│ │ ├── IdentityPropsPanel.tsx # Identity properties checklist + change history
│ │ ├── EventPropsPanel.tsx # Event metadata properties tracker
│ │ ├── QAReportOverlay.tsx # Report modal with export options
│ │ └── PollRing.tsx # Animated polling countdown indicator
│ ├── ui/
│ │ ├── Button.tsx # Button variants (Primary, Secondary, Success, Danger)
│ │ ├── Badge.tsx # Status badges
│ │ ├── Card.tsx # Card containers
│ │ ├── Table.tsx # Table components
│ │ └── index.ts # UI barrel exports
│ ├── PanelErrorBoundary.tsx # Contains render-time exceptions to a single panel
│ ├── excel_drop_zone.tsx # File upload for .xlsx/.xls tracking plans
│ ├── stat.tsx # Statistics display card
│ └── toast.tsx # Toast notification component
├── hooks/
│ ├── useQAPolling.ts # Polling loop (6s interval, auth-aware pause/resume)
│ ├── useToast.ts # Toast notification hook
│ └── index.ts
├── store/
│ ├── authStore.ts # Auth lifecycle (unauthenticated / authenticated / lost)
│ ├── qaSessionStore.ts # QA session state + actions
│ ├── trackingPlanStore.ts # Excel tracking plan state
│ └── index.ts
├── services/
│ ├── auth.ts # Login, refresh, logout. In-memory token state only.
│ ├── environment.ts # Per-env URL resolution (localStorage + optional env-var pre-fill)
│ ├── satori.ts # API methods (getIdentityEvents, getIdentity)
│ └── http.ts # Bearer-token fetcher with 401 → refresh → retry / auth-lost
├── excel/
│ └── masterJson.ts # Excel parsing + normalization logic
├── types/
│ ├── qa.ts # QA types (ChecklistItem, PropertyChecklistItem, etc.)
│ └── master.ts # Excel types (ExcelEvent, ExcelProperty, etc.)
├── utils/
│ ├── qaExport.ts # Session export to JSON
│ ├── qaReportExport.ts # Report generation, CSV export, clipboard copy
│ ├── statusVariants.ts # Status string → color variant mapping
│ └── validators.ts # Validation helpers
├── App.tsx # Root component (HashRouter for Electron, BrowserRouter for web)
├── main.tsx # React entry point
└── vite-env.d.ts # Vite type declarations
- Node.js 18+
- A Satori Analytics account with login credentials (and MFA if your tenant requires it)
- The Satori API endpoint URL for the environment(s) you want to test against
- An Excel tracking plan (
.xlsx)
npm installA .env file is not required. Engineers enter their Satori endpoint URL on the login screen and it's saved per-browser to localStorage.
For internal/Electron builds you control, you can pre-fill the endpoint fields by setting:
VITE_SATORI_DEV_BASE=https://your-dev-instance.satoricloud.io
VITE_SATORI_PROD_BASE=https://your-prod-instance.satoricloud.ioEngineers can still override these values from the login screen if needed. Public/CI builds should leave them unset so the bundle contains no infrastructure references.
Web (development)
npm run devElectron (development)
npm run electron:devProduction build (web)
npm run build
npm run previewProduction build (Electron)
npm run electron:build- Sign in — Pick Dev or Prod, enter (or paste) the Satori endpoint URL, then your email + password + MFA
- Upload your Excel tracking plan using the file upload button
- Enter the identity ID you want to monitor
- Start Session — polling begins, events appear in the live feed
- Interact with the application under test to trigger analytics events
- Watch the checklist auto-check as expected events arrive
- Flag any event metadata properties that contain incorrect values
- Stop Session to freeze results
- View Report for overall verdict and detailed breakdown
- Export the report as JSON or CSV for regression evidence
If your access token expires mid-session, a re-login overlay appears — sign in again and polling resumes with all your in-flight QA data intact.
The Excel file should contain up to three sheets:
| Column | Description |
|---|---|
TRACK this event |
Event name |
WHEN |
Trigger condition |
Status |
Active / Parked / Remove |
SATORI VALUE |
Expected value type |
Feature |
Product feature |
Action |
User action |
NOTES |
Additional context |
| Column | Description |
|---|---|
TRACK this property |
Property name |
WHEN below event occurs: |
Associated event name |
Type |
Data type (String, Number, Boolean, Object) |
Status |
Active / Parked / Remove |
Description |
Property description |
Example |
Example values |
| Column | Description |
|---|---|
Property Name |
Identity property name |
WHEN |
When the property is set |
Type |
Data type |
Status |
Active / Parked / Remove |
Example |
Example value |
Description |
Property description |
Column matching is flexible — headers are matched case-insensitively with punctuation removed.
Per-engineer OAuth-style login. The login screen POSTs credentials to the configured endpoint and receives a bearer token:
POST {endpoint}/v1/console/authenticate
Content-Type: application/json
{ "email": "...", "password": "...", "mfa": "..." }
Successful response:
{ "token": "...", "refresh_token": "..." }Subsequent requests attach the access token:
Authorization: Bearer {token}
When a request returns 401, the http layer attempts a single inline refresh:
POST {endpoint}/v1/console/authenticate/refresh
{ "refresh_token": "..." }
If refresh succeeds, the original request is retried once with the new token. If refresh fails, the auth store transitions to lost, polling pauses, and the re-login overlay mounts over the existing QA UI — preserving all session data until the engineer re-authenticates.
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /v1/console/authenticate |
Login (email + password + MFA → token) |
| POST | /v1/console/authenticate/refresh |
Exchange refresh token for new access token |
| GET | /v1/console/identity/{id}/event |
Fetch events for an identity |
| GET | /v1/console/identity/{id} |
Fetch identity with properties |
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server |
npm run build |
TypeScript compile + Vite production build |
npm run preview |
Preview production build |
npm run lint |
Run ESLint |
npm run electron:dev |
Start Vite + Electron with hot reload |
npm run electron:build |
Package as distributable Electron app |
npm run electron:preview |
Preview Electron app locally |
| Package | Purpose |
|---|---|
react / react-dom |
UI framework |
react-router-dom |
Client-side routing |
zustand |
Lightweight state management |
xlsx |
Excel file parsing |
electron |
Desktop app shell |
electron-builder |
Desktop packaging |
vite |
Build tool with HMR |