[PULSE-4] Goals & Events dashboard block and settings UI #5

Merged
uz1mani merged 8 commits from staging into main 2026-02-04 15:04:10 +00:00
uz1mani commented 2026-02-04 14:27:36 +00:00 (Migrated from github.com)

Work Item

PULSE-4

Summary

  • Add Goals & Events block on the site dashboard showing custom event counts and an empty-state with setup instructions.
  • Add Goals & Events tab in site settings with list, add, edit, and delete for goals (name, event_name, optional funnel).
  • Expose pulse.track(eventName) in the snippet and document it on the installation page.

Changes

  • Dashboard: components/dashboard/GoalStats.tsx — shows goal counts from dashboard data; empty state with “Need help tracking goals?” and link to installation. app/sites/[id]/page.tsx — fetches and passes goal_counts into GoalStats. lib/api/stats.tsGoalCountStat and goal_counts on DashboardData.
  • Settings: app/sites/[id]/settings/page.tsx — new “Goals & Events” tab; state and modal for add/edit; list with edit/delete; calls to goals API. lib/api/goals.tslistGoals, createGoal, updateGoal, deleteGoal.
  • Snippet: public/script.jswindow.pulse.track(eventName) with validation (regex/length).
  • Installation: app/installation/page.tsx — instructions and example for pulse.track() and Goals & Events in settings.

Test Plan

[ ] Site dashboard shows Goals & Events block; with no custom events, empty state and doc link display.
[ ] After sending pulse.track('event_name') events, dashboard shows counts for those event names.
[ ] Settings → Goals & Events: create, edit, and delete goals; list updates and persists.
[ ] Installation page documents pulse.track() and points to Settings → Goals & Events for defining goals.

## Work Item PULSE-4 ## Summary - Add Goals & Events block on the site dashboard showing custom event counts and an empty-state with setup instructions. - Add Goals & Events tab in site settings with list, add, edit, and delete for goals (name, event_name, optional funnel). - Expose `pulse.track(eventName)` in the snippet and document it on the installation page. ## Changes - **Dashboard:** `components/dashboard/GoalStats.tsx` — shows goal counts from dashboard data; empty state with “Need help tracking goals?” and link to installation. `app/sites/[id]/page.tsx` — fetches and passes `goal_counts` into `GoalStats`. `lib/api/stats.ts` — `GoalCountStat` and `goal_counts` on `DashboardData`. - **Settings:** `app/sites/[id]/settings/page.tsx` — new “Goals & Events” tab; state and modal for add/edit; list with edit/delete; calls to goals API. `lib/api/goals.ts` — `listGoals`, `createGoal`, `updateGoal`, `deleteGoal`. - **Snippet:** `public/script.js` — `window.pulse.track(eventName)` with validation (regex/length). - **Installation:** `app/installation/page.tsx` — instructions and example for `pulse.track()` and Goals & Events in settings. ## Test Plan [ ] Site dashboard shows Goals & Events block; with no custom events, empty state and doc link display. [ ] After sending `pulse.track('event_name')` events, dashboard shows counts for those event names. [ ] Settings → Goals & Events: create, edit, and delete goals; list updates and persists. [ ] Installation page documents `pulse.track()` and points to Settings → Goals & Events for defining goals.
greptile-apps[bot] commented 2026-02-04 14:30:04 +00:00 (Migrated from github.com)

Greptile Overview

Greptile Summary

This PR implements a comprehensive goals and events tracking system for Pulse. Users can define custom goals in Settings → Goals & Events, track them via pulse.track('event_name') in their site code, and view counts in the dashboard.

Key changes:

  • Adds client-side pulse.track() API with validation (alphanumeric + underscores, max 64 chars)
  • Creates Goals & Events settings UI with CRUD operations, duplicate detection, and user warnings about event name changes
  • Integrates goal counts into dashboard with GoalStats component showing display names
  • Documents the feature on the installation page with clear examples

Architecture highlights:

  • Proper separation: client validation in script.js, server-side API via goals.ts
  • Smart display: dashboard shows friendly goal names (e.g., "Signups") via display_name field from backend
  • User guidance: clear warnings about event name transformations and historical data implications
  • Previous thread concerns have been addressed with duplicate checking and helpful UI hints

The implementation is well-structured with proper validation at multiple layers and good user experience.

Confidence Score: 4.5/5

  • PR is safe to merge with high confidence; well-structured implementation with proper validation
  • Strong implementation with validation at client and UI layers, previous concerns addressed with duplicate checking and user warnings. Minor deduction only because feature depends on backend API endpoints not visible in this PR
  • No files require special attention - all changes follow best practices

Important Files Changed

Filename Overview
public/script.js Adds pulse.track() API with proper validation for custom event tracking
components/dashboard/GoalStats.tsx Dashboard widget showing goal counts with empty state, correctly uses display_name fallback
app/sites/[id]/settings/page.tsx Adds Goals & Events tab with comprehensive validation and duplicate checking, warns about event name changes

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant PulseScript as pulse.track()
    participant API as Backend API
    participant Dashboard
    participant Settings

    Note over User,Settings: Setup Phase
    User->>Settings: Navigate to Goals & Events tab
    Settings->>API: GET /sites/{id}/goals
    API-->>Settings: Return goals list
    User->>Settings: Create goal (name: "Signups", event: "signup_click")
    Settings->>Settings: Validate & transform event name
    Settings->>API: POST /sites/{id}/goals
    API-->>Settings: Goal created
    
    Note over User,Settings: Tracking Phase
    User->>Browser: Trigger action on site (e.g., click signup)
    Browser->>PulseScript: pulse.track('signup_click')
    PulseScript->>PulseScript: Validate event name (regex, length)
    PulseScript->>API: POST /api/v1/events (with event name)
    API-->>PulseScript: Event recorded
    
    Note over User,Settings: Dashboard View
    User->>Dashboard: View site dashboard
    Dashboard->>API: GET /sites/{id}/dashboard
    API-->>Dashboard: DashboardData (includes goal_counts)
    Dashboard->>Dashboard: Render GoalStats with display names
    Dashboard-->>User: Show goal counts
<h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR implements a comprehensive goals and events tracking system for Pulse. Users can define custom goals in Settings → Goals & Events, track them via `pulse.track('event_name')` in their site code, and view counts in the dashboard. **Key changes:** - Adds client-side `pulse.track()` API with validation (alphanumeric + underscores, max 64 chars) - Creates Goals & Events settings UI with CRUD operations, duplicate detection, and user warnings about event name changes - Integrates goal counts into dashboard with `GoalStats` component showing display names - Documents the feature on the installation page with clear examples **Architecture highlights:** - Proper separation: client validation in script.js, server-side API via goals.ts - Smart display: dashboard shows friendly goal names (e.g., "Signups") via `display_name` field from backend - User guidance: clear warnings about event name transformations and historical data implications - Previous thread concerns have been addressed with duplicate checking and helpful UI hints The implementation is well-structured with proper validation at multiple layers and good user experience. <h3>Confidence Score: 4.5/5</h3> - PR is safe to merge with high confidence; well-structured implementation with proper validation - Strong implementation with validation at client and UI layers, previous concerns addressed with duplicate checking and user warnings. Minor deduction only because feature depends on backend API endpoints not visible in this PR - No files require special attention - all changes follow best practices <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | public/script.js | Adds `pulse.track()` API with proper validation for custom event tracking | | components/dashboard/GoalStats.tsx | Dashboard widget showing goal counts with empty state, correctly uses `display_name` fallback | | app/sites/[id]/settings/page.tsx | Adds Goals & Events tab with comprehensive validation and duplicate checking, warns about event name changes | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant User participant Browser participant PulseScript as pulse.track() participant API as Backend API participant Dashboard participant Settings Note over User,Settings: Setup Phase User->>Settings: Navigate to Goals & Events tab Settings->>API: GET /sites/{id}/goals API-->>Settings: Return goals list User->>Settings: Create goal (name: "Signups", event: "signup_click") Settings->>Settings: Validate & transform event name Settings->>API: POST /sites/{id}/goals API-->>Settings: Goal created Note over User,Settings: Tracking Phase User->>Browser: Trigger action on site (e.g., click signup) Browser->>PulseScript: pulse.track('signup_click') PulseScript->>PulseScript: Validate event name (regex, length) PulseScript->>API: POST /api/v1/events (with event name) API-->>PulseScript: Event recorded Note over User,Settings: Dashboard View User->>Dashboard: View site dashboard Dashboard->>API: GET /sites/{id}/dashboard API-->>Dashboard: DashboardData (includes goal_counts) Dashboard->>Dashboard: Render GoalStats with display names Dashboard-->>User: Show goal counts ```
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 14:30:09 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

<sub>2 files reviewed, 1 comment</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:30:08 +00:00

Event name validation missing max length check. The client-side script enforces a 64-character limit (EVENT_NAME_MAX in public/script.js:232), but this validation doesn't check length.

    const eventName = goalForm.event_name.trim().toLowerCase().replace(/\s+/g, '_')
    if (eventName.length > 64) {
      toast.error('Event name must be 64 characters or less')
      return
    }
    if (!/^[a-zA-Z0-9_]+$/.test(eventName)) {
      toast.error('Event name can only contain letters, numbers, and underscores')
      return
    }
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/settings/page.tsx
Line: 158:162

Comment:
Event name validation missing max length check. The client-side script enforces a 64-character limit (EVENT_NAME_MAX in `public/script.js:232`), but this validation doesn't check length.

```suggestion
    const eventName = goalForm.event_name.trim().toLowerCase().replace(/\s+/g, '_')
    if (eventName.length > 64) {
      toast.error('Event name must be 64 characters or less')
      return
    }
    if (!/^[a-zA-Z0-9_]+$/.test(eventName)) {
      toast.error('Event name can only contain letters, numbers, and underscores')
      return
    }
```

How can I resolve this? If you propose a fix, please make it concise.
Event name validation missing max length check. The client-side script enforces a 64-character limit (EVENT_NAME_MAX in `public/script.js:232`), but this validation doesn't check length. ```suggestion const eventName = goalForm.event_name.trim().toLowerCase().replace(/\s+/g, '_') if (eventName.length > 64) { toast.error('Event name must be 64 characters or less') return } if (!/^[a-zA-Z0-9_]+$/.test(eventName)) { toast.error('Event name can only contain letters, numbers, and underscores') return } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/settings/page.tsx Line: 158:162 Comment: Event name validation missing max length check. The client-side script enforces a 64-character limit (EVENT_NAME_MAX in `public/script.js:232`), but this validation doesn't check length. ```suggestion const eventName = goalForm.event_name.trim().toLowerCase().replace(/\s+/g, '_') if (eventName.length > 64) { toast.error('Event name must be 64 characters or less') return } if (!/^[a-zA-Z0-9_]+$/.test(eventName)) { toast.error('Event name can only contain letters, numbers, and underscores') return } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 14:34:50 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

4 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>4 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -115,3 +202,4 @@
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setSaving(true)
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:34:48 +00:00

order of operations: transform then validate length. The transformation (.trim().toLowerCase().replace(/\s+/g, '_')) happens before length check. A 50-char input with spaces becomes 48 chars after replacing spaces with underscores. This is fine, but consider if users might be confused when "my event name" (13 chars) becomes "my_event_name" (13 chars) - the length stays the same here but spaces→underscores could theoretically reduce length if multiple spaces exist.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/settings/page.tsx
Line: 158:158

Comment:
order of operations: transform then validate length. The transformation (`.trim().toLowerCase().replace(/\s+/g, '_')`) happens before length check. A 50-char input with spaces becomes 48 chars after replacing spaces with underscores. This is fine, but consider if users might be confused when "my event name" (13 chars) becomes "my_event_name" (13 chars) - the length stays the same here but spaces→underscores could theoretically reduce length if multiple spaces exist.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.
order of operations: transform then validate length. The transformation (`.trim().toLowerCase().replace(/\s+/g, '_')`) happens before length check. A 50-char input with spaces becomes 48 chars after replacing spaces with underscores. This is fine, but consider if users might be confused when "my event name" (13 chars) becomes "my_event_name" (13 chars) - the length stays the same here but spaces→underscores could theoretically reduce length if multiple spaces exist. <sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub> <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/settings/page.tsx Line: 158:158 Comment: order of operations: transform then validate length. The transformation (`.trim().toLowerCase().replace(/\s+/g, '_')`) happens before length check. A 50-char input with spaces becomes 48 chars after replacing spaces with underscores. This is fine, but consider if users might be confused when "my event name" (13 chars) becomes "my_event_name" (13 chars) - the length stays the same here but spaces→underscores could theoretically reduce length if multiple spaces exist. <sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub> How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +1,64 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:34:49 +00:00

displays transformed event_name instead of goal's display name. The API returns GoalCountStat with only event_name and count, but goals have a separate name field (display name). Users define goals with friendly names like "User Signups" for event "signup_click", but this shows "signup click" (transformed event name).

Verify if the backend should return goal names with the counts, or if showing transformed event names is intentional.

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/dashboard/GoalStats.tsx
Line: 36:36

Comment:
displays transformed `event_name` instead of goal's display name. The API returns `GoalCountStat` with only `event_name` and `count`, but goals have a separate `name` field (display name). Users define goals with friendly names like "User Signups" for event "signup_click", but this shows "signup click" (transformed event name).

Verify if the backend should return goal names with the counts, or if showing transformed event names is intentional.

How can I resolve this? If you propose a fix, please make it concise.
displays transformed `event_name` instead of goal's display name. The API returns `GoalCountStat` with only `event_name` and `count`, but goals have a separate `name` field (display name). Users define goals with friendly names like "User Signups" for event "signup_click", but this shows "signup click" (transformed event name). Verify if the backend should return goal names with the counts, or if showing transformed event names is intentional. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/dashboard/GoalStats.tsx Line: 36:36 Comment: displays transformed `event_name` instead of goal's display name. The API returns `GoalCountStat` with only `event_name` and `count`, but goals have a separate `name` field (display name). Users define goals with friendly names like "User Signups" for event "signup_click", but this shows "signup click" (transformed event name). Verify if the backend should return goal names with the counts, or if showing transformed event names is intentional. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 14:46:24 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

<sub>3 files reviewed, 1 comment</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +1,64 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:46:23 +00:00

siteId and dateRange props are unused - can remove from interface and component

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/dashboard/GoalStats.tsx
Line: 10:11

Comment:
`siteId` and `dateRange` props are unused - can remove from interface and component

How can I resolve this? If you propose a fix, please make it concise.
`siteId` and `dateRange` props are unused - can remove from interface and component <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/dashboard/GoalStats.tsx Line: 10:11 Comment: `siteId` and `dateRange` props are unused - can remove from interface and component How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 14:53:25 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

4 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

<sub>4 files reviewed, 3 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:53:23 +00:00

no validation for duplicate event_name within the same site. Users can create multiple goals with the same event_name, which could cause confusion in the dashboard or unexpected behavior when tracking events.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/settings/page.tsx
Line: 158:166

Comment:
no validation for duplicate `event_name` within the same site. Users can create multiple goals with the same `event_name`, which could cause confusion in the dashboard or unexpected behavior when tracking events.

How can I resolve this? If you propose a fix, please make it concise.
no validation for duplicate `event_name` within the same site. Users can create multiple goals with the same `event_name`, which could cause confusion in the dashboard or unexpected behavior when tracking events. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/settings/page.tsx Line: 158:166 Comment: no validation for duplicate `event_name` within the same site. Users can create multiple goals with the same `event_name`, which could cause confusion in the dashboard or unexpected behavior when tracking events. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:53:24 +00:00

updating a goal's event_name loses historical connection to tracked events. Events already tracked under the old name won't be associated with the renamed goal.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/settings/page.tsx
Line: 169:175

Comment:
updating a goal's `event_name` loses historical connection to tracked events. Events already tracked under the old name won't be associated with the renamed goal.

How can I resolve this? If you propose a fix, please make it concise.
updating a goal's `event_name` loses historical connection to tracked events. Events already tracked under the old name won't be associated with the renamed goal. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/settings/page.tsx Line: 169:175 Comment: updating a goal's `event_name` loses historical connection to tracked events. Events already tracked under the old name won't be associated with the renamed goal. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -228,4 +228,40 @@
// * Track popstate (browser back/forward)
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:53:21 +00:00

overwrites existing window.pulse if it exists. If the script loads multiple times or another library uses window.pulse, this silently replaces it.

  window.pulse = window.pulse || {};
  window.pulse.track = trackCustomEvent;
Prompt To Fix With AI
This is a comment left during a code review.
Path: public/script.js
Line: 264:264

Comment:
overwrites existing `window.pulse` if it exists. If the script loads multiple times or another library uses `window.pulse`, this silently replaces it.

```suggestion
  window.pulse = window.pulse || {};
  window.pulse.track = trackCustomEvent;
```

How can I resolve this? If you propose a fix, please make it concise.
overwrites existing `window.pulse` if it exists. If the script loads multiple times or another library uses `window.pulse`, this silently replaces it. ```suggestion window.pulse = window.pulse || {}; window.pulse.track = trackCustomEvent; ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: public/script.js Line: 264:264 Comment: overwrites existing `window.pulse` if it exists. If the script loads multiple times or another library uses `window.pulse`, this silently replaces it. ```suggestion window.pulse = window.pulse || {}; window.pulse.track = trackCustomEvent; ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 14:59:19 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

<sub>3 files reviewed, 1 comment</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 14:59:18 +00:00

transformation logic only handles spaces. Input like "test!!!test" gets trimmed/lowercased but has no spaces, so the replace(/\s+/g, '_') doesn't change invalid chars. It's then rejected by the regex. The current approach is correct (reject rather than silently fix), but consider adding a hint in the UI that invalid characters cannot be used (not just that spaces become underscores).

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/settings/page.tsx
Line: 158:166

Comment:
transformation logic only handles spaces. Input like `"test!!!test"` gets trimmed/lowercased but has no spaces, so the `replace(/\s+/g, '_')` doesn't change invalid chars. It's then rejected by the regex. The current approach is correct (reject rather than silently fix), but consider adding a hint in the UI that invalid characters cannot be used (not just that spaces become underscores).

How can I resolve this? If you propose a fix, please make it concise.
transformation logic only handles spaces. Input like `"test!!!test"` gets trimmed/lowercased but has no spaces, so the `replace(/\s+/g, '_')` doesn't change invalid chars. It's then rejected by the regex. The current approach is correct (reject rather than silently fix), but consider adding a hint in the UI that invalid characters cannot be used (not just that spaces become underscores). <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/settings/page.tsx Line: 158:166 Comment: transformation logic only handles spaces. Input like `"test!!!test"` gets trimmed/lowercased but has no spaces, so the `replace(/\s+/g, '_')` doesn't change invalid chars. It's then rejected by the regex. The current approach is correct (reject rather than silently fix), but consider adding a hint in the UI that invalid characters cannot be used (not just that spaces become underscores). How can I resolve this? If you propose a fix, please make it concise. ````` </details>
Sign in to join this conversation.
No description provided.