Skip to content

Commit

Permalink
[pinpoint-apm#11962] Equalizer Bar Chart to replace real time chart
Browse files Browse the repository at this point in the history
  • Loading branch information
jihea-park committed Jan 16, 2025
1 parent eaab4b1 commit 155b72d
Show file tree
Hide file tree
Showing 7 changed files with 616 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React from 'react';
import {
ChartConfig,
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from '../ui';
import {
Bar,
BarChart,
CartesianGrid,
Cell,
ReferenceLine,
ResponsiveContainer,
XAxis,
YAxis,
} from 'recharts';
import { AgentActiveSettingType } from './AgentActiveSetting';
import { AgentActiveData } from './AgentActiveTable';

export const DefaultValue = { yMax: 100 };

export interface AgentActiveChartProps {
data?: AgentActiveData[];
setting?: AgentActiveSettingType;
clickedActiveThread?: string;
setClickedActiveThread?: React.Dispatch<React.SetStateAction<string>>;
}

const DefaultReferenceLineLength = 50;
const chartConfig = {
slow: {
label: 'slow',
color: '#e67f22',
},
'5s': {
label: '5s',
color: '#ffba00',
},
'3s': {
label: '3s',
color: '#51afdf',
},
'1s': {
label: '1s',
color: '#34b994',
},
} satisfies ChartConfig;

export const AgentActiveChart = ({
data,
setting,
clickedActiveThread,
setClickedActiveThread,
}: AgentActiveChartProps) => {
const chartContainerRef = React.useRef<HTMLDivElement>(null);
const height = chartContainerRef?.current?.clientHeight || 0;
const yMax = setting?.yMax || DefaultValue.yMax;
const ReferenceLineLength = yMax < DefaultReferenceLineLength ? yMax : DefaultReferenceLineLength;

return (
<ResponsiveContainer>
<ChartContainer
config={chartConfig}
className="p-1.5 z-0 min-w-[50%]"
ref={chartContainerRef}
>
<BarChart
accessibilityLayer
data={data || []}
onClick={(props) =>
setClickedActiveThread?.((prev) => {
const activeLabel = props?.activeLabel || '';
return prev === activeLabel ? '' : activeLabel;
})
}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="server"
hide={true}
tickLine={false}
axisLine={false}
type="category"
interval={0}
tick={(props) => <CustomTick {...props} />}
/>
<YAxis
tickLine={false}
axisLine={false}
tickMargin={10}
allowDecimals={false}
domain={() => [0, yMax]}
/>
<ChartTooltip content={<ChartTooltipContent />} />
<ChartLegend content={<ChartLegendContent />} />
{Object.keys(chartConfig).map((key) => (
<Bar
key={key}
dataKey={key}
stackId="agentActiveThread"
fill={chartConfig[key as keyof typeof chartConfig].color}
>
{data?.map((entry, index) => (
<Cell
key={`cell-${index}`}
opacity={
clickedActiveThread ? (entry.server === clickedActiveThread ? 1 : 0.3) : 1
}
/>
))}
</Bar>
))}
{Array.from({ length: ReferenceLineLength }, (_, index) => {
return (
<ReferenceLine
key={index}
y={Math.floor(yMax / ReferenceLineLength) * (index + 1)}
stroke="white"
strokeWidth={height >= 650 ? 4 : height >= 450 ? 3 : height >= 300 ? 2 : 1}
isFront={true}
/>
);
})}
</BarChart>
</ChartContainer>
</ResponsiveContainer>
);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const CustomTick = (props: any) => {
const { payload, x, y } = props;
const maxLength = Math.ceil(payload?.offset / 10);
const value = payload?.value;
const displayValue = value?.length <= maxLength ? value : value?.slice(0, maxLength) + '...';

return (
<g transform={`translate(${x},${y})`}>
<text x={0} y={0} dy={16} textAnchor="middle" fill="#666">
{displayValue}
</text>
</g>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from 'react';
import { useOnClickOutside } from 'usehooks-ts';
import { cn } from '@pinpoint-fe/ui/lib';
import {
Button,
Input,
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Checkbox,
} from '@pinpoint-fe/ui/components/ui';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

export interface AgentActiveSettingProps {
className?: string;
onApply?: (value: AgentActiveSettingType) => void;
onClose?: () => void;
defaultValues?: AgentActiveSettingType;
}

export const DefaultValue = { yMax: 100, isSplit: true };

const FormSchema = z.object({
yMax: z.coerce.number(),
isSplit: z.boolean(),
});

export type AgentActiveSettingType = z.infer<typeof FormSchema>;

export const AgentActiveSetting = ({
className,
onApply,
onClose,
defaultValues = DefaultValue,
}: AgentActiveSettingProps) => {
const containerRef = React.useRef(null);

const handleClickClose = () => {
onClose?.();
};

useOnClickOutside(containerRef, handleClickClose);

const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
yMax: defaultValues?.yMax,
isSplit: defaultValues?.isSplit,
},
});

function onSubmit(data: z.infer<typeof FormSchema>) {
onApply?.(data);
handleClickClose();
}

const handleKeyDown = (e: React.KeyboardEvent) => {
const data = form.getValues();

if (e.key === 'Enter') {
onApply?.(data);
}
};

return (
<div
className={cn(
'rounded shadow bg-background p-4 w-60 flex gap-3 flex-col text-sm border',
className,
)}
ref={containerRef}
>
<div className="mb-3 font-semibold">Agent request chart Setting</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
<FormField
control={form.control}
name="yMax"
render={({ field }) => (
<FormItem>
<FormLabel className="text-xs text-muted-foreground">Max of Y axis</FormLabel>
<FormControl>
<Input type="number" className="w-24 h-7" onKeyDown={handleKeyDown} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="isSplit"
render={({ field }) => (
<FormItem>
<FormLabel className="text-xs text-muted-foreground">
Split chart in 2 (&gt;=100)
</FormLabel>
<FormControl className="flex flex-row items-start space-x-3 space-y-0">
<Checkbox
checked={field.value}
onCheckedChange={(checked) => {
return field.onChange(checked);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-1 mt-6">
<Button className="text-xs h-7" variant="outline" onClick={handleClickClose}>
Cancel
</Button>
<Button type="submit" className="text-xs h-7">
Apply
</Button>
</div>
</form>
</Form>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { VirtualizedDataTable } from '../DataTable';
import { ColumnDef } from '@tanstack/react-table';
import { cn } from '@pinpoint-fe/ui/lib';
import React from 'react';

export type AgentActiveData = {
server: string;
'1s': number;
'3s': number;
'5s': number;
slow: number;
};

const SIZE = 50;

export const AgentActiveTable = ({
data,
clickedActiveThread,
}: {
data: AgentActiveData[];
clickedActiveThread?: string;
}) => {
const focusRowId = React.useMemo(() => {
return data.findIndex((d) => d.server === clickedActiveThread);
}, [data, clickedActiveThread]);

const columns: ColumnDef<AgentActiveData>[] = [
{
accessorKey: 'server',
header: 'Server',
size: 200,
},
{
accessorKey: 'slow',
header: 'Slow',
size: SIZE,
meta: {
headerClassName: 'flex justify-end',
cellClassName: 'text-right',
},
cell: ({ getValue }) => {
const value = getValue<number>() || 0;
return (
<span
className={cn({
'text-red-500 font-bold': value > 0,
})}
>
{value}
</span>
);
},
},
{
accessorKey: '5s',
header: '5s',
size: SIZE,
meta: {
headerClassName: 'flex justify-end',
cellClassName: 'text-right',
},
cell: ({ getValue }) => {
const value = getValue<number>() || 0;
return (
<span
className={cn({
'text-red-500 font-bold': value > 0,
})}
>
{value}
</span>
);
},
},
{
accessorKey: '3s',
header: '3s',
size: SIZE,
meta: {
headerClassName: 'flex justify-end',
cellClassName: 'text-right',
},
},
{
accessorKey: '1s',
header: '1s',
size: SIZE,
meta: {
headerClassName: 'flex justify-end',
cellClassName: 'text-right',
},
},
];

return (
<div className="block h-[-webkit-fill-available] max-w-[50%] w-auto">
<VirtualizedDataTable
tableClassName="[&>tbody]:text-xs w-auto"
columns={columns}
data={data || []}
focusRowIndex={focusRowId}
rowClassName={(row) => {
if (row?.id === String(focusRowId)) {
return 'bg-yellow-200';
}
return '';
}}
/>
</div>
);
};
Loading

0 comments on commit 155b72d

Please sign in to comment.