Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/components/Products/Slides/TracesComparisonTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react'
import OSTable from 'components/OSTable'
import Logo from 'components/Logo'
import { useApp } from '../../../context/App'

// Small building blocks for the comparison cells
const Check = () => <span className="text-green text-lg font-bold">✓</span>
const Cross = () => <span className="text-red text-lg font-bold">✗</span>
const CheckWith = ({ note }: { note: string }) => (
<span>
<span className="text-green text-lg font-bold">✓</span> <span className="text-secondary">({note})</span>
</span>
)

const CompetitorName = ({ children }: { children: React.ReactNode }) => (
<span className="text-base font-bold text-primary">{children}</span>
)

export default function TracesComparisonTable(): JSX.Element {
const { siteSettings } = useApp()
const isDark = siteSettings?.theme === 'dark'

const columns = [
{ name: '', align: 'left' as const, width: 'minmax(180px, 1.6fr)' },
{
name: <CompetitorName>Better Stack</CompetitorName>,
align: 'center' as const,
width: '1fr',
},
{
name: <CompetitorName>Datadog</CompetitorName>,
align: 'center' as const,
width: '1fr',
},
{
name: <CompetitorName>Sentry</CompetitorName>,
align: 'center' as const,
width: '1fr',
},
{
name: <Logo className="h-5 mx-auto w-auto max-w-full" fill={isDark ? 'white' : ''} />,
align: 'center' as const,
width: '1fr',
},
]

// Section header row spanning the full width
const sectionRow = (label: string) => ({
cells: [
{
content: <strong>{label}</strong>,
className: 'col-span-full !items-start text-left bg-accent font-bold',
},
],
})

// Feature row: label + one cell per competitor (Better Stack, Datadog, Sentry, PostHog)
const featureRow = (label: React.ReactNode, values: React.ReactNode[]) => ({
cells: [{ content: label, className: '!items-start text-left font-semibold' }, ...values.map((content) => ({ content }))],
})

const rows = [
sectionRow('Tracing'),
featureRow('Distributed trace waterfall', [<Check />, <Check />, <Check />, <Check />]),
featureRow('Service / dependency map', [<Check />, <Check />, <Cross />, <Cross />]),
featureRow('Code-level profiling (flame graphs)', [<Cross />, <Check />, <Check />, <Cross />]),
featureRow('Sampling & retention controls', [
<Check />,
<Check />,
<span>Head sampling only</span>,
<span>Via OTel SDK</span>,
]),
sectionRow('Standards & setup'),
featureRow('Native OpenTelemetry ingestion', [
<Check />,
<CheckWith note="maps to its own model" />,
<CheckWith note="beta" />,
<Check />,
]),
featureRow('No proprietary SDK required', [<Check />, <span>Partial</span>, <span>Partial</span>, <Check />]),
featureRow('Instrumentation', [
<span>eBPF</span>,
<span>Agent</span>,
<span>SDK</span>,
<span>OpenTelemetry</span>,
]),
sectionRow('One platform'),
featureRow('Errors, logs & session replay alongside traces', [<Check />, <Check />, <Check />, <Check />]),
sectionRow('AI & self-driving'),
featureRow('AI opens a code fix PR', [<Cross />, <Cross />, <CheckWith note="beta" />, <Check />]),
featureRow('Fix a bug and open a PR from Slack', [<Cross />, <Cross />, <CheckWith note="beta" />, <Check />]),
sectionRow('Pricing & licensing'),
featureRow('Pricing model', [
<span>Per GB ingested</span>,
<span>Per host + per span</span>,
<span>Per span</span>,
<span>Usage-based, no per-host fee</span>,
]),
featureRow('Free tier', [<Check />, <span>Trial only</span>, <Check />, <Check />]),
featureRow('Open source', [<Cross />, <Cross />, <span>Source-available (FSL)</span>, <Check />]),
]

return (
<div className="h-full text-primary bg-primary overflow-auto p-4 @md:p-6 [&_.OSTable]:mb-0">
<OSTable columns={columns} rows={rows} width="full" size="md" rowAlignment="center" editable={false} />
</div>
)
}
106 changes: 106 additions & 0 deletions src/components/Products/Slides/TracesSelfHealing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React from 'react'
import TabbedCarousel from 'components/TabbedCarousel'
import type { TabbedCarouselTab } from 'components/TabbedCarousel'

// "Your product, fixing itself" section. Uses the same tabbed carousel layout as
// the "How a product improves itself" section on /self-driving. The section
// headline + intro stay constant; each tab swaps in its own copy. Per-tab
// illustrations will be added later.
const SELF_HEALING_HEADLINE = 'Your product, fixing itself'
const SELF_HEALING_INTRO =
'Point a scout at your slowest traces. It runs on a schedule, and when a query or endpoint starts creeping up, it reports the regression and kicks off an investigation into why.'
const TRACE_IS_THE_EVIDENCE =
'Throughout, the trace is the evidence: what the scout watches to raise the alarm, and what the agent reads to land the fix on the right line.'

const steps: {
value: string
label: string
color: string
activeText: string
progressBar: string
copy: string
image?: string
}[] = [
{
value: 'scout',
label: 'Scout',
color: 'bg-blue',
activeText: 'text-white',
progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
copy: 'Turn on the APM scout. It watches latency and request volume per service, on a schedule, and files any regression as a report.',
image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Scout_troop_Mock_b59fadf110.png',
},
{
value: 'signal',
label: 'Signal',
color: 'bg-red',
activeText: 'text-white',
progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
copy: 'Latency on GET /api/checkout starts climbing. The scout catches it and files a report. Nobody had to notice first.',
image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Post_Hog_Inbox_Mock_6485bb0963.png',
},
{
value: 'investigate',
label: 'Investigate',
color: 'bg-purple',
activeText: 'text-white',
progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
copy: 'The agent pulls the slow traces, lines them up against the fast ones, and finds the span they share: the inventory service firing one DB query per cart item. Classic N+1.',
image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Report_Investigate_Mock_2_76290ef07e.png',
},
{
value: 'pr',
label: 'PR',
color: 'bg-green',
activeText: 'text-white',
progressBar: 'bg-white shadow-[0_0_6px_2px_rgba(0,0,0,0.2)]',
copy: 'The agent fixes the exact query it located and opens a pull request, wired up with the instrumentation to measure whether it worked.',
image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Git_Hub_PR_Mock_1_8c4240dc83.png',
},
{
value: 'merge',
label: 'Merge',
color: 'bg-yellow',
activeText: 'text-black',
progressBar: 'bg-black/70 shadow-[0_0_6px_2px_rgba(255,255,255,0.4)]',
copy: 'You review the diff in your Inbox and hit merge. Nothing ships until you do.',
image: 'https://res.cloudinary.com/dmukukwp6/image/upload/Git_Hub_PR_Merged_Mock_1_5b9cf8f4b5.png',
},
]

const TabPanel = ({ title, children, image }: { title: string; children: React.ReactNode; image?: string }) => (
<div className="rounded bg-primary p-4 @xl:p-6 h-full flex flex-col justify-center">
<div className="text-secondary text-lg @xl:text-xl !leading-normal">{children}</div>
{image && (
<img src={image} alt={title} className="mt-6 block w-full rounded-md border border-primary shadow-lg" />
)}
</div>
)

const tabs: TabbedCarouselTab[] = steps.map((step) => ({
value: step.value,
label: step.label,
color: step.color,
activeText: step.activeText,
progressBar: step.progressBar,
content: (
<TabPanel title={step.label} image={step.image}>
<p className="m-0">{step.copy}</p>
</TabPanel>
),
}))

export default function TracesSelfHealing(): JSX.Element {
return (
<div className="h-full text-primary bg-primary overflow-auto p-4 @md:p-8 flex flex-col justify-center">
<div className="max-w-4xl mx-auto w-full">
<h1 className="text-4xl @2xl:text-5xl font-bold mb-2">{SELF_HEALING_HEADLINE}</h1>
<p className="text-lg @2xl:text-xl text-secondary mb-6">{SELF_HEALING_INTRO}</p>
<div className="not-prose">
<TabbedCarousel tabs={tabs} />
</div>
<p className="text-base @2xl:text-lg text-secondary mt-6">{TRACE_IS_THE_EVIDENCE}</p>
</div>
</div>
)
}
50 changes: 50 additions & 0 deletions src/components/Products/Slides/TracesSlackCallout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'
import { IconCheck } from '@posthog/icons'
import { CallToAction } from 'components/CallToAction'

const checklist = [
'Find which span made a request slow',
'Follow a failure across services',
'Catch p99 you can’t reproduce',
'Surface async work that usually hides',
'Turn a trace into a PR',
]

export default function TracesSlackCallout(): JSX.Element {
return (
<div className="h-full text-primary bg-primary overflow-auto p-4 @md:p-8 flex flex-col justify-center">
<div className="border border-primary rounded-md bg-accent overflow-hidden">
<div className="p-6 @2xl:p-8 grid @2xl:grid-cols-2 gap-6 @2xl:gap-10">
<div>
<p className="text-sm font-semibold uppercase tracking-wide text-secondary mb-2">
PostHog in Slack
</p>
<h2 className="text-4xl @2xl:text-3xl font-bold mb-2">
Latency? <span className="font-code font-normal">@PostHog</span>
</h2>
<p className="text-xl mb-6">
Mention <code>@PostHog</code> on a slow endpoint. It pulls the traces, finds the span eating
your time, and opens a PR right there in the thread. You review without leaving the channel.
</p>
<CallToAction to="/slack" type="primary" size="lg">
About the Slack app
</CallToAction>
</div>
<ul className="space-y-3 list-none p-0 m-0 @2xl:self-center">
{checklist.map((item) => (
<li key={item} className="relative pl-6 text-lg">
<IconCheck className="size-5 text-green absolute left-0 top-1" />
{item}
</li>
))}
</ul>
</div>
<img
src="https://res.cloudinary.com/dmukukwp6/image/upload/Group_144141_842b4283dd.png"
alt="PostHog in Slack, fixing a slow trace"
className="mx-auto block w-full max-w-4xl"
/>
</div>
</div>
)
}
Loading
Loading