analytics.ts 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. 'use client'
  2. import posthog from 'posthog-js'
  3. /**
  4. * Product analytics events, and the only way the app sends them.
  5. *
  6. * PostHog runs in cloud mode and on the demo instance; a self-hosted install
  7. * never starts it, and then nothing here sends anything.
  8. *
  9. * Calls made before PostHog has started are held and sent once it has. The
  10. * provider starts it in an effect in the root layout, and React runs a page's
  11. * effects before its layout's, so an event fired when a page mounts would
  12. * otherwise be dropped on every full page load.
  13. *
  14. * Names follow PostHog's guide: lowercase snake case, fixed strings,
  15. * `category:object_action` with a present-tense verb. Anything that varies
  16. * goes in a property, never in the name.
  17. */
  18. export type AnalyticsEvent =
  19. /** A work order opened, with the layout it was drawn in. */
  20. | 'work_order:page_view'
  21. /** A browser moved between the classic and the overhauled work order page. */
  22. | 'work_order:layout_switch'
  23. /** The invitation to try the overhauled page closed without trying it. */
  24. | 'work_order:layout_invite_dismiss'
  25. type Properties = Record<string, string | number | boolean | null>
  26. type Pending =
  27. | { kind: 'capture'; event: AnalyticsEvent; properties?: Properties }
  28. | { kind: 'register'; properties: Properties }
  29. /** A page opened and closed many times before PostHog starts, or on an install that never does. */
  30. const MAX_PENDING = 20
  31. let ready = false
  32. const pending: Pending[] = []
  33. function hold(item: Pending) {
  34. if (pending.length < MAX_PENDING) pending.push(item)
  35. }
  36. /** Sends one event, or holds it until PostHog has started. */
  37. export function track(event: AnalyticsEvent, properties?: Properties): void {
  38. if (ready) posthog.capture(event, properties)
  39. else hold({ kind: 'capture', event, properties })
  40. }
  41. /**
  42. * Attaches properties to every later event from this browser, autocaptured
  43. * clicks and page views included (PostHog's super properties). For facts
  44. * about the browser rather than about one action: the layout it shows, say.
  45. */
  46. export function registerAnalyticsProperties(properties: Properties): void {
  47. if (ready) posthog.register(properties)
  48. else hold({ kind: 'register', properties })
  49. }
  50. /** Called by the provider once PostHog has started: sends what was held, in order. */
  51. export function analyticsStarted(): void {
  52. ready = true
  53. for (const item of pending.splice(0)) {
  54. if (item.kind === 'register') posthog.register(item.properties)
  55. else posthog.capture(item.event, item.properties)
  56. }
  57. }