Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions frontend/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-tooltip": "^1.2.8",
"@sentry/react": "^10.30.0",
"@t3-oss/env-core": "^0.13.8",
"@tanstack/react-query": "^5.90.12",
Expand Down
46 changes: 46 additions & 0 deletions frontend/src/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import {cn} from 'utils/cn';

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

interface TooltipContentProps extends React.ComponentPropsWithoutRef<
typeof TooltipPrimitive.Content
> {
ref?: React.Ref<HTMLDivElement>;
}

function TooltipContent({
className,
align = 'center',
sideOffset = 4,
ref,
...props
}: TooltipContentProps) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50',
'rounded-radius-md',
'border',
'border-gray-200',
'bg-background-primary',
'p-space-md',
'shadow-lg',
'outline-none',
className
)}
{...props}
/>
</TooltipPrimitive.Portal>
);
}

export {TooltipProvider, Tooltip, TooltipTrigger, TooltipContent};
17 changes: 10 additions & 7 deletions frontend/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import type {QueryClient} from '@tanstack/react-query';
import {createRootRouteWithContext, Outlet} from '@tanstack/react-router';
import {TanStackRouterDevtools} from '@tanstack/react-router-devtools';
import {ErrorState} from 'components/ErrorState';
import {TooltipProvider} from 'components/Tooltip';

import {Header} from './components/Header';

const RootLayout = () => (
<div className="bg-background-tertiary text-content-primary leading-default min-h-screen">
<Header />
<main className="px-space-md py-space-xl md:px-space-xl mx-auto max-w-6xl">
<Outlet />
</main>
<TanStackRouterDevtools />
</div>
<TooltipProvider delayDuration={200}>
<div className="bg-background-tertiary text-content-primary leading-default min-h-screen">
<Header />
<main className="px-space-md py-space-xl md:px-space-xl mx-auto max-w-6xl">
<Outlet />
</main>
<TanStackRouterDevtools />
</div>
</TooltipProvider>
);

export const Route = createRootRouteWithContext<{queryClient: QueryClient}>()({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {Tooltip, TooltipContent, TooltipTrigger} from 'components/Tooltip';
import {Info} from 'lucide-react';

export function AvailabilityTooltip() {
return (
<Tooltip>
<TooltipTrigger asChild>
<button
className="text-content-secondary hover:text-content-primary transition-colors"
aria-label="How availability is calculated"
>
<Info size={18} />
</button>
</TooltipTrigger>
<TooltipContent align="start" className="text-size-sm max-w-sm">
<h3 className="text-content-headings mb-space-sm font-semibold">
How availability is calculated
</h3>
<p className="text-content-secondary mb-space-sm">
Availability percentage is calculated as:
</p>
<p className="text-content-primary bg-background-secondary mb-space-sm px-space-sm py-space-xs text-size-xs rounded font-mono">
(Total Time &minus; Downtime) / Total Time &times; 100
</p>
<p className="text-content-secondary mb-space-sm">
Only <strong>T0 service tier</strong> incidents with{' '}
<strong>availability impact</strong> are included.
</p>
<p className="text-content-secondary mb-space-md">
Downtime is captured in the time period the incident was created.
</p>
<h4 className="text-content-headings mb-space-xs font-medium">
Color thresholds
</h4>
<ul className="text-content-secondary space-y-space-xs">
<li className="gap-space-xs flex items-center">
<span className="bg-graphics-success-moderate inline-block size-3 rounded" />
<span>Green: &ge; 99.9%</span>
</li>
<li className="gap-space-xs flex items-center">
<span className="bg-graphics-warning-moderate inline-block size-3 rounded" />
<span>Yellow: &ge; 99.85%</span>
</li>
<li className="gap-space-xs flex items-center">
<span className="bg-graphics-danger-moderate inline-block size-3 rounded" />
<span>Red: &lt; 99.85%</span>
</li>
</ul>
</TooltipContent>
</Tooltip>
);
}
12 changes: 8 additions & 4 deletions frontend/src/routes/availability/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {ErrorState} from 'components/ErrorState';
import {GetHelpLink} from 'components/GetHelpLink';
import {z} from 'zod';

import {AvailabilityTooltip} from './components/AvailabilityTooltip';
import {PeriodLabels} from './components/PeriodLabels';
import {PeriodTabs} from './components/PeriodTabs';
import {RegionRow} from './components/RegionRow';
Expand Down Expand Up @@ -73,16 +74,19 @@ function AvailabilityPage() {
<div className="flex flex-col">
<div className="mb-space-xl flex flex-wrap items-end justify-between gap-4">
<div>
<h1 className="text-content-headings text-size-2xl font-semibold">
Availability by Region
</h1>
<div className="gap-space-sm flex items-center">
<h1 className="text-content-headings text-size-2xl font-semibold">
Availability by Region
</h1>
<AvailabilityTooltip />
</div>
<p className="text-content-secondary mt-space-xs text-size-sm">
{getDateRangeLabel(periods)}
</p>
</div>
<PeriodTabs activePeriod={activePeriod} />
</div>
<Card className="flex flex-col gap-space-md px-space-xl pt-space-sm pb-space-lg">
<Card className="gap-space-md px-space-xl pt-space-sm pb-space-lg flex flex-col">
<PeriodLabels periods={periods} />
{regionNames.map(name => (
<RegionRow key={name} regionName={name} periods={periods} />
Expand Down
Loading