tianji/src/client/components/monitor/MonitorDataChart.tsx
2024-10-15 01:08:04 +08:00

199 lines
5.8 KiB
TypeScript

import { Select } from 'antd';
import dayjs, { Dayjs } from 'dayjs';
import { get, takeRight, uniqBy } from 'lodash-es';
import React, { useState, useMemo } from 'react';
import { useSocketSubscribeList } from '../../api/socketio';
import { trpc } from '../../api/trpc';
import { useCurrentWorkspaceId } from '../../store/user';
import { getMonitorProvider, getProviderDisplay } from './provider';
import { useTranslation } from '@i18next-toolkit/react';
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from '../ui/chart';
import {
Area,
AreaChart,
CartesianGrid,
Customized,
Rectangle,
XAxis,
YAxis,
} from 'recharts';
import { useTheme } from '@/hooks/useTheme';
import { CustomizedErrorArea } from './CustomizedErrorArea';
const chartConfig = {
value: {
label: <span className="text-sm font-bold">Result</span>,
},
} satisfies ChartConfig;
export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
(props) => {
const { t } = useTranslation();
const workspaceId = useCurrentWorkspaceId();
const { monitorId } = props;
const [rangeType, setRangeType] = useState('recent');
const { colors } = useTheme();
const subscribedDataList = useSocketSubscribeList(
'onMonitorReceiveNewData',
{
filter: (data) => {
return data.monitorId === props.monitorId;
},
}
);
const range = useMemo((): [Dayjs, Dayjs] => {
if (rangeType === '3h') {
return [dayjs().subtract(3, 'hour'), dayjs()];
}
if (rangeType === '6h') {
return [dayjs().subtract(6, 'hour'), dayjs()];
}
if (rangeType === '24h') {
return [dayjs().subtract(24, 'hour'), dayjs()];
}
if (rangeType === '1w') {
return [dayjs().subtract(1, 'week'), dayjs()];
}
return [dayjs().subtract(0.5, 'hour'), dayjs()];
}, [rangeType]);
const { data: monitorInfo } = trpc.monitor.get.useQuery({
workspaceId,
monitorId,
});
const { data: _recentData = [] } = trpc.monitor.recentData.useQuery({
workspaceId,
monitorId,
take: 40,
});
const { data: _data = [] } = trpc.monitor.data.useQuery({
workspaceId,
monitorId,
startAt: range[0].valueOf(),
endAt: range[1].valueOf(),
});
const providerInfo = getMonitorProvider(monitorInfo?.type ?? '');
const { data } = useMemo(() => {
let fetchedData = rangeType === 'recent' ? _recentData : _data;
const data = takeRight(
uniqBy([...fetchedData, ...subscribedDataList], 'createdAt'),
fetchedData.length
).map((d, i, arr) => {
const value = d.value > 0 ? d.value : null;
const time = dayjs(d.createdAt).valueOf();
return {
value,
time,
};
});
return { data };
}, [_recentData, _data, subscribedDataList]);
const isTrendingMode = monitorInfo?.trendingMode ?? false; // if true, y axis not start from 0
return (
<div>
<div className="mb-4 text-right">
<Select
className="w-20 text-center"
size="small"
value={rangeType}
onChange={(val) => setRangeType(val)}
>
<Select.Option value="recent">{t('Recent')}</Select.Option>
<Select.Option value="3h">{t('3h')}</Select.Option>
<Select.Option value="6h">{t('6h')}</Select.Option>
<Select.Option value="24h">{t('24h')}</Select.Option>
<Select.Option value="1w">{t('1w')}</Select.Option>
</Select>
</div>
<ChartContainer className="h-[200px] w-full" config={chartConfig}>
<AreaChart
data={data}
margin={{ top: 10, right: 0, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="color" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor={colors.chart.monitor}
stopOpacity={0.3}
/>
<stop
offset="95%"
stopColor={colors.chart.monitor}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
dataKey="time"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={(date) =>
dayjs(date).format(rangeType === '1w' ? 'MM-DD HH:mm' : 'HH:mm')
}
/>
<YAxis
mirror
domain={[isTrendingMode ? 'dataMin' : 0, 'dataMax']}
/>
<CartesianGrid vertical={false} />
<ChartTooltip
labelFormatter={(label, payload) =>
dayjs(get(payload, [0, 'payload', 'time'])).format(
'YYYY-MM-DD HH:mm:ss'
)
}
formatter={(value, defaultText, item, index, payload) => {
if (typeof value !== 'number') {
return defaultText;
}
const { name, text } = getProviderDisplay(
Number(value),
providerInfo
);
return (
<div>
<span className="mr-2">{name}:</span>
<span>{text}</span>
</div>
);
}}
content={<ChartTooltipContent />}
/>
<Customized component={CustomizedErrorArea} />
<Area
type="monotone"
dataKey="value"
stroke={colors.chart.monitor}
fillOpacity={1}
fill="url(#color)"
strokeWidth={2}
isAnimationActive={false}
/>
</AreaChart>
</ChartContainer>
</div>
);
}
);
MonitorDataChart.displayName = 'MonitorDataChart';