diff --git a/website/package.json b/website/package.json index 6ed98070b..b8d83ed9e 100644 --- a/website/package.json +++ b/website/package.json @@ -16,12 +16,13 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.0.2", "bytes": "^3.1.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dotenv": "^16.1.4", - "lucide-react": "^0.363.0", + "lucide-react": "^0.400.0", "moment": "^2.29.4", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -29,6 +30,8 @@ "react-json-pretty": "^2.2.0", "react-router-dom": "^6.11.2", "react-spinners": "^0.13.8", + "react-use": "^17.5.0", + "recharts": "^2.12.7", "serve": "^14.2.0", "swiper": "^9.4.1", "tailwind-merge": "^2.2.2", diff --git a/website/src/assets/styles/tailwind.css b/website/src/assets/styles/tailwind.css index 63d4c5401..12e5d2c8e 100644 --- a/website/src/assets/styles/tailwind.css +++ b/website/src/assets/styles/tailwind.css @@ -50,6 +50,12 @@ limitations under the License. --ring: 24.6 95% 53.1%; --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --front: 0 0% 10%; --back: 0 0% 100%; @@ -86,6 +92,12 @@ limitations under the License. --ring: 20.5 90.2% 48.2%; --radius: 0.5rem; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --front: 0 0% 100%; --back: 0 0% 10%; diff --git a/website/src/common/DailySummary.tsx b/website/src/common/DailySummary.tsx new file mode 100644 index 000000000..61be81f63 --- /dev/null +++ b/website/src/common/DailySummary.tsx @@ -0,0 +1,126 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, +} from "@/components/ui/chart"; +import { DailySummarydata } from "@/types"; +import PropTypes from "prop-types"; +import { Line, LineChart, XAxis } from "recharts"; + +export type DailySummaryProps = { + data: DailySummarydata; + benchmarkType: string; + setBenchmarktype: (type: string) => void; +}; + +export default function DailySummary({ data }: DailySummaryProps) { + type ChartData = { name: string; totalQps: number }; + const chartData: ChartData[] = []; + + const chartConfig = { + desktop: { + label: "Total QPS", + color: "hsl(var(--primary))", + }, + mobile: { + label: "Total QPS", + color: "hsl(var(--primary))", + }, + } satisfies ChartConfig; + + if (data.data !== null) { + data.data.map((item) => ({ + totalQps: chartData.push({ + name: "Total QPS", + totalQps: item.total_qps.center, + }), + })); + } + + return ( + + + {data.name} + + + + + + + } /> + + + + + + ); +} + +const CustomTooltip = ({ + active, + payload, +}: { + active?: boolean; + payload?: { value: number }[]; +}) => { + if (active && payload && payload.length) { + return ( +
+

+ + {`Total QPS: ${payload[0].value.toFixed(0)}`} +

+
+ ); + } + + return null; +}; + +CustomTooltip.propTypes = { + active: PropTypes.bool, + payload: PropTypes.array, + label: PropTypes.string, +}; diff --git a/website/src/components/ui/chart.tsx b/website/src/components/ui/chart.tsx new file mode 100644 index 000000000..3f4dc4876 --- /dev/null +++ b/website/src/components/ui/chart.tsx @@ -0,0 +1,377 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/library/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +}) +ChartContainer.displayName = "Chart" + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([_, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +