mirror of
https://github.com/graphql-hive/console
synced 2026-05-24 01:28:32 +00:00
Hive Gateway benchmark table (#6482)
This commit is contained in:
parent
2c0271c284
commit
5307b0c235
6 changed files with 282 additions and 1 deletions
|
|
@ -0,0 +1,101 @@
|
|||
'use client';
|
||||
|
||||
import { use } from 'react';
|
||||
import { cn, ComparisonTable as Table } from '@theguild/components';
|
||||
import { functionalTones } from './functional-tones';
|
||||
import { CheckmarkIcon, XIcon } from './icons';
|
||||
|
||||
interface BenchmarkDatum {
|
||||
name: string;
|
||||
cases: {
|
||||
passed: number;
|
||||
failed: number;
|
||||
};
|
||||
suites: {
|
||||
passed: number;
|
||||
failed: number;
|
||||
};
|
||||
}
|
||||
|
||||
const dataJson = fetch(
|
||||
'https://the-guild.dev/graphql/hive/federation-gateway-audit/data.json',
|
||||
).then(
|
||||
res =>
|
||||
// we didn't parse this, because we trust @kamilkisiela
|
||||
res.json() as Promise<BenchmarkDatum[]>,
|
||||
);
|
||||
|
||||
export function BenchmarkTableBody() {
|
||||
// we're fetching in client component to get fresh data without redeploy
|
||||
// if we don't need it THAT fresh, feel free to just await it in the parent component
|
||||
const data = use(dataJson);
|
||||
|
||||
return (
|
||||
<tbody className="">
|
||||
{data.map(row => {
|
||||
const compatibility = (row.cases.passed / (row.cases.passed + row.cases.failed)) * 100;
|
||||
|
||||
return (
|
||||
<Table.Row key={row.name} highlight={row.name === 'Hive Gateway'}>
|
||||
<Table.Cell
|
||||
className={cn(
|
||||
// todo: this is a bug in Components: we diverged from design
|
||||
row.name === 'Hive Gateway' ? '!bg-green-100' : '',
|
||||
'pl-5', // yes, the dot cuts in to the left per design
|
||||
'max-sm:pr-1.5',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2.5 whitespace-nowrap">
|
||||
<div
|
||||
className="size-3 rounded-full"
|
||||
style={{
|
||||
background:
|
||||
compatibility > 99
|
||||
? functionalTones.positiveBright
|
||||
: compatibility > 90
|
||||
? functionalTones.warning
|
||||
: functionalTones.criticalBright,
|
||||
}}
|
||||
/>
|
||||
{row.name}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="text-sm text-green-800">{compatibility.toFixed(2)}%</Table.Cell>
|
||||
<Table.Cell>
|
||||
<span
|
||||
className="inline-flex items-center gap-0.5 text-sm"
|
||||
style={{ color: functionalTones.positiveDark }}
|
||||
>
|
||||
<CheckmarkIcon className="size-4" /> {row.cases.passed}
|
||||
</span>
|
||||
{row.cases.failed > 0 && (
|
||||
<span
|
||||
className="ml-2 inline-flex items-center text-sm"
|
||||
style={{ color: functionalTones.criticalDark }}
|
||||
>
|
||||
<XIcon className="size-4" /> {row.cases.failed}
|
||||
</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<span
|
||||
className="inline-flex items-center gap-0.5 text-sm"
|
||||
style={{ color: functionalTones.positiveDark }}
|
||||
>
|
||||
<CheckmarkIcon className="size-4" /> {row.suites.passed}
|
||||
</span>
|
||||
{row.suites.failed > 0 && (
|
||||
<span
|
||||
className="ml-2 inline-flex items-center text-sm"
|
||||
style={{ color: functionalTones.criticalDark }}
|
||||
>
|
||||
<XIcon className="size-4" /> {row.suites.failed}
|
||||
</span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* todo: move this to the design system as Tailwind classes
|
||||
*/
|
||||
export const functionalTones = {
|
||||
criticalBright: '#FD3325',
|
||||
criticalDark: ' #F81202',
|
||||
warning: '#FE8830',
|
||||
positiveBright: '#24D551',
|
||||
positiveDark: '#1BA13D',
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// these are different than CheckIcon and CloseIcon we have in the design system
|
||||
|
||||
export function CheckmarkIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" {...props}>
|
||||
<path d="M6.66668 10.1134L12.7947 3.98608L13.7373 4.92875L6.66668 11.9994L2.42401 7.75675L3.36668 6.81408L6.66668 10.1134Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function XIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" {...props}>
|
||||
<path d="M7.99999 7.05806L11.3 3.75806L12.2427 4.70072L8.94266 8.00072L12.2427 11.3007L11.2993 12.2434L7.99932 8.94339L4.69999 12.2434L3.75732 11.3001L7.05732 8.00006L3.75732 4.70006L4.69999 3.75872L7.99999 7.05806Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
import { Suspense } from 'react';
|
||||
import { CallToAction, cn, Heading, ComparisonTable as Table } from '@theguild/components';
|
||||
import { BenchmarkTableBody } from './benchmark-table-body';
|
||||
import { functionalTones } from './functional-tones';
|
||||
import { CheckmarkIcon, XIcon } from './icons';
|
||||
|
||||
export interface FederationCompatibleBenchmarksSectionProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export function FederationCompatibleBenchmarksSection({
|
||||
className,
|
||||
...rest
|
||||
}: FederationCompatibleBenchmarksSectionProps) {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'text-green-1000 px-4 py-6 sm:py-12 md:px-6 lg:py-[120px] xl:px-[120px]',
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<header className="md:text-balance md:text-center">
|
||||
<Heading as="h1" size="lg">
|
||||
Federation-Compatible Gateway Benchmarks
|
||||
</Heading>
|
||||
<p className="mb-6 mt-4 text-green-800 md:mb-16">
|
||||
See the results of our open-source audit for Apollo Federation Gateways.
|
||||
</p>
|
||||
</header>
|
||||
<div className="my-6 flex items-start gap-6 max-md:flex-col md:mb-12 md:mt-16">
|
||||
<p className="text-pretty text-2xl/8 lg:text-[32px]/10">
|
||||
Learn how Hive Gateway performs against other gateways in terms of correctness and
|
||||
compliance with the Apollo Federation specification
|
||||
</p>
|
||||
<CallToAction
|
||||
variant="tertiary"
|
||||
href="https://the-guild.dev/graphql/hive/federation-gateway-audit"
|
||||
>
|
||||
Learn about our audit and methodology
|
||||
</CallToAction>
|
||||
</div>
|
||||
<div className="hive-focus nextra-scrollbar border-beige-400 [&_:is(td,th)]:border-beige-400 overflow-x-auto rounded-2xl border [scrollbar-width:auto] max-sm:-mx-8">
|
||||
<Table className="table w-full border-none max-sm:rounded-none max-sm:text-sm">
|
||||
<thead>
|
||||
<Table.Row className="*:text-left">
|
||||
<Table.Header className="whitespace-pre pl-6">
|
||||
Gateway
|
||||
<small className="block text-xs/[18px] text-green-800">Name and variant</small>
|
||||
</Table.Header>
|
||||
<Table.Header className="whitespace-pre sm:w-1/4">
|
||||
Compatibility
|
||||
<small className="block text-xs/[18px] text-green-800">
|
||||
Pass rate of test cases
|
||||
</small>
|
||||
</Table.Header>
|
||||
<Table.Header className="whitespace-pre sm:w-1/4">
|
||||
Test Cases
|
||||
<small className="block text-xs/[18px] text-green-800">
|
||||
All available test cases
|
||||
</small>
|
||||
</Table.Header>
|
||||
<Table.Header className="whitespace-pre sm:w-1/4">
|
||||
Test Suites
|
||||
<small className="block text-xs/[18px] text-green-800">
|
||||
Test cases grouped by feature
|
||||
</small>
|
||||
</Table.Header>
|
||||
</Table.Row>
|
||||
</thead>
|
||||
<Suspense
|
||||
fallback={
|
||||
<tbody aria-busy>
|
||||
<tr>
|
||||
<td colSpan={4} className="bg-beige-100 h-[347.5px] animate-pulse cursor-wait" />
|
||||
</tr>
|
||||
</tbody>
|
||||
}
|
||||
>
|
||||
<BenchmarkTableBody />
|
||||
</Suspense>
|
||||
</Table>
|
||||
</div>
|
||||
<BenchmarkLegend />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function BenchmarkLegend() {
|
||||
return (
|
||||
<div className="mt-6 flex flex-wrap gap-2 whitespace-nowrap text-xs text-green-800 sm:gap-4">
|
||||
<div className="flex gap-2 max-sm:-mx-1 max-sm:w-full sm:contents">
|
||||
<div className="flex items-center gap-1">
|
||||
<CheckmarkIcon className="size-4" style={{ color: functionalTones.positiveDark }} />{' '}
|
||||
Passed tests
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<XIcon className="size-4" style={{ color: functionalTones.criticalDark }} /> Failed tests
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="size-2 rounded-full"
|
||||
style={{ background: functionalTones.positiveBright }}
|
||||
/>
|
||||
Perfect compatibility
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="size-2 rounded-full" style={{ background: functionalTones.warning }} />
|
||||
75% and higher
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="size-2 rounded-full" style={{ background: functionalTones.criticalDark }} />
|
||||
Less than 75%
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -8,8 +8,10 @@ import {
|
|||
HeroLogo,
|
||||
HiveGatewayIcon,
|
||||
} from '@theguild/components';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
import { LandingPageContainer } from '../../components/landing-page-container';
|
||||
import { metadata as rootMetadata } from '../layout';
|
||||
import { FederationCompatibleBenchmarksSection } from './federation-compatible-benchmarks';
|
||||
import { GatewayFeatureTabs } from './gateway-feature-tabs';
|
||||
import GatewayLandingFAQ from './gateway-landing-faq.mdx';
|
||||
import { OrchestrateYourWay } from './orchestrate-your-way';
|
||||
|
|
@ -54,7 +56,14 @@ export default function HiveGatewayPage() {
|
|||
</Hero>
|
||||
<GatewayFeatureTabs className="relative mt-6 sm:mt-[-72px] sm:bg-blue-100" />
|
||||
<OrchestrateYourWay className="mx-4 mt-6 sm:mx-8" />
|
||||
{/* Federation-Compatible Gateway Benchmarks */}
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
// this section doesn't make sense if data didn't load, so we just unmount
|
||||
null
|
||||
}
|
||||
>
|
||||
<FederationCompatibleBenchmarksSection />
|
||||
</ErrorBoundary>
|
||||
{/* Let's get advanced */}
|
||||
{/* Cloud-Native Nature */}
|
||||
<ExploreMainProductCards className="max-lg:mx-4 max-lg:my-8" />
|
||||
|
|
|
|||
27
packages/web/docs/src/components/error-boundary.ts
Normal file
27
packages/web/docs/src/components/error-boundary.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
'use client';
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
export class ErrorBoundary extends Component<{
|
||||
fallback: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}> {
|
||||
state = { hasError: false };
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
console.error(error);
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: { componentStack: string }) {
|
||||
console.error(error, info);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue