Files
pulse/lib/api/stats.ts
Usman Baig b305b5345b refactor: remove performance insights (Web Vitals) feature entirely
Remove Performance tab, PerformanceStats component, settings toggle,
Web Vitals observers from tracking script, and all related API types
and SWR hooks. Duration tracking is preserved.
2026-03-14 22:47:33 +01:00

421 lines
17 KiB
TypeScript

import apiRequest from './client'
import { Site } from './sites'
// ─── Types ──────────────────────────────────────────────────────────
export interface Stats {
pageviews: number
visitors: number
bounce_rate: number
avg_duration: number
}
export interface TopPage {
path: string
pageviews: number
visits?: number
}
export interface ScreenResolutionStat {
screen_resolution: string
pageviews: number
}
export interface GoalCountStat {
event_name: string
count: number
display_name?: string | null
}
export interface CampaignStat {
source: string
medium: string
campaign: string
visitors: number
pageviews: number
}
export interface TopReferrer {
referrer: string
pageviews: number
}
export interface CountryStat {
country: string
pageviews: number
}
export interface CityStat {
city: string
country: string
pageviews: number
}
export interface RegionStat {
region: string
country: string
pageviews: number
}
export interface BrowserStat {
browser: string
pageviews: number
}
export interface OSStat {
os: string
pageviews: number
}
export interface DeviceStat {
device: string
pageviews: number
}
export interface DailyStat {
date: string
pageviews: number
visitors: number
bounce_rate: number
avg_duration: number
}
export interface RealtimeStats {
visitors: number
}
export interface AuthParams {
password?: string
captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
}
export interface FrustrationSummary {
rage_clicks: number
rage_unique_elements: number
rage_top_page: string
dead_clicks: number
dead_unique_elements: number
dead_top_page: string
prev_rage_clicks: number
prev_dead_clicks: number
}
export interface FrustrationElement {
selector: string
page_path: string
count: number
avg_click_count?: number
sessions: number
last_seen: string
}
export interface FrustrationByPage {
page_path: string
rage_clicks: number
dead_clicks: number
total: number
unique_elements: number
}
// ─── Helpers ────────────────────────────────────────────────────────
function appendAuthParams(params: URLSearchParams, auth?: AuthParams) {
if (auth?.password) params.append('password', auth.password)
if (auth?.captcha?.captcha_id) params.append('captcha_id', auth.captcha.captcha_id)
if (auth?.captcha?.captcha_solution) params.append('captcha_solution', auth.captcha.captcha_solution)
if (auth?.captcha?.captcha_token) params.append('captcha_token', auth.captcha.captcha_token)
}
function buildQuery(
opts: {
startDate?: string
endDate?: string
limit?: number
interval?: string
countryLimit?: number
sort?: string
filters?: string
},
auth?: AuthParams
): string {
const params = new URLSearchParams()
if (opts.startDate) params.append('start_date', opts.startDate)
if (opts.endDate) params.append('end_date', opts.endDate)
if (opts.limit != null) params.append('limit', opts.limit.toString())
if (opts.interval) params.append('interval', opts.interval)
if (opts.countryLimit != null) params.append('country_limit', opts.countryLimit.toString())
if (opts.sort) params.append('sort', opts.sort)
if (opts.filters) params.append('filters', opts.filters)
if (auth) appendAuthParams(params, auth)
const query = params.toString()
return query ? `?${query}` : ''
}
/** Factory for endpoints that return an array nested under a response key. */
function createListFetcher<T>(path: string, field: string, defaultLimit = 10) {
return (siteId: string, startDate?: string, endDate?: string, limit = defaultLimit, filters?: string): Promise<T[]> =>
apiRequest<Record<string, T[]>>(`/sites/${siteId}/${path}${buildQuery({ startDate, endDate, limit, filters })}`)
.then(r => r?.[field] || [])
}
// ─── List Endpoints ─────────────────────────────────────────────────
export const getTopPages = createListFetcher<TopPage>('pages', 'pages')
export const getTopReferrers = createListFetcher<TopReferrer>('referrers', 'referrers')
export const getCountries = createListFetcher<CountryStat>('countries', 'countries')
export const getCities = createListFetcher<CityStat>('cities', 'cities')
export const getRegions = createListFetcher<RegionStat>('regions', 'regions')
export const getBrowsers = createListFetcher<BrowserStat>('browsers', 'browsers')
export const getOS = createListFetcher<OSStat>('os', 'os')
export const getDevices = createListFetcher<DeviceStat>('devices', 'devices')
export const getEntryPages = createListFetcher<TopPage>('entry-pages', 'pages')
export const getExitPages = createListFetcher<TopPage>('exit-pages', 'pages')
export const getScreenResolutions = createListFetcher<ScreenResolutionStat>('screen-resolutions', 'screen_resolutions')
export const getGoalStats = createListFetcher<GoalCountStat>('goals/stats', 'goal_counts', 20)
export const getCampaigns = createListFetcher<CampaignStat>('campaigns', 'campaigns')
// ─── Stats & Realtime ───────────────────────────────────────────────
export function getStats(siteId: string, startDate?: string, endDate?: string, filters?: string): Promise<Stats> {
return apiRequest<Stats>(`/sites/${siteId}/stats${buildQuery({ startDate, endDate, filters })}`)
}
export function getPublicStats(siteId: string, startDate?: string, endDate?: string, auth?: AuthParams): Promise<Stats> {
return apiRequest<Stats>(`/public/sites/${siteId}/stats${buildQuery({ startDate, endDate }, auth)}`)
}
export function getRealtime(siteId: string): Promise<RealtimeStats> {
return apiRequest<RealtimeStats>(`/sites/${siteId}/realtime`)
}
export function getPublicRealtime(siteId: string, auth?: AuthParams): Promise<RealtimeStats> {
return apiRequest<RealtimeStats>(`/public/sites/${siteId}/realtime${buildQuery({}, auth)}`)
}
// ─── Daily Stats ────────────────────────────────────────────────────
export function getDailyStats(siteId: string, startDate?: string, endDate?: string, interval?: string, filters?: string): Promise<DailyStat[]> {
return apiRequest<{ stats: DailyStat[] }>(`/sites/${siteId}/daily${buildQuery({ startDate, endDate, interval, filters })}`)
.then(r => r?.stats || [])
}
export function getPublicDailyStats(siteId: string, startDate?: string, endDate?: string, interval?: string, auth?: AuthParams): Promise<DailyStat[]> {
return apiRequest<{ stats: DailyStat[] }>(`/public/sites/${siteId}/daily${buildQuery({ startDate, endDate, interval }, auth)}`)
.then(r => r?.stats || [])
}
// ─── Public Campaigns ───────────────────────────────────────────────
export function getPublicCampaigns(siteId: string, startDate?: string, endDate?: string, limit = 10, auth?: AuthParams): Promise<CampaignStat[]> {
return apiRequest<{ campaigns: CampaignStat[] }>(`/public/sites/${siteId}/campaigns${buildQuery({ startDate, endDate, limit }, auth)}`)
.then(r => r?.campaigns || [])
}
// ─── Full Dashboard ─────────────────────────────────────────────────
export interface DashboardData {
site: Site
stats: Stats
realtime_visitors: number
daily_stats: DailyStat[]
top_pages: TopPage[]
entry_pages: TopPage[]
exit_pages: TopPage[]
top_referrers: TopReferrer[]
countries: CountryStat[]
cities: CityStat[]
regions: RegionStat[]
browsers: BrowserStat[]
os: OSStat[]
devices: DeviceStat[]
screen_resolutions: ScreenResolutionStat[]
goal_counts?: GoalCountStat[]
}
export function getDashboard(siteId: string, startDate?: string, endDate?: string, limit = 10, interval?: string, filters?: string): Promise<DashboardData> {
return apiRequest<DashboardData>(`/sites/${siteId}/dashboard${buildQuery({ startDate, endDate, limit, interval, filters })}`)
}
export function getPublicDashboard(
siteId: string,
startDate?: string,
endDate?: string,
limit = 10,
interval?: string,
password?: string,
captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
): Promise<DashboardData> {
return apiRequest<DashboardData>(
`/public/sites/${siteId}/dashboard${buildQuery({ startDate, endDate, limit, interval }, { password, captcha })}`
)
}
// ─── Focused Dashboard Endpoints ────────────────────────────────────
export interface DashboardOverviewData {
site: Site
stats: Stats
realtime_visitors: number
daily_stats: DailyStat[]
}
export interface DashboardPagesData {
top_pages: TopPage[]
entry_pages: TopPage[]
exit_pages: TopPage[]
}
export interface DashboardLocationsData {
countries: CountryStat[]
cities: CityStat[]
regions: RegionStat[]
}
export interface DashboardDevicesData {
browsers: BrowserStat[]
os: OSStat[]
devices: DeviceStat[]
screen_resolutions: ScreenResolutionStat[]
}
export interface DashboardReferrersData {
top_referrers: TopReferrer[]
}
export interface DashboardGoalsData {
goal_counts: GoalCountStat[]
}
export function getDashboardOverview(siteId: string, startDate?: string, endDate?: string, interval?: string, filters?: string): Promise<DashboardOverviewData> {
return apiRequest<DashboardOverviewData>(`/sites/${siteId}/dashboard/overview${buildQuery({ startDate, endDate, interval, filters })}`)
}
export function getPublicDashboardOverview(
siteId: string, startDate?: string, endDate?: string, interval?: string,
password?: string, captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
): Promise<DashboardOverviewData> {
return apiRequest<DashboardOverviewData>(`/public/sites/${siteId}/dashboard/overview${buildQuery({ startDate, endDate, interval }, { password, captcha })}`)
}
export function getDashboardPages(siteId: string, startDate?: string, endDate?: string, limit = 10, filters?: string): Promise<DashboardPagesData> {
return apiRequest<DashboardPagesData>(`/sites/${siteId}/dashboard/pages${buildQuery({ startDate, endDate, limit, filters })}`)
}
export function getPublicDashboardPages(
siteId: string, startDate?: string, endDate?: string, limit = 10,
password?: string, captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
): Promise<DashboardPagesData> {
return apiRequest<DashboardPagesData>(`/public/sites/${siteId}/dashboard/pages${buildQuery({ startDate, endDate, limit }, { password, captcha })}`)
}
export function getDashboardLocations(siteId: string, startDate?: string, endDate?: string, limit = 10, countryLimit = 250, filters?: string): Promise<DashboardLocationsData> {
return apiRequest<DashboardLocationsData>(`/sites/${siteId}/dashboard/locations${buildQuery({ startDate, endDate, limit, countryLimit, filters })}`)
}
export function getPublicDashboardLocations(
siteId: string, startDate?: string, endDate?: string, limit = 10, countryLimit = 250,
password?: string, captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
): Promise<DashboardLocationsData> {
return apiRequest<DashboardLocationsData>(`/public/sites/${siteId}/dashboard/locations${buildQuery({ startDate, endDate, limit, countryLimit }, { password, captcha })}`)
}
export function getDashboardDevices(siteId: string, startDate?: string, endDate?: string, limit = 10, filters?: string): Promise<DashboardDevicesData> {
return apiRequest<DashboardDevicesData>(`/sites/${siteId}/dashboard/devices${buildQuery({ startDate, endDate, limit, filters })}`)
}
export function getPublicDashboardDevices(
siteId: string, startDate?: string, endDate?: string, limit = 10,
password?: string, captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
): Promise<DashboardDevicesData> {
return apiRequest<DashboardDevicesData>(`/public/sites/${siteId}/dashboard/devices${buildQuery({ startDate, endDate, limit }, { password, captcha })}`)
}
export function getDashboardReferrers(siteId: string, startDate?: string, endDate?: string, limit = 10, filters?: string): Promise<DashboardReferrersData> {
return apiRequest<DashboardReferrersData>(`/sites/${siteId}/dashboard/referrers${buildQuery({ startDate, endDate, limit, filters })}`)
}
export function getPublicDashboardReferrers(
siteId: string, startDate?: string, endDate?: string, limit = 10,
password?: string, captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
): Promise<DashboardReferrersData> {
return apiRequest<DashboardReferrersData>(`/public/sites/${siteId}/dashboard/referrers${buildQuery({ startDate, endDate, limit }, { password, captcha })}`)
}
export function getDashboardGoals(siteId: string, startDate?: string, endDate?: string, limit = 10, filters?: string): Promise<DashboardGoalsData> {
return apiRequest<DashboardGoalsData>(`/sites/${siteId}/dashboard/goals${buildQuery({ startDate, endDate, limit, filters })}`)
}
export function getPublicDashboardGoals(
siteId: string, startDate?: string, endDate?: string, limit = 10,
password?: string, captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string }
): Promise<DashboardGoalsData> {
return apiRequest<DashboardGoalsData>(`/public/sites/${siteId}/dashboard/goals${buildQuery({ startDate, endDate, limit }, { password, captcha })}`)
}
// ─── Event Properties ────────────────────────────────────────────────
export interface EventPropertyKey {
key: string
count: number
}
export interface EventPropertyValue {
value: string
count: number
}
export function getEventPropertyKeys(siteId: string, eventName: string, startDate?: string, endDate?: string): Promise<EventPropertyKey[]> {
return apiRequest<{ keys: EventPropertyKey[] }>(`/sites/${siteId}/goals/${encodeURIComponent(eventName)}/properties${buildQuery({ startDate, endDate })}`)
.then(r => r?.keys || [])
}
export function getEventPropertyValues(siteId: string, eventName: string, propName: string, startDate?: string, endDate?: string, limit = 20): Promise<EventPropertyValue[]> {
return apiRequest<{ values: EventPropertyValue[] }>(`/sites/${siteId}/goals/${encodeURIComponent(eventName)}/properties/${encodeURIComponent(propName)}${buildQuery({ startDate, endDate, limit })}`)
.then(r => r?.values || [])
}
// ─── Frustration Signals ────────────────────────────────────────────
export interface BehaviorData {
summary: FrustrationSummary
rage_clicks: { items: FrustrationElement[]; total: number }
dead_clicks: { items: FrustrationElement[]; total: number }
by_page: FrustrationByPage[]
}
const emptyBehavior: BehaviorData = {
summary: { rage_clicks: 0, rage_unique_elements: 0, rage_top_page: '', dead_clicks: 0, dead_unique_elements: 0, dead_top_page: '', prev_rage_clicks: 0, prev_dead_clicks: 0 },
rage_clicks: { items: [], total: 0 },
dead_clicks: { items: [], total: 0 },
by_page: [],
}
export function getBehavior(siteId: string, startDate?: string, endDate?: string, limit = 7): Promise<BehaviorData> {
return apiRequest<BehaviorData>(`/sites/${siteId}/behavior${buildQuery({ startDate, endDate, limit })}`)
.then(r => r ?? emptyBehavior)
}
export function getFrustrationSummary(siteId: string, startDate?: string, endDate?: string): Promise<FrustrationSummary> {
return apiRequest<FrustrationSummary>(`/sites/${siteId}/frustration/summary${buildQuery({ startDate, endDate })}`)
.then(r => r ?? { rage_clicks: 0, rage_unique_elements: 0, rage_top_page: '', dead_clicks: 0, dead_unique_elements: 0, dead_top_page: '', prev_rage_clicks: 0, prev_dead_clicks: 0 })
}
export function getRageClicks(siteId: string, startDate?: string, endDate?: string, limit = 10, pagePath?: string): Promise<{ items: FrustrationElement[], total: number }> {
const params = buildQuery({ startDate, endDate, limit })
const pageFilter = pagePath ? `&page_path=${encodeURIComponent(pagePath)}` : ''
return apiRequest<{ items: FrustrationElement[], total: number }>(`/sites/${siteId}/frustration/rage-clicks${params}${pageFilter}`)
.then(r => r ?? { items: [], total: 0 })
}
export function getDeadClicks(siteId: string, startDate?: string, endDate?: string, limit = 10, pagePath?: string): Promise<{ items: FrustrationElement[], total: number }> {
const params = buildQuery({ startDate, endDate, limit })
const pageFilter = pagePath ? `&page_path=${encodeURIComponent(pagePath)}` : ''
return apiRequest<{ items: FrustrationElement[], total: number }>(`/sites/${siteId}/frustration/dead-clicks${params}${pageFilter}`)
.then(r => r ?? { items: [], total: 0 })
}
export function getFrustrationByPage(siteId: string, startDate?: string, endDate?: string, limit = 20): Promise<FrustrationByPage[]> {
return apiRequest<{ pages: FrustrationByPage[] }>(`/sites/${siteId}/frustration/by-page${buildQuery({ startDate, endDate, limit })}`)
.then(r => r?.pages ?? [])
}