[PULSE-53] Campaigns block improvements #23

Merged
uz1mani merged 2 commits from staging into main 2026-02-11 18:56:30 +00:00
uz1mani commented 2026-02-11 18:54:47 +00:00 (Migrated from github.com)

Work Item

PULSE-53

Summary

  • Add sortable columns, source favicons, and pageviews to the Campaigns dashboard card
  • Replace loading spinner with skeleton loader; use stable row keys; improve empty value display
  • Add Campaigns export (CSV from block, PDF/Excel from main Export modal)

Changes

  • Campaigns.tsx: Sortable column headers with ChevronDownIcon; source favicons/display names via existing icon utils; pageviews column; em-dash (—) for empty Medium/Campaign; skeleton loading state; Export button for CSV download; stable keys via campaignRowKey()
  • ExportModal.tsx: campaigns?: CampaignStat[] prop; campaigns table in PDF export; campaigns sheet in Excel export; use getReferrerDisplayName for source display
  • app/sites/[id]/page.tsx: Fetch campaigns in loadData via getCampaigns(); add campaigns state; pass campaigns to ExportModal
  • CHANGELOG.md: Entry under 0.3.0-alpha for Campaigns block improvements

Test Plan

  • [] Open site dashboard, verify Campaigns card shows sortable columns and source favicons when data exists
  • Click column headers, verify sort order changes; verify chevron reflects direction
  • Verify empty Medium/Campaign show "—" instead of "-"
  • Refresh with campaigns data; verify skeleton loader during load
  • Click Export on Campaigns card; verify CSV downloads with correct data and date range in filename
  • Click main Export, choose PDF; verify campaigns table appears after Top Referrers
  • Click main Export, choose Excel; verify Campaigns sheet exists with correct columns
  • Test dark mode and keyboard focus on sort headers
## Work Item PULSE-53 ## Summary - Add sortable columns, source favicons, and pageviews to the Campaigns dashboard card - Replace loading spinner with skeleton loader; use stable row keys; improve empty value display - Add Campaigns export (CSV from block, PDF/Excel from main Export modal) ## Changes - **Campaigns.tsx**: Sortable column headers with ChevronDownIcon; source favicons/display names via existing icon utils; pageviews column; em-dash (—) for empty Medium/Campaign; skeleton loading state; Export button for CSV download; stable keys via campaignRowKey() - **ExportModal.tsx**: `campaigns?: CampaignStat[]` prop; campaigns table in PDF export; campaigns sheet in Excel export; use getReferrerDisplayName for source display - **app/sites/[id]/page.tsx**: Fetch campaigns in loadData via getCampaigns(); add campaigns state; pass campaigns to ExportModal - **CHANGELOG.md**: Entry under 0.3.0-alpha for Campaigns block improvements ## Test Plan - [] Open site dashboard, verify Campaigns card shows sortable columns and source favicons when data exists - [x] Click column headers, verify sort order changes; verify chevron reflects direction - [x] Verify empty Medium/Campaign show "—" instead of "-" - [x] Refresh with campaigns data; verify skeleton loader during load - [x] Click Export on Campaigns card; verify CSV downloads with correct data and date range in filename - [x] Click main Export, choose PDF; verify campaigns table appears after Top Referrers - [x] Click main Export, choose Excel; verify Campaigns sheet exists with correct columns - [x] Test dark mode and keyboard focus on sort headers
greptile-apps[bot] commented 2026-02-11 18:57:36 +00:00 (Migrated from github.com)

Greptile Overview

Greptile Summary

This PR enhances the Campaigns dashboard block with sortable columns, source favicons with display names, a pageviews column, and comprehensive export functionality (CSV from the block, PDF/Excel from the main Export modal). The loading state was improved from a spinner to a skeleton loader, empty values now display em-dash (—), and rows use stable keys for better React reconciliation.

Key changes:

  • Sortable column headers (Source, Medium, Campaign, Visitors, Pageviews) with chevron indicators
  • Source favicons using existing getReferrerFavicon and getReferrerDisplayName utilities, matching the Top Referrers pattern
  • Export button on Campaigns card for quick CSV download with date range in filename
  • Campaigns included in main dashboard PDF/Excel exports
  • Skeleton loading state replacing spinner for better UX

Confidence Score: 4/5

  • Safe to merge after addressing CSV export escaping
  • Well-structured feature addition with good UX improvements. One CSV injection vulnerability needs fixing in the export function, but otherwise follows existing patterns and includes proper state management.
  • Pay attention to components/dashboard/Campaigns.tsx for the CSV export escaping issue

Important Files Changed

Filename Overview
components/dashboard/Campaigns.tsx Added sortable columns, favicons, pageviews column, skeleton loading, CSV export, and stable row keys. Minor CSV injection risk in export.
components/dashboard/ExportModal.tsx Added campaigns prop and export support for PDF and Excel formats using existing patterns consistently.
app/sites/[id]/page.tsx Added campaigns data fetching with getCampaigns and passed campaigns to ExportModal. Clean integration with existing dashboard.
CHANGELOG.md Added comprehensive changelog entry describing all Campaigns block improvements accurately.

Sequence Diagram

sequenceDiagram
    participant User
    participant Page as SiteDashboardPage
    participant API as getCampaigns API
    participant Campaigns as Campaigns Component
    participant Export as ExportModal
    
    User->>Page: Load dashboard
    Page->>API: getCampaigns(siteId, dateRange, 100)
    API-->>Page: campaigns data
    Page->>Campaigns: Pass campaigns via props
    Page->>Export: Pass campaigns to ExportModal
    
    User->>Campaigns: Click column header
    Campaigns->>Campaigns: handleSort(key)
    Campaigns->>Campaigns: Update sortKey/sortDir state
    Campaigns->>Campaigns: Re-render with sortedData
    
    User->>Campaigns: Click Export button
    Campaigns->>Campaigns: handleExportCampaigns()
    Campaigns->>Campaigns: Generate CSV from sortedData
    Campaigns->>User: Download campaigns_[dateRange].csv
    
    User->>Export: Click main Export, choose PDF
    Export->>Export: Generate PDF with campaigns table
    Export->>User: Download PDF with Campaigns section
    
    User->>Export: Click main Export, choose Excel
    Export->>Export: Create Campaigns sheet
    Export->>User: Download Excel with Campaigns tab
<h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR enhances the Campaigns dashboard block with sortable columns, source favicons with display names, a pageviews column, and comprehensive export functionality (CSV from the block, PDF/Excel from the main Export modal). The loading state was improved from a spinner to a skeleton loader, empty values now display em-dash (—), and rows use stable keys for better React reconciliation. **Key changes:** - Sortable column headers (Source, Medium, Campaign, Visitors, Pageviews) with chevron indicators - Source favicons using existing `getReferrerFavicon` and `getReferrerDisplayName` utilities, matching the Top Referrers pattern - Export button on Campaigns card for quick CSV download with date range in filename - Campaigns included in main dashboard PDF/Excel exports - Skeleton loading state replacing spinner for better UX <h3>Confidence Score: 4/5</h3> - Safe to merge after addressing CSV export escaping - Well-structured feature addition with good UX improvements. One CSV injection vulnerability needs fixing in the export function, but otherwise follows existing patterns and includes proper state management. - Pay attention to `components/dashboard/Campaigns.tsx` for the CSV export escaping issue <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | components/dashboard/Campaigns.tsx | Added sortable columns, favicons, pageviews column, skeleton loading, CSV export, and stable row keys. Minor CSV injection risk in export. | | components/dashboard/ExportModal.tsx | Added campaigns prop and export support for PDF and Excel formats using existing patterns consistently. | | app/sites/[id]/page.tsx | Added campaigns data fetching with getCampaigns and passed campaigns to ExportModal. Clean integration with existing dashboard. | | CHANGELOG.md | Added comprehensive changelog entry describing all Campaigns block improvements accurately. | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant User participant Page as SiteDashboardPage participant API as getCampaigns API participant Campaigns as Campaigns Component participant Export as ExportModal User->>Page: Load dashboard Page->>API: getCampaigns(siteId, dateRange, 100) API-->>Page: campaigns data Page->>Campaigns: Pass campaigns via props Page->>Export: Pass campaigns to ExportModal User->>Campaigns: Click column header Campaigns->>Campaigns: handleSort(key) Campaigns->>Campaigns: Update sortKey/sortDir state Campaigns->>Campaigns: Re-render with sortedData User->>Campaigns: Click Export button Campaigns->>Campaigns: handleExportCampaigns() Campaigns->>Campaigns: Generate CSV from sortedData Campaigns->>User: Download campaigns_[dateRange].csv User->>Export: Click main Export, choose PDF Export->>Export: Generate PDF with campaigns table Export->>User: Download PDF with Campaigns section User->>Export: Click main Export, choose Excel Export->>Export: Create Campaigns sheet Export->>User: Download Excel with Campaigns tab ```
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-11 18:57:41 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

<sub>4 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-11 18:57:40 +00:00

CSV values with special characters (, " newlines) or formula injection chars (= + - @) aren't escaped, which could break CSV parsing or create security issues

      ...rows.map(r => {
        const escapeCSV = (val: string | number) => {
          if (typeof val === 'number') return val.toString()
          const str = val.toString()
          if (str.includes(',') || str.includes('"') || str.includes('\n') || /^[=+\-@]/.test(str)) {
            return `"${str.replace(/"/g, '""')}"`
          }
          return str
        }
        return [
          escapeCSV(r.source),
          escapeCSV(r.medium || EMPTY_LABEL),
          escapeCSV(r.campaign || EMPTY_LABEL),
          r.visitors,
          r.pageviews
        ].join(',')
      }),
Prompt To Fix With AI
This is a comment left during a code review.
Path: components/dashboard/Campaigns.tsx
Line: 130:132

Comment:
CSV values with special characters (`,` `"` newlines) or formula injection chars (`=` `+` `-` `@`) aren't escaped, which could break CSV parsing or create security issues

```suggestion
      ...rows.map(r => {
        const escapeCSV = (val: string | number) => {
          if (typeof val === 'number') return val.toString()
          const str = val.toString()
          if (str.includes(',') || str.includes('"') || str.includes('\n') || /^[=+\-@]/.test(str)) {
            return `"${str.replace(/"/g, '""')}"`
          }
          return str
        }
        return [
          escapeCSV(r.source),
          escapeCSV(r.medium || EMPTY_LABEL),
          escapeCSV(r.campaign || EMPTY_LABEL),
          r.visitors,
          r.pageviews
        ].join(',')
      }),
```

How can I resolve this? If you propose a fix, please make it concise.
CSV values with special characters (`,` `"` newlines) or formula injection chars (`=` `+` `-` `@`) aren't escaped, which could break CSV parsing or create security issues ```suggestion ...rows.map(r => { const escapeCSV = (val: string | number) => { if (typeof val === 'number') return val.toString() const str = val.toString() if (str.includes(',') || str.includes('"') || str.includes('\n') || /^[=+\-@]/.test(str)) { return `"${str.replace(/"/g, '""')}"` } return str } return [ escapeCSV(r.source), escapeCSV(r.medium || EMPTY_LABEL), escapeCSV(r.campaign || EMPTY_LABEL), r.visitors, r.pageviews ].join(',') }), ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/dashboard/Campaigns.tsx Line: 130:132 Comment: CSV values with special characters (`,` `"` newlines) or formula injection chars (`=` `+` `-` `@`) aren't escaped, which could break CSV parsing or create security issues ```suggestion ...rows.map(r => { const escapeCSV = (val: string | number) => { if (typeof val === 'number') return val.toString() const str = val.toString() if (str.includes(',') || str.includes('"') || str.includes('\n') || /^[=+\-@]/.test(str)) { return `"${str.replace(/"/g, '""')}"` } return str } return [ escapeCSV(r.source), escapeCSV(r.medium || EMPTY_LABEL), escapeCSV(r.campaign || EMPTY_LABEL), r.visitors, r.pageviews ].join(',') }), ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
Sign in to join this conversation.
No description provided.