mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-05 04:50:55 +08:00
feat: 图表支持存储拖拽排序 & 补点逻辑优化
This commit is contained in:
@@ -2,9 +2,10 @@ import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, use
|
||||
import { AppContainer, Drawer, Spin, Table, SingleChart, Utils, Tooltip } from 'knowdesign';
|
||||
import moment from 'moment';
|
||||
import api, { MetricType } from '@src/api';
|
||||
import { MetricDefaultChartDataType, MetricChartDataType, formatChartData } from '@src/constants/chartConfig';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { debounce } from 'lodash';
|
||||
import { MetricDefaultChartDataType, MetricChartDataType, formatChartData, getDetailChartConfig } from './config';
|
||||
import { getDetailChartConfig } from './config';
|
||||
import { UNIT_MAP } from '@src/constants/chartConfig';
|
||||
import RenderEmpty from '../RenderEmpty';
|
||||
|
||||
@@ -50,8 +51,6 @@ interface DataZoomEventProps {
|
||||
const DATA_ZOOM_DEFAULT_SCALE = 0.25;
|
||||
// 单次向服务器请求数据的范围(默认 6 小时,超过后采集频率间隔会变长),单位: ms
|
||||
const DEFAULT_REQUEST_TIME_RANGE = 6 * 60 * 60 * 1000;
|
||||
// 采样间隔,影响前端补点逻辑,单位: ms
|
||||
const DEFAULT_POINT_INTERVAL = 60 * 1000;
|
||||
// 向服务器每轮请求的数量
|
||||
const DEFAULT_REQUEST_COUNT = 6;
|
||||
// 进入详情页默认展示的时间范围
|
||||
@@ -376,8 +375,6 @@ const ChartDetail = (props: ChartDetailProps) => {
|
||||
global.getMetricDefine || {},
|
||||
metricType,
|
||||
timeRange,
|
||||
DEFAULT_POINT_INTERVAL,
|
||||
false,
|
||||
chartInfo.current.transformUnit
|
||||
) as MetricChartDataType[];
|
||||
// 增量填充图表数据
|
||||
@@ -540,14 +537,7 @@ const ChartDetail = (props: ChartDetailProps) => {
|
||||
if (res?.length) {
|
||||
// 格式化图表需要的数据
|
||||
const formattedMetricData = (
|
||||
formatChartData(
|
||||
res,
|
||||
global.getMetricDefine || {},
|
||||
metricType,
|
||||
curTimeRange,
|
||||
DEFAULT_POINT_INTERVAL,
|
||||
false
|
||||
) as MetricChartDataType[]
|
||||
formatChartData(res, global.getMetricDefine || {}, metricType, curTimeRange) as MetricChartDataType[]
|
||||
)[0];
|
||||
// 填充图表数据
|
||||
let initFullTimeRange = curTimeRange;
|
||||
|
||||
@@ -1,150 +1,4 @@
|
||||
import { getUnit, getDataNumberUnit, getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfig';
|
||||
import { MetricType } from '@src/api';
|
||||
import { MetricsDefine } from '@src/pages/CommonConfig';
|
||||
|
||||
export interface MetricInfo {
|
||||
name: string;
|
||||
desc: string;
|
||||
type: number;
|
||||
set: boolean;
|
||||
support: boolean;
|
||||
}
|
||||
|
||||
// 接口返回图表原始数据类型
|
||||
export interface MetricDefaultChartDataType {
|
||||
metricName: string;
|
||||
metricLines: {
|
||||
name: string;
|
||||
createTime: number;
|
||||
updateTime: number;
|
||||
metricPoints: {
|
||||
aggType: string;
|
||||
timeStamp: number;
|
||||
value: number;
|
||||
createTime: number;
|
||||
updateTime: number;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
// 格式化后图表数据类型
|
||||
export interface MetricChartDataType {
|
||||
metricName: string;
|
||||
metricUnit: string;
|
||||
metricLines: {
|
||||
name: string;
|
||||
data: (string | number)[][];
|
||||
}[];
|
||||
dragKey?: number;
|
||||
}
|
||||
|
||||
// 补点
|
||||
export const supplementaryPoints = (
|
||||
lines: MetricChartDataType['metricLines'],
|
||||
timeRange: readonly [number, number],
|
||||
interval: number,
|
||||
extraCallback?: (point: [number, 0]) => any[]
|
||||
) => {
|
||||
lines.forEach(({ data }) => {
|
||||
// 获取未补点前线条的点的个数
|
||||
let len = data.length;
|
||||
// 记录当前处理到的点的下标值
|
||||
let i = 0;
|
||||
|
||||
for (; i < len; i++) {
|
||||
if (i === 0) {
|
||||
let firstPointTimestamp = data[0][0] as number;
|
||||
while (firstPointTimestamp - interval > timeRange[0]) {
|
||||
const prevPointTimestamp = firstPointTimestamp - interval;
|
||||
data.unshift(extraCallback ? extraCallback([prevPointTimestamp, 0]) : [prevPointTimestamp, 0]);
|
||||
firstPointTimestamp = prevPointTimestamp;
|
||||
len++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (i === len - 1) {
|
||||
let lastPointTimestamp = data[i][0] as number;
|
||||
while (lastPointTimestamp + interval < timeRange[1]) {
|
||||
const nextPointTimestamp = lastPointTimestamp + interval;
|
||||
data.push(extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
|
||||
lastPointTimestamp = nextPointTimestamp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
let timestamp = data[i][0] as number;
|
||||
while (timestamp + interval < data[i + 1][0]) {
|
||||
const nextPointTimestamp = timestamp + interval;
|
||||
data.splice(i + 1, 0, extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
|
||||
timestamp = nextPointTimestamp;
|
||||
len++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化图表数据
|
||||
export const formatChartData = (
|
||||
metricData: MetricDefaultChartDataType[],
|
||||
getMetricDefine: (type: MetricType, metric: string) => MetricsDefine[keyof MetricsDefine],
|
||||
metricType: MetricType,
|
||||
timeRange: readonly [number, number],
|
||||
supplementaryInterval: number,
|
||||
needDrag = false,
|
||||
transformUnit: [string, number] = undefined
|
||||
): MetricChartDataType[] => {
|
||||
return metricData.map(({ metricName, metricLines }) => {
|
||||
const curMetricInfo = (getMetricDefine && getMetricDefine(metricType, metricName)) || null;
|
||||
const isByteUnit = curMetricInfo?.unit?.toLowerCase().includes('byte');
|
||||
let maxValue = -1;
|
||||
|
||||
const PointsMapMethod = ({ timeStamp, value }: { timeStamp: number; value: string | number }) => {
|
||||
let parsedValue: string | number = Number(value);
|
||||
|
||||
if (Number.isNaN(parsedValue)) {
|
||||
parsedValue = value;
|
||||
} else {
|
||||
// 为避免出现过小的数字影响图表展示效果,图表值统一只保留到小数点后三位
|
||||
parsedValue = parseFloat(parsedValue.toFixed(3));
|
||||
if (maxValue < parsedValue) maxValue = parsedValue;
|
||||
}
|
||||
|
||||
return [timeStamp, parsedValue];
|
||||
};
|
||||
|
||||
const chartData = Object.assign(
|
||||
{
|
||||
metricName,
|
||||
metricUnit: curMetricInfo?.unit || '',
|
||||
metricLines: metricLines
|
||||
.sort((a, b) => Number(a.name < b.name) - 0.5)
|
||||
.map(({ name, metricPoints }) => ({
|
||||
name,
|
||||
data: metricPoints.map(PointsMapMethod),
|
||||
})),
|
||||
},
|
||||
needDrag ? { dragKey: 999 } : {}
|
||||
);
|
||||
|
||||
chartData.metricLines.forEach(({ data }) => data.sort((a, b) => (a[0] as number) - (b[0] as number)));
|
||||
supplementaryPoints(chartData.metricLines, timeRange, supplementaryInterval);
|
||||
|
||||
// 将所有图表点的值按单位进行转换
|
||||
if (maxValue > 0) {
|
||||
const [unitName, unitSize]: [string, number] = transformUnit || isByteUnit ? getUnit(maxValue) : getDataNumberUnit(maxValue);
|
||||
chartData.metricUnit = isByteUnit
|
||||
? chartData.metricUnit.toLowerCase().replace('byte', unitName)
|
||||
: `${unitName}${chartData.metricUnit}`;
|
||||
chartData.metricLines.forEach(({ data }) => data.forEach((point: any) => (point[1] /= unitSize)));
|
||||
}
|
||||
|
||||
return chartData;
|
||||
});
|
||||
};
|
||||
import { getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfig';
|
||||
|
||||
const seriesCallback = (lines: { name: string; data: [number, string | number][] }[]) => {
|
||||
const len = CHART_COLOR_LIST.length;
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { arrayMoveImmutable } from 'array-move';
|
||||
import { Utils, Empty, IconFont, Spin, AppContainer, SingleChart, Tooltip } from 'knowdesign';
|
||||
import { Utils, Empty, Spin, AppContainer, SingleChart, Tooltip } from 'knowdesign';
|
||||
import { IconFont } from '@knowdesign/icons';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import api, { MetricType } from '@src/api';
|
||||
import {
|
||||
MetricInfo,
|
||||
MetricDefaultChartDataType,
|
||||
MetricChartDataType,
|
||||
formatChartData,
|
||||
resolveMetricsRank,
|
||||
} from '@src/constants/chartConfig';
|
||||
import SingleChartHeader, { KsHeaderOptions } from '../SingleChartHeader';
|
||||
import DragGroup from '../DragGroup';
|
||||
import ChartDetail from './ChartDetail';
|
||||
import { MetricInfo, MetricDefaultChartDataType, MetricChartDataType, formatChartData, getChartConfig } from './config';
|
||||
import { getChartConfig } from './config';
|
||||
import './index.less';
|
||||
import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common';
|
||||
|
||||
interface IcustomScope {
|
||||
label: string;
|
||||
@@ -39,8 +46,8 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
||||
const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>();
|
||||
const [metricChartData, setMetricChartData] = useState<MetricChartDataType[]>([]); // 指标图表数据列表
|
||||
const [gridNum, setGridNum] = useState<number>(12); // 图表列布局
|
||||
const metricRankList = useRef<string[]>([]);
|
||||
const chartDetailRef = useRef(null);
|
||||
const chartDragOrder = useRef([]);
|
||||
const curFetchingTimestamp = useRef(0);
|
||||
|
||||
// 获取节点范围列表
|
||||
@@ -60,23 +67,33 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
||||
setScopeList(list);
|
||||
};
|
||||
|
||||
// 更新 rank
|
||||
const updateRank = (metricList: MetricInfo[]) => {
|
||||
const { list, listInfo, shouldUpdate } = resolveMetricsRank(metricList);
|
||||
metricRankList.current = list;
|
||||
if (shouldUpdate) {
|
||||
setMetricList(listInfo);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取指标列表
|
||||
const getMetricList = () => {
|
||||
Utils.request(api.getDashboardMetricList(clusterId, dashboardType)).then((res: MetricInfo[] | null) => {
|
||||
if (!res) return;
|
||||
const showMetrics = res.filter((metric) => metric.support);
|
||||
const selectedMetrics = showMetrics.filter((metric) => metric.set).map((metric) => metric.name);
|
||||
setMetricsList(showMetrics);
|
||||
const supportMetrics = res.filter((metric) => metric.support);
|
||||
const selectedMetrics = supportMetrics.filter((metric) => metric.set).map((metric) => metric.name);
|
||||
updateRank([...supportMetrics]);
|
||||
setMetricsList(supportMetrics);
|
||||
setSelectedMetricNames(selectedMetrics);
|
||||
});
|
||||
};
|
||||
|
||||
// 更新指标
|
||||
const setMetricList = (metricsSet: { [name: string]: boolean }) => {
|
||||
const setMetricList = (metricDetailDTOList: { metric: string; rank: number; set: boolean }[]) => {
|
||||
return Utils.request(api.getDashboardMetricList(clusterId, dashboardType), {
|
||||
method: 'POST',
|
||||
data: {
|
||||
metricsSet,
|
||||
metricDetailDTOList,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -84,10 +101,11 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
||||
// 根据筛选项获取图表信息
|
||||
const getMetricChartData = () => {
|
||||
!curHeaderOptions.isAutoReload && setLoading(true);
|
||||
const [startTime, endTime] = curHeaderOptions.rangeTime;
|
||||
|
||||
const [startTime, endTime] = curHeaderOptions.rangeTime;
|
||||
const curTimestamp = Date.now();
|
||||
curFetchingTimestamp.current = curTimestamp;
|
||||
|
||||
Utils.post(api.getDashboardMetricChartData(clusterId, dashboardType), {
|
||||
startTime,
|
||||
endTime,
|
||||
@@ -108,36 +126,20 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
||||
setMetricChartData([]);
|
||||
} else {
|
||||
// 格式化图表需要的数据
|
||||
const supplementaryInterval = (endTime - startTime > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
|
||||
const formattedMetricData = formatChartData(
|
||||
res,
|
||||
global.getMetricDefine || {},
|
||||
dashboardType,
|
||||
curHeaderOptions.rangeTime,
|
||||
supplementaryInterval,
|
||||
true
|
||||
curHeaderOptions.rangeTime
|
||||
) as MetricChartDataType[];
|
||||
// 处理图表的拖拽顺序
|
||||
if (chartDragOrder.current && chartDragOrder.current.length) {
|
||||
// 根据当前拖拽顺序排列图表数据
|
||||
formattedMetricData.forEach((metric) => {
|
||||
const i = chartDragOrder.current.indexOf(metric.metricName);
|
||||
metric.dragKey = i === -1 ? 999 : i;
|
||||
});
|
||||
formattedMetricData.sort((a, b) => a.dragKey - b.dragKey);
|
||||
}
|
||||
// 更新当前拖拽顺序(处理新增或减少图表的情况)
|
||||
chartDragOrder.current = formattedMetricData.map((data) => data.metricName);
|
||||
// 指标排序
|
||||
formattedMetricData.sort((a, b) => metricRankList.current.indexOf(a.metricName) - metricRankList.current.indexOf(b.metricName));
|
||||
|
||||
setMetricChartData(formattedMetricData);
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
() => {
|
||||
if (curFetchingTimestamp.current === curTimestamp) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
() => curFetchingTimestamp.current === curTimestamp && setLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -163,11 +165,19 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
||||
|
||||
// 指标选中项更新回调
|
||||
const indicatorChangeCallback = (newMetricNames: (string | number)[]) => {
|
||||
const updateMetrics: { [name: string]: boolean } = {};
|
||||
const updateMetrics: { metric: string; set: boolean; rank: number }[] = [];
|
||||
// 需要选中的指标
|
||||
newMetricNames.forEach((name) => !selectedMetricNames.includes(name) && (updateMetrics[name] = true));
|
||||
newMetricNames.forEach(
|
||||
(name) =>
|
||||
!selectedMetricNames.includes(name) &&
|
||||
updateMetrics.push({ metric: name as string, set: true, rank: metricsList.find(({ name: metric }) => metric === name)?.rank })
|
||||
);
|
||||
// 取消选中的指标
|
||||
selectedMetricNames.forEach((name) => !newMetricNames.includes(name) && (updateMetrics[name] = false));
|
||||
selectedMetricNames.forEach(
|
||||
(name) =>
|
||||
!newMetricNames.includes(name) &&
|
||||
updateMetrics.push({ metric: name as string, set: false, rank: metricsList.find(({ name: metric }) => metric === name)?.rank })
|
||||
);
|
||||
|
||||
const requestPromise = Object.keys(updateMetrics).length ? setMetricList(updateMetrics) : Promise.resolve();
|
||||
requestPromise.then(
|
||||
@@ -186,7 +196,11 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
||||
// 拖拽结束回调,更新图表顺序,并触发图表的 onDrag 事件( 设置为 false ),允许同步展示图表的 tooltip
|
||||
const dragEnd = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {
|
||||
busInstance.emit('onDrag', false);
|
||||
chartDragOrder.current = arrayMoveImmutable(chartDragOrder.current, oldIndex, newIndex);
|
||||
const originFrom = metricRankList.current.indexOf(metricChartData[oldIndex].metricName);
|
||||
const originTarget = metricRankList.current.indexOf(metricChartData[newIndex].metricName);
|
||||
const newList = arrayMoveImmutable(metricRankList.current, originFrom, originTarget);
|
||||
metricRankList.current = newList;
|
||||
setMetricList(newList.map((metric, rank) => ({ metric, rank, set: metricsList.find(({ name }) => metric === name)?.set || false })));
|
||||
setMetricChartData(arrayMoveImmutable(metricChartData, oldIndex, newIndex));
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user