forked from pinpoint-apm/pinpoint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[pinpoint-apm#11962] Equalizer Bar Chart to replace real time chart
- Loading branch information
1 parent
eaab4b1
commit 155b72d
Showing
7 changed files
with
616 additions
and
23 deletions.
There are no files selected for viewing
147 changes: 147 additions & 0 deletions
147
web-frontend/src/main/v3/packages/ui/src/components/AgentActiveThread/AgentActiveChart.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
126 changes: 126 additions & 0 deletions
126
web-frontend/src/main/v3/packages/ui/src/components/AgentActiveThread/AgentActiveSetting.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 (>=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> | ||
); | ||
}; |
111 changes: 111 additions & 0 deletions
111
web-frontend/src/main/v3/packages/ui/src/components/AgentActiveThread/AgentActiveTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
Oops, something went wrong.