Skip to content

Tour Analytics

The analytics panel in the admin overlay provides real-time visibility into how users engage with tours and content. It surfaces completion rates, step-level engagement, drop-off analysis, and selector health metrics.

Data Architecture

Analytics data is stored in two DynamoDB tables using a two-tier rollup strategy:

Events.Interactions (Raw Events)

Append-only log of individual user interaction events. Each event has a 90-day TTL.

PK: tenant_id#content_id     (e.g. "tenant_abc#tour_123")
SK: timestamp#event_id       (e.g. "2025-05-10T14:22:00Z#uuid")

Events.Aggregates (Pre-computed Counters)

Hourly and daily counters maintained atomically on every interaction:

PK: tenant_id#content_id     (e.g. "tenant_abc#tour_123")
SK: date_metric              (e.g. "2025-05-10#views" or "2025-05-10T14#completions")

The sort key format determines granularity:

  • Daily: YYYY-MM-DD#metric -- used for trend analysis (weekly/monthly views).
  • Hourly: YYYY-MM-DDTHH#metric -- used for fine-grained dashboards (last-24-hour view).

Both counters are incremented atomically on every interaction using DynamoDB ADD operations -- no batch jobs or delayed processing.

Tracked Metrics

The following events are captured and aggregated:

Tour Events

EventMetric NameDescription
TourStartedstartsUser began the tour
StepViewedstep_viewsUser saw a specific step
StepCompletedstep_completionsUser advanced past a step
TourCompletedcompletionsUser finished all steps
TourDismisseddismissalsUser closed the tour early
TourRestartedrestartsUser restarted the tour

Content Events

EventMetric NameDescription
ContentViewedviewsUser saw the content
ContentDismisseddismissalsUser dismissed the content
ContentCtaClickedcta_clicksUser clicked the call-to-action

Targeting Events

EventMetric NameDescription
target_not_foundtarget_failuresSDK could not locate the target element

Completion Rate

The primary metric for tour effectiveness. Calculated as:

completion_rate = completions / starts * 100

The analytics panel displays this as a percentage with a trend indicator showing change over the selected time period.

Drop-off Analysis

The step-level funnel view shows where users abandon tours:

Step 1: "Welcome"          ████████████████████  100% (200 users)
Step 2: "Navigate to..."   ██████████████████    90%  (180 users)
Step 3: "Click the..."     ████████████████      80%  (160 users)
Step 4: "Configure..."     █████████             45%  (90 users)  <-- drop-off
Step 5: "Complete"         ███████               35%  (70 users)

The step with the largest percentage drop is highlighted to identify where the tour needs improvement. Common causes:

  • The target element is not visible (lazy-loaded, behind a tab, requires scrolling).
  • The step instructions are unclear.
  • The step requires an action the user is not ready to take.

Querying Analytics

Daily Aggregates

bash
curl -H "X-API-Key: sk_live_..." \
     -H "Authorization: Bearer <jwt>" \
     "https://api.example.com/v1/admin/analytics/tours/tour_123?date_from=2025-05-01&date_to=2025-05-10"
json
{
  "data": {
    "tour_id": "tour_123",
    "period": {
      "from": "2025-05-01",
      "to": "2025-05-10"
    },
    "totals": {
      "starts": 1250,
      "completions": 875,
      "dismissals": 312,
      "completion_rate": 70.0
    },
    "daily": [
      {
        "date": "2025-05-01",
        "starts": 150,
        "completions": 105,
        "dismissals": 38,
        "completion_rate": 70.0
      }
    ]
  }
}

Hourly Aggregates

For fine-grained views (last 24 hours):

bash
curl -H "X-API-Key: sk_live_..." \
     -H "Authorization: Bearer <jwt>" \
     "https://api.example.com/v1/admin/analytics/tours/tour_123?date=2025-05-10&granularity=hourly"
json
{
  "data": {
    "tour_id": "tour_123",
    "date": "2025-05-10",
    "granularity": "hourly",
    "hours": [
      { "hour": "00", "starts": 2, "completions": 1, "dismissals": 1 },
      { "hour": "01", "starts": 0, "completions": 0, "dismissals": 0 },
      { "hour": "09", "starts": 45, "completions": 32, "dismissals": 10 },
      { "hour": "10", "starts": 62, "completions": 44, "dismissals": 15 }
    ]
  }
}

Activity Feed

The Activity.UserEvents table stores a per-user event log with three access patterns:

GSIQuestion AnsweredKey Structure
Primary key"What has this user been doing?"PK: tenant_user_id, SK: timestamp_event_id
gsi-content"Who interacted with this tour?"PK: tenant_content_id, SK: timestamp
gsi-session"What happened in this session?"PK: tenant_session_id, SK: timestamp

Activity events have a configurable TTL (default: 90 days) to manage storage costs.

Admin Panel Views

The analytics panel in the admin overlay provides four views:

Overview Dashboard

Aggregate metrics across all tours and content for the tenant:

  • Total active tours and content items
  • Combined completion rate with trend
  • Top-performing tour (highest completion rate)
  • Tour with highest drop-off (needs attention)

Tour Detail View

Per-tour metrics with step-level funnel:

  • Completion rate with daily trend chart
  • Step-by-step funnel with drop-off markers
  • User count by status (completed, in-progress, dismissed)
  • Recent activity feed

Selector Health View

Aggregated targeting failure report (see Element Selector):

  • Total failures in the analysis window
  • Broken selectors ranked by failure count
  • Drill-down to individual failure events per content item

Export

Analytics data can be exported via the admin API for integration with external BI tools. The raw Events.Interactions table data is queryable with date range filters.

Event Processing Pipeline

User Interaction (SDK)
        |
        v
POST /v1/sdk/events
        |
        v
Events.Outbox table (transactional write)
        |
        v
Outbox Processor (polls pending events)
        |
        v
EventBridge (spotlight-events bus)
        |
        +---> Activity Handler --> Activity.UserEvents
        |
        +---> Analytics Handler --> Events.Interactions
        |                          Events.Aggregates
        |
        +---> Audit Handler --> Audit.AdminActions (admin events only)

In local development, the outbox processor runs as a FastAPI background task (configured via ENABLE_LOCAL_PROCESSOR=true) instead of a separate Lambda function, polling every LOCAL_PROCESSOR_INTERVAL seconds.

In production, DynamoDB Streams on the outbox table trigger a Lambda function that publishes events to EventBridge, which routes them to the stream processor, analytics processor, and any configured webhook targets.

Spotlight